diff --git a/public/css/generator.css b/public/css/generator.css index d0082b1..0dd3471 100644 --- a/public/css/generator.css +++ b/public/css/generator.css @@ -21,7 +21,7 @@ body { display: flex; flex-direction: column; align-items: center; - justify-content: center; + justify-content: flex-start; padding: 20px; background-image: radial-gradient(circle at 20% 80%, #1a1a2e 0%, transparent 50%), @@ -38,11 +38,10 @@ body { max-width: 700px; width: 100%; position: relative; - overflow: hidden; - flex-grow: 1; + overflow: visible; + margin-bottom: 20px; display: flex; flex-direction: column; - justify-content: center; border: 1px solid rgba(255, 255, 255, 0.1); } diff --git a/public/css/leaderboard.css b/public/css/leaderboard.css index 29bbd38..ea36c56 100644 --- a/public/css/leaderboard.css +++ b/public/css/leaderboard.css @@ -24,6 +24,38 @@ body { min-height: 100vh; } +/* Language Selector */ +.language-selector { + position: fixed; + top: 2rem; + left: 2rem; + z-index: 1000; +} + +.language-selector select { + padding: 0.5rem 1rem; + background: rgba(15, 23, 42, 0.9); + border: 1px solid #334155; + border-radius: 0.5rem; + color: #ffffff; + font-size: 0.9rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + backdrop-filter: blur(10px); +} + +.language-selector select:hover { + border-color: #00d4ff; + box-shadow: 0 0 0 2px rgba(0, 212, 255, 0.1); +} + +.language-selector select:focus { + outline: none; + border-color: #00d4ff; + box-shadow: 0 0 0 3px rgba(0, 212, 255, 0.2); +} + .header-section { text-align: center; margin-bottom: 3rem; diff --git a/public/generator.html b/public/generator.html index 05722ab..85244c8 100644 --- a/public/generator.html +++ b/public/generator.html @@ -67,5 +67,19 @@ + diff --git a/public/index.html b/public/index.html index eb6e1e4..fd1f6f2 100644 --- a/public/index.html +++ b/public/index.html @@ -1,5 +1,5 @@ - + @@ -15,13 +15,21 @@
🏁
-
Neue Zeit!
-
Ein neuer Rekord wurde erstellt
+
New Time!
+
A new record has been created
+ +
+ +
+
@@ -31,47 +39,47 @@

NINJACROSS LEADERBOARD

-

Die ultimative NinjaCross-Rangliste

+

The ultimate NinjaCross leaderboard

- +
-
- +
-
@@ -80,15 +88,15 @@
0
-
Teilnehmer
+
Participants
--:--
-
Rekordzeit
+
Record Time
0
-
Läufe
+
Runs
@@ -97,15 +105,15 @@
- 📍 Bitte Standort auswählen • ♾️ Alle Zeiten + 📍 Please select location • ♾️ All Times
- Letzter Sync: Loading... + Last Sync: Loading...
- +
@@ -114,12 +122,12 @@ diff --git a/public/js/leaderboard.js b/public/js/leaderboard.js index 0420d28..6846f5f 100644 --- a/public/js/leaderboard.js +++ b/public/js/leaderboard.js @@ -65,15 +65,15 @@ function saveLocationSelection(locationId, locationName) { // WebSocket Event Handlers socket.on('connect', () => { - console.log('🔌 WebSocket verbunden'); + console.log('🔌 WebSocket connected'); }); socket.on('disconnect', () => { - console.log('🔌 WebSocket getrennt'); + console.log('🔌 WebSocket disconnected'); }); socket.on('newTime', (data) => { - console.log('🏁 Neue Zeit empfangen:', data); + console.log('🏁 New time received:', data); showNotification(data); // Reload data to show the new time loadData(); @@ -86,12 +86,13 @@ function showNotification(timeData) { const notificationSubtitle = document.getElementById('notificationSubtitle'); // Format the time data - const playerName = timeData.player_name || 'Unbekannter Spieler'; - const locationName = timeData.location_name || 'Unbekannter Standort'; + const playerName = timeData.player_name || (currentLanguage === 'de' ? 'Unbekannter Spieler' : 'Unknown Player'); + const locationName = timeData.location_name || (currentLanguage === 'de' ? 'Unbekannter Standort' : 'Unknown Location'); const timeString = timeData.recorded_time || '--:--'; // Update notification content - notificationTitle.textContent = `🏁 Neue Zeit von ${playerName}!`; + const newTimeText = currentLanguage === 'de' ? 'Neue Zeit von' : 'New time from'; + notificationTitle.textContent = `🏁 ${newTimeText} ${playerName}!`; notificationSubtitle.textContent = `${timeString} • ${locationName}`; // Show notification @@ -171,7 +172,8 @@ async function loadLocations() { locationsData = locations; // Clear existing options and set default placeholder - locationSelect.innerHTML = ''; + const placeholderText = currentLanguage === 'de' ? '📍 Bitte Standort auswählen' : '📍 Please select location'; + locationSelect.innerHTML = ``; // Add locations from database locations.forEach(location => { @@ -230,14 +232,17 @@ async function findNearestLocation() { // Check if geolocation is supported if (!navigator.geolocation) { - showLocationError('Geolocation wird von diesem Browser nicht unterstützt.'); + const errorMsg = currentLanguage === 'de' ? + 'Geolocation wird von diesem Browser nicht unterstützt.' : + 'Geolocation is not supported by this browser.'; + showLocationError(errorMsg); return; } // Update button state to loading btn.disabled = true; btn.classList.add('loading'); - btn.textContent = '🔍 Suche...'; + btn.textContent = currentLanguage === 'de' ? '🔍 Suche...' : '🔍 Searching...'; try { // Get user's current position @@ -283,18 +288,24 @@ async function findNearestLocation() { } catch (error) { console.error('Error getting location:', error); - let errorMessage = 'Standort konnte nicht ermittelt werden.'; + let errorMessage = currentLanguage === 'de' ? 'Standort konnte nicht ermittelt werden.' : 'Location could not be determined.'; if (error.code) { switch(error.code) { case error.PERMISSION_DENIED: - errorMessage = 'Standortzugriff wurde verweigert. Bitte erlaube den Standortzugriff in den Browser-Einstellungen.'; + errorMessage = currentLanguage === 'de' ? + 'Standortzugriff wurde verweigert. Bitte erlaube den Standortzugriff in den Browser-Einstellungen.' : + 'Location access was denied. Please allow location access in browser settings.'; break; case error.POSITION_UNAVAILABLE: - errorMessage = 'Standortinformationen sind nicht verfügbar.'; + errorMessage = currentLanguage === 'de' ? + 'Standortinformationen sind nicht verfügbar.' : + 'Location information is not available.'; break; case error.TIMEOUT: - errorMessage = 'Zeitüberschreitung beim Abrufen des Standorts.'; + errorMessage = currentLanguage === 'de' ? + 'Zeitüberschreitung beim Abrufen des Standorts.' : + 'Timeout while retrieving location.'; break; } } @@ -304,7 +315,7 @@ async function findNearestLocation() { // Reset button state btn.disabled = false; btn.classList.remove('loading'); - btn.textContent = '📍 Mein Standort'; + btn.textContent = currentLanguage === 'de' ? '📍 Mein Standort' : '📍 My Location'; } } @@ -315,8 +326,10 @@ function showLocationSuccess(locationName, distance) { const notificationSubtitle = document.getElementById('notificationSubtitle'); // Update notification content - notificationTitle.textContent = `📍 Standort gefunden!`; - notificationSubtitle.textContent = `${locationName} (${distance.toFixed(1)} km entfernt)`; + const locationFoundText = currentLanguage === 'de' ? 'Standort gefunden!' : 'Location found!'; + const distanceText = currentLanguage === 'de' ? 'km entfernt' : 'km away'; + notificationTitle.textContent = `📍 ${locationFoundText}`; + notificationSubtitle.textContent = `${locationName} (${distance.toFixed(1)} ${distanceText})`; // Show notification notificationBubble.classList.remove('hide'); @@ -338,7 +351,8 @@ function showLocationError(message) { notificationBubble.style.background = 'linear-gradient(135deg, #dc3545, #c82333)'; // Update notification content - notificationTitle.textContent = '❌ Fehler'; + const errorText = currentLanguage === 'de' ? 'Fehler' : 'Error'; + notificationTitle.textContent = `❌ ${errorText}`; notificationSubtitle.textContent = message; // Show notification @@ -356,15 +370,16 @@ function showLocationError(message) { // Show prompt when no location is selected function showLocationSelectionPrompt() { const rankingList = document.getElementById('rankingList'); + const emptyTitle = currentLanguage === 'de' ? 'Standort auswählen' : 'Select Location'; + const emptyDescription = currentLanguage === 'de' ? + 'Bitte wähle einen Standort aus dem Dropdown-Menü aus
oder nutze den "📍 Mein Standort" Button, um automatisch
den nächstgelegenen Standort zu finden.' : + 'Please select a location from the dropdown menu
or use the "📍 My Location" button to automatically
find the nearest location.'; + rankingList.innerHTML = `
📍
-
Standort auswählen
-
- Bitte wähle einen Standort aus dem Dropdown-Menü aus
- oder nutze den "📍 Mein Standort" Button, um automatisch
- den nächstgelegenen Standort zu finden. -
+
${emptyTitle}
+
${emptyDescription}
`; @@ -410,10 +425,11 @@ async function loadData() { const leaderboardData = times.map(time => { const { minutes, seconds, milliseconds } = time.recorded_time; const timeString = `${minutes}:${seconds.toString().padStart(2, '0')}.${milliseconds}`; - const playerName = time.player ? - `${time.player.firstname} ${time.player.lastname}` : - 'Unknown Player'; - const locationName = time.location ? time.location.name : 'Unknown Location'; + const playerName = time.player ? + `${time.player.firstname} ${time.player.lastname}` : + (currentLanguage === 'de' ? 'Unbekannter Spieler' : 'Unknown Player'); + const locationName = time.location ? time.location.name : + (currentLanguage === 'de' ? 'Unbekannter Standort' : 'Unknown Location'); const date = new Date(time.created_at).toISOString().split('T')[0]; return { @@ -482,34 +498,43 @@ function updateCurrentSelection() { // Get the display text from the selected option const locationSelect = document.getElementById('locationSelect'); const selectedLocationOption = locationSelect.options[locationSelect.selectedIndex]; - const locationDisplay = selectedLocationOption ? selectedLocationOption.textContent : '📍 Bitte Standort auswählen'; + const locationDisplay = selectedLocationOption ? selectedLocationOption.textContent : + (currentLanguage === 'de' ? '📍 Bitte Standort auswählen' : '📍 Please select location'); - const periodIcons = { + const periodIcons = currentLanguage === 'de' ? { 'today': '📅 Heute', 'week': '📊 Diese Woche', 'month': '📈 Dieser Monat', 'all': '♾️ Alle Zeiten' + } : { + 'today': '📅 Today', + 'week': '📊 This Week', + 'month': '📈 This Month', + 'all': '♾️ All Times' }; document.getElementById('currentSelection').textContent = `${locationDisplay} • ${periodIcons[period]}`; + const lastSyncText = currentLanguage === 'de' ? 'Letzter Sync' : 'Last Sync'; document.getElementById('lastUpdated').textContent = - `Letzter Sync: ${new Date().toLocaleTimeString('de-DE')}`; + `${lastSyncText}: ${new Date().toLocaleTimeString(currentLanguage === 'de' ? 'de-DE' : 'en-US')}`; } function updateLeaderboard(data) { const rankingList = document.getElementById('rankingList'); if (data.length === 0) { + const emptyTitle = currentLanguage === 'de' ? 'Keine Rekorde gefunden' : 'No records found'; + const emptyDescription = currentLanguage === 'de' ? + 'Für diese Filtereinstellungen liegen noch keine Zeiten vor.
Versuche es mit einem anderen Zeitraum oder Standort.' : + 'No times available for these filter settings.
Try a different time period or location.'; + rankingList.innerHTML = `
🏁
-
Keine Rekorde gefunden
-
- Für diese Filtereinstellungen liegen noch keine Zeiten vor.
- Versuche es mit einem anderen Zeitraum oder Standort. -
+
${emptyTitle}
+
${emptyDescription}
`; return; @@ -533,7 +558,7 @@ function updateLeaderboard(data) { trophy = '⭐'; } - const formatDate = new Date(player.date).toLocaleDateString('de-DE', { + const formatDate = new Date(player.date).toLocaleDateString(currentLanguage === 'de' ? 'de-DE' : 'en-US', { day: '2-digit', month: 'short' }); @@ -600,8 +625,99 @@ function startAutoRefresh() { setInterval(loadData, 45000); } +// Language Management +let currentLanguage = 'en'; // Default to English + +// Translation function +function translateElement(element, language) { + if (element.dataset[language]) { + element.textContent = element.dataset[language]; + } +} + +// Change language function +function changeLanguage() { + const languageSelect = document.getElementById('languageSelect'); + currentLanguage = languageSelect.value; + + // Save language preference + localStorage.setItem('ninjacross_language', currentLanguage); + + // Translate all elements with data attributes + const elementsToTranslate = document.querySelectorAll('[data-de][data-en]'); + elementsToTranslate.forEach(element => { + translateElement(element, currentLanguage); + }); + + // Update dynamic content + updateDynamicContent(); + + console.log(`🌐 Language changed to: ${currentLanguage}`); +} + +// Update dynamic content that's not in HTML +function updateDynamicContent() { + // Update location select placeholder + const locationSelect = document.getElementById('locationSelect'); + if (locationSelect && locationSelect.options[0]) { + locationSelect.options[0].textContent = currentLanguage === 'de' ? + '📍 Bitte Standort auswählen' : '📍 Please select location'; + } + + // Update find location button + const findLocationBtn = document.getElementById('findLocationBtn'); + if (findLocationBtn) { + findLocationBtn.textContent = currentLanguage === 'de' ? + '📍 Mein Standort' : '📍 My Location'; + findLocationBtn.title = currentLanguage === 'de' ? + 'Nächstgelegenen Standort finden' : 'Find nearest location'; + } + + // Update refresh button + const refreshBtn = document.querySelector('.refresh-btn'); + if (refreshBtn) { + refreshBtn.textContent = currentLanguage === 'de' ? + '⚡ Live Update' : '⚡ Live Update'; + } + + // Update notification elements + const notificationTitle = document.getElementById('notificationTitle'); + const notificationSubtitle = document.getElementById('notificationSubtitle'); + if (notificationTitle) { + notificationTitle.textContent = currentLanguage === 'de' ? 'Neue Zeit!' : 'New Time!'; + } + if (notificationSubtitle) { + notificationSubtitle.textContent = currentLanguage === 'de' ? + 'Ein neuer Rekord wurde erstellt' : 'A new record has been created'; + } + + // Update current selection display + updateCurrentSelection(); + + // Reload data to update any dynamic content + if (document.getElementById('locationSelect').value) { + loadData(); + } else { + showLocationSelectionPrompt(); + } +} + +// Load saved language preference +function loadLanguagePreference() { + const savedLanguage = localStorage.getItem('ninjacross_language'); + if (savedLanguage && (savedLanguage === 'de' || savedLanguage === 'en')) { + currentLanguage = savedLanguage; + const languageSelect = document.getElementById('languageSelect'); + if (languageSelect) { + languageSelect.value = currentLanguage; + } + } +} + // Start the application when DOM is loaded document.addEventListener('DOMContentLoaded', function() { + loadLanguagePreference(); + changeLanguage(); // Apply saved language init(); startAutoRefresh(); diff --git a/public/js/login.js b/public/js/login.js index 2c28356..5eebe3a 100644 --- a/public/js/login.js +++ b/public/js/login.js @@ -9,7 +9,11 @@ const supabase = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY); async function checkAuth() { const { data: { session } } = await supabase.auth.getSession(); if (session) { - window.location.href = '/'; + // Show a message that user is already logged in + showMessage('Sie sind bereits eingeloggt! Weiterleitung zum Dashboard...', 'success'); + setTimeout(() => { + window.location.href = '/'; + }, 2000); } }