Файловый менеджер - Редактировать - /home/gqdcvggs/go.imators.com/bike-speed.tar
Назад
index.php 0000644 00000115314 15114743070 0006373 0 ustar 00 <!DOCTYPE html> <html lang="fr"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Bike Speed Tracker</title> <script src="https://cdn.tailwindcss.com"></script> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; overflow: hidden; height: 100vh; background: linear-gradient(135deg, #0f0f23, #1a1a3e); } .access-denied { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(135deg, #0f0f23, #1a1a3e); justify-content: center; align-items: center; flex-direction: column; z-index: 10000; color: white; } .settings-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.95); backdrop-filter: blur(20px); z-index: 9999; display: none; opacity: 0; transition: opacity 0.3s ease; overflow-y: auto; padding: 20px; color: white; } .settings-overlay.active { opacity: 1; } .main-interface { display: none; grid-template-columns: 200px 1fr 200px; height: 100vh; gap: 4px; background: linear-gradient(135deg, #0f0f23, #1a1a3e); } .side-panel { background: rgba(255,255,255,0.02); backdrop-filter: blur(10px); border: 1px solid rgba(255,255,255,0.05); display: flex; flex-direction: column; justify-content: space-evenly; align-items: center; padding: 15px 10px; position: relative; } .map-zone { position: relative; overflow: hidden; border-radius: 0; background: #000; } #map { height: 100%; width: 100%; filter: contrast(1.1) brightness(0.9); } .metric-card { background: rgba(255,255,255,0.03); backdrop-filter: blur(15px); border: 1px solid rgba(255,255,255,0.08); border-radius: 16px; padding: 16px 12px; text-align: center; width: 100%; margin: 8px 0; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); position: relative; overflow: hidden; } .metric-card::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent); transition: left 0.5s ease; } .metric-card:hover::before { left: 100%; } .metric-label { font-size: 10px; color: rgba(255,255,255,0.6); text-transform: uppercase; letter-spacing: 1.5px; margin-bottom: 8px; font-weight: 600; } .metric-value { font-size: 26px; font-weight: 900; font-family: 'Courier New', monospace; margin: 6px 0; text-shadow: 0 0 20px currentColor; transition: all 0.2s cubic-bezier(0.68, -0.55, 0.265, 1.55); position: relative; } .metric-unit { font-size: 9px; color: rgba(255,255,255,0.4); font-weight: 500; } .speed-current { color: #00ff88; animation: glow-green 2s ease-in-out infinite alternate; } .speed-max { color: #ff4466; animation: glow-red 3s ease-in-out infinite alternate; } .distance { color: #4488ff; animation: glow-blue 2.5s ease-in-out infinite alternate; } .timer-color { color: #ffaa00; animation: glow-orange 1.5s ease-in-out infinite alternate; } @keyframes glow-green { 0% { text-shadow: 0 0 5px #00ff88, 0 0 10px #00ff88; } 100% { text-shadow: 0 0 10px #00ff88, 0 0 20px #00ff88, 0 0 30px #00ff88; } } @keyframes glow-red { 0% { text-shadow: 0 0 5px #ff4466, 0 0 10px #ff4466; } 100% { text-shadow: 0 0 10px #ff4466, 0 0 20px #ff4466, 0 0 30px #ff4466; } } @keyframes glow-blue { 0% { text-shadow: 0 0 5px #4488ff, 0 0 10px #4488ff; } 100% { text-shadow: 0 0 10px #4488ff, 0 0 20px #4488ff, 0 0 30px #4488ff; } } @keyframes glow-orange { 0% { text-shadow: 0 0 5px #ffaa00, 0 0 10px #ffaa00; } 100% { text-shadow: 0 0 10px #ffaa00, 0 0 20px #ffaa00, 0 0 30px #ffaa00; } } .pulse-update { animation: pulse-scale 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55); } @keyframes pulse-scale { 0% { transform: scale(1); } 50% { transform: scale(1.15); } 100% { transform: scale(1); } } .timer-container { display: flex; flex-direction: column; align-items: center; gap: 12px; } .timer-ring { width: 70px; height: 70px; border-radius: 50%; border: 3px solid rgba(255,170,0,0.2); position: relative; display: flex; align-items: center; justify-content: center; background: rgba(255,170,0,0.05); } .timer-progress { position: absolute; top: -3px; left: -3px; width: 70px; height: 70px; border-radius: 50%; border: 3px solid transparent; border-top-color: #ffaa00; transition: transform 0.3s ease; filter: drop-shadow(0 0 10px #ffaa00); } .timer-value { font-weight: 900; font-size: 16px; color: #ffaa00; font-family: 'Courier New', monospace; } .timer-result { font-size: 13px; font-weight: bold; color: #ffaa00; min-height: 20px; text-shadow: 0 0 10px #ffaa00; } .floating-controls { position: absolute; top: 15px; right: 15px; z-index: 1000; display: flex; gap: 8px; } .control-btn { width: 44px; height: 44px; background: rgba(0,0,0,0.7); backdrop-filter: blur(10px); border: 1px solid rgba(255,255,255,0.1); border-radius: 12px; color: white; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 18px; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); position: relative; overflow: hidden; } .control-btn::before { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0; background: rgba(255,255,255,0.1); border-radius: 50%; transition: all 0.3s ease; transform: translate(-50%, -50%); } .control-btn:hover::before { width: 100%; height: 100%; } .control-btn:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(0,0,0,0.3); } .debug-display { position: absolute; bottom: 15px; left: 15px; background: rgba(0,0,0,0.8); backdrop-filter: blur(10px); color: #00ff88; padding: 8px 12px; border-radius: 8px; font-size: 10px; font-family: 'Courier New', monospace; border: 1px solid #00ff88; display: none; animation: blink 2s infinite; } @keyframes blink { 0%, 90% { opacity: 1; } 95% { opacity: 0.5; } 100% { opacity: 1; } } .speed-trail { position: absolute; top: 0; left: 0; width: 100%; height: 4px; background: linear-gradient(90deg, transparent, #00ff88, transparent); opacity: 0; transition: opacity 0.3s ease; } .speed-trail.active { opacity: 1; animation: trail-move 1s ease-in-out infinite; } @keyframes trail-move { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } } .settings-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-top: 30px; } .setting-group { background: rgba(255,255,255,0.05); backdrop-filter: blur(15px); border: 1px solid rgba(255,255,255,0.1); border-radius: 16px; padding: 24px; } .setting-title { font-size: 20px; font-weight: bold; margin-bottom: 20px; color: #fff; text-shadow: 0 0 10px rgba(255,255,255,0.3); } .setting-item { display: flex; justify-content: space-between; align-items: center; margin: 15px 0; padding: 12px 0; border-bottom: 1px solid rgba(255,255,255,0.1); } .setting-label { color: rgba(255,255,255,0.8); font-size: 14px; font-weight: 500; } .toggle-switch { position: relative; width: 54px; height: 28px; background: rgba(255,255,255,0.2); border-radius: 28px; cursor: pointer; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); border: 1px solid rgba(255,255,255,0.1); } .toggle-switch.active { background: linear-gradient(45deg, #00ff88, #00cc6a); box-shadow: 0 0 20px rgba(0,255,136,0.3); } .toggle-switch::after { content: ''; position: absolute; top: 2px; left: 2px; width: 22px; height: 22px; background: white; border-radius: 50%; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 0 2px 8px rgba(0,0,0,0.2); } .toggle-switch.active::after { left: 28px; } input[type="range"] { width: 120px; height: 6px; background: rgba(255,255,255,0.1); border-radius: 3px; outline: none; cursor: pointer; } input[type="range"]::-webkit-slider-thumb { appearance: none; width: 18px; height: 18px; background: linear-gradient(45deg, #4488ff, #2266dd); border-radius: 50%; cursor: pointer; box-shadow: 0 0 10px rgba(68,136,255,0.5); } input[type="color"] { width: 44px; height: 32px; border: none; border-radius: 8px; cursor: pointer; background: none; } select { background: rgba(0,0,0,0.6); color: white; border: 1px solid rgba(255,255,255,0.2); border-radius: 8px; padding: 8px 12px; cursor: pointer; } @media (max-width: 768px) and (orientation: portrait) { .main-interface { display: none !important; } .settings-overlay { display: block; opacity: 1; } } @media (max-width: 768px) and (orientation: landscape) { .main-interface { display: grid; } .access-denied { display: none !important; } } @media (min-width: 1024px) { .access-denied { display: flex !important; } .main-interface { display: none !important; } .settings-overlay { display: none !important; } } </style> </head> <body> <div class="access-denied" id="accessDenied"> <div style="text-align: center;"> <div style="font-size: 64px; margin-bottom: 30px; animation: glow-blue 2s ease-in-out infinite alternate;">📱</div> <h1 style="font-size: 28px; margin-bottom: 15px; font-weight: 900;">Mode Mobile Requis</h1> <p style="color: rgba(255,255,255,0.7); font-size: 16px;">Cette application est optimisée pour smartphone en mode paysage uniquement.</p> <p style="color: rgba(255,255,255,0.5); margin-top: 25px; font-size: 14px;">Utilisez votre téléphone et tournez-le en mode paysage</p> </div> </div> <div class="settings-overlay" id="settingsOverlay"> <div style="max-width: 800px; margin: 0 auto;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 40px;"> <h1 style="font-size: 32px; font-weight: 900; background: linear-gradient(45deg, #00ff88, #4488ff); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">Paramètres</h1> <button onclick="closeSettings()" class="control-btn" style="width: auto; padding: 0 20px; font-size: 16px;"> Retour </button> </div> <div class="settings-grid"> <div class="setting-group"> <div class="setting-title">🎨 Interface</div> <div class="setting-item"> <span class="setting-label">Couleur vitesse actuelle</span> <input type="color" value="#00ff88" id="colorSpeed"> </div> <div class="setting-item"> <span class="setting-label">Couleur vitesse max</span> <input type="color" value="#ff4466" id="colorMaxSpeed"> </div> <div class="setting-item"> <span class="setting-label">Couleur distance</span> <input type="color" value="#4488ff" id="colorDistance"> </div> <div class="setting-item"> <span class="setting-label">Mode debug</span> <div class="toggle-switch" onclick="toggleDebug()" id="debugToggle"></div> </div> <div class="setting-item"> <span class="setting-label">Effets visuels</span> <div class="toggle-switch active" onclick="toggleEffects()" id="effectsToggle"></div> </div> </div> <div class="setting-group"> <div class="setting-title">📡 GPS & Tracking</div> <div class="setting-item"> <span class="setting-label">Précision GPS</span> <select id="gpsAccuracy"> <option value="high">Haute précision</option> <option value="balanced">Équilibrée</option> <option value="low">Économie d'énergie</option> </select> </div> <div class="setting-item"> <span class="setting-label">Filtrage vitesse intelligent</span> <div class="toggle-switch active" onclick="toggleSpeedFilter()" id="speedFilterToggle"></div> </div> <div class="setting-item"> <span class="setting-label">Vitesse max filtrée</span> <div style="display: flex; align-items: center; gap: 10px;"> <input type="range" min="50" max="300" value="200" id="maxSpeedFilter"> <span id="maxSpeedFilterValue">200 km/h</span> </div> </div> </div> <div class="setting-group"> <div class="setting-title">⚡ Performance</div> <div class="setting-item"> <span class="setting-label">Fréquence mise à jour</span> <div style="display: flex; align-items: center; gap: 10px;"> <input type="range" min="100" max="1000" value="300" id="updateFreq"> <span id="updateFreqValue">300ms</span> </div> </div> <div class="setting-item"> <span class="setting-label">Vibrations haptiques</span> <div class="toggle-switch active" onclick="toggleVibration()" id="vibrationToggle"></div> </div> <div class="setting-item"> <span class="setting-label">Auto-sauvegarde session</span> <div class="toggle-switch active" onclick="toggleAutoSave()" id="autoSaveToggle"></div> </div> </div> <div class="setting-group"> <div class="setting-title">⏱️ Timer & Sessions</div> <div class="setting-item"> <span class="setting-label">Durée timer</span> <div style="display: flex; align-items: center; gap: 10px;"> <input type="range" min="5" max="60" value="10" id="timerDuration"> <span id="timerDurationValue">10s</span> </div> </div> <div class="setting-item"> <span class="setting-label">Unité vitesse</span> <select id="speedUnit"> <option value="kmh">km/h</option> <option value="mph">mph</option> <option value="ms">m/s</option> </select> </div> </div> </div> <div style="margin-top: 50px; text-align: center;"> <button onclick="resetSettings()" style="background: linear-gradient(45deg, #ff4466, #cc2244); color: white; border: none; padding: 15px 30px; border-radius: 12px; font-size: 16px; font-weight: bold; cursor: pointer; box-shadow: 0 4px 15px rgba(255,68,102,0.3);"> Réinitialiser Paramètres </button> </div> </div> </div> <div class="main-interface" id="mainInterface"> <div class="side-panel"> <div class="metric-card"> <div class="speed-trail" id="speedTrail"></div> <div class="metric-label">Vitesse</div> <div class="metric-value speed-current" id="currentSpeed">0</div> <div class="metric-unit">km/h</div> </div> <div class="metric-card"> <div class="metric-label">Distance</div> <div class="metric-value distance" id="distance">0</div> <div class="metric-unit">mètres</div> </div> </div> <div class="map-zone"> <div id="map"></div> <div class="floating-controls"> <button class="control-btn" onclick="resetSession()" title="Reset Session">🔄</button> <button class="control-btn" onclick="openSettings()" title="Paramètres">⚙️</button> </div> <div class="debug-display" id="debugDisplay"> GPS: INIT | Speed: 0 km/h | Updates: 0 </div> </div> <div class="side-panel"> <div class="metric-card"> <div class="metric-label">Record</div> <div class="metric-value speed-max" id="maxSpeed">0</div> <div class="metric-unit">km/h</div> </div> <div class="metric-card"> <div class="timer-container"> <div class="metric-label">Timer</div> <div class="timer-ring"> <div class="timer-progress" id="timerProgress"></div> <div class="timer-value" id="timerValue">10</div> </div> <div class="timer-result" id="timerResult"></div> </div> </div> </div> </div> <script> let map, locationMarker, userPath = [], pathLine; let currentPosition = null, lastPosition = null; let distance = 0, currentSpeed = 0, maxSpeed = 0; let lapInterval, lapTimer = 10, isShowingResult = false; let lapSpeeds = []; let gpsActive = false, updateCount = 0; let lastUpdateTime = Date.now(); let settings = { colorSpeed: '#00ff88', colorMaxSpeed: '#ff4466', colorDistance: '#4488ff', debugMode: false, effectsEnabled: true, gpsAccuracy: 'high', speedFilter: true, maxSpeedFilter: 200, updateFreq: 300, vibration: true, autoSave: true, timerDuration: 10, speedUnit: 'kmh' }; function checkAccess() { const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); const isLandscape = window.innerWidth > window.innerHeight; const isDesktop = window.innerWidth >= 1024; if (isDesktop || !isMobile) { document.getElementById('accessDenied').style.display = 'flex'; document.getElementById('mainInterface').style.display = 'none'; document.getElementById('settingsOverlay').style.display = 'none'; return false; } if (!isLandscape) { document.getElementById('accessDenied').style.display = 'none'; document.getElementById('mainInterface').style.display = 'none'; document.getElementById('settingsOverlay').style.display = 'block'; document.getElementById('settingsOverlay').classList.add('active'); return false; } document.getElementById('accessDenied').style.display = 'none'; document.getElementById('settingsOverlay').style.display = 'none'; document.getElementById('mainInterface').style.display = 'grid'; return true; } function openSettings() { document.getElementById('settingsOverlay').style.display = 'block'; setTimeout(() => { document.getElementById('settingsOverlay').classList.add('active'); }, 10); } function closeSettings() { document.getElementById('settingsOverlay').classList.remove('active'); setTimeout(() => { if (checkAccess()) { document.getElementById('settingsOverlay').style.display = 'none'; } }, 300); } function initMap() { if (!map) { map = L.map('map', { zoomControl: false, attributionControl: false }).setView([50.8503, 4.3517], 18); L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { maxZoom: 19 }).addTo(map); startGPS(); startLapTimer(); } } function startGPS() { if (navigator.geolocation) { const options = { enableHighAccuracy: settings.gpsAccuracy === 'high', maximumAge: settings.gpsAccuracy === 'high' ? 0 : 30000, timeout: settings.updateFreq }; navigator.geolocation.watchPosition(updatePosition, handleGPSError, options); } } function updatePosition(position) { const lat = position.coords.latitude; const lng = position.coords.longitude; const timestamp = Date.now(); gpsActive = true; currentPosition = { lat, lng, timestamp }; if (!locationMarker) { const locationIcon = L.divIcon({ className: 'custom-location-icon', html: `<div style='background: ${settings.colorSpeed}; width: 14px; height: 14px; border-radius: 50%; border: 3px solid white; box-shadow: 0 0 20px ${settings.colorSpeed};'></div>`, iconSize: [14, 14], iconAnchor: [7, 7] }); locationMarker = L.marker([lat, lng], { icon: locationIcon }).addTo(map); map.setView([lat, lng], 18); } else { locationMarker.setLatLng([lat, lng]); map.panTo([lat, lng], { animate: false }); } if (lastPosition) { const distanceIncrement = calculateDistance(lastPosition, currentPosition); distance += distanceIncrement; const timeDiff = (timestamp - lastPosition.timestamp) / 1000; if (timeDiff > 0) { let speed = (distanceIncrement / timeDiff) * 3.6; if (settings.speedFilter && speed > settings.maxSpeedFilter) { speed = currentSpeed * 0.9; } currentSpeed = Math.max(0, speed); if (currentSpeed > maxSpeed) { maxSpeed = currentSpeed; if (settings.vibration && navigator.vibrate) { navigator.vibrate([50, 30, 50]); } } lapSpeeds.push(currentSpeed); updateDisplay(); } updatePath(lat, lng); } lastPosition = currentPosition; updateCount++; updateDebugInfo(); } function calculateDistance(pos1, pos2) { const R = 6371000; const dLat = (pos2.lat - pos1.lat) * Math.PI / 180; const dLng = (pos2.lng - pos1.lng) * Math.PI / 180; const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(pos1.lat * Math.PI / 180) * Math.cos(pos2.lat * Math.PI / 180) * Math.sin(dLng/2) * Math.sin(dLng/2); return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); } function updatePath(lat, lng) { userPath.push([lat, lng]); if (pathLine) { map.removeLayer(pathLine); } pathLine = L.polyline(userPath, { color: settings.colorSpeed, weight: 4, opacity: 0.9, dashArray: null }).addTo(map); } function updateDisplay() { const speed = parseFloat(currentSpeed.toFixed(1)); const dist = Math.round(distance); const record = parseFloat(maxSpeed.toFixed(1)); const elements = [ { el: document.getElementById('currentSpeed'), value: speed, prevValue: speed }, { el: document.getElementById('distance'), value: dist, prevValue: dist }, { el: document.getElementById('maxSpeed'), value: record, prevValue: record } ]; elements.forEach(item => { if (item.el && item.el.textContent != item.value) { item.el.textContent = item.value; if (settings.effectsEnabled) { item.el.classList.add('pulse-update'); setTimeout(() => item.el.classList.remove('pulse-update'), 400); } } }); if (settings.effectsEnabled && currentSpeed > 5) { document.getElementById('speedTrail').classList.add('active'); } else { document.getElementById('speedTrail').classList.remove('active'); } } function startLapTimer() { if (lapInterval) clearInterval(lapInterval); lapInterval = setInterval(() => { if (isShowingResult) return; lapTimer--; const timerEl = document.getElementById('timerValue'); if (timerEl) timerEl.textContent = lapTimer; const progress = ((settings.timerDuration - lapTimer) / settings.timerDuration) * 360; const progressEl = document.getElementById('timerProgress'); if (progressEl) progressEl.style.transform = `rotate(${progress}deg)`; if (lapTimer <= 0) { showLapResult(); } }, 1000); } function showLapResult() { isShowingResult = true; clearInterval(lapInterval); const avgSpeed = lapSpeeds.length > 0 ? (lapSpeeds.reduce((a, b) => a + b, 0) / lapSpeeds.length).toFixed(1) : 0; const resultEl = document.getElementById('timerResult'); if (resultEl) { resultEl.textContent = `${avgSpeed} km/h`; if (settings.effectsEnabled) { resultEl.style.animation = 'glow-orange 0.5s ease-in-out 3'; } } if (settings.vibration && navigator.vibrate) { navigator.vibrate([100, 50, 100, 50, 200]); } setTimeout(() => { isShowingResult = false; lapTimer = settings.timerDuration; lapSpeeds = []; if (resultEl) { resultEl.textContent = ''; resultEl.style.animation = ''; } const progressEl = document.getElementById('timerProgress'); if (progressEl) progressEl.style.transform = 'rotate(0deg)'; startLapTimer(); }, 4000); } function resetSession() { distance = 0; currentSpeed = 0; maxSpeed = 0; userPath = []; lapSpeeds = []; updateCount = 0; if (pathLine && map) { map.removeLayer(pathLine); pathLine = null; } updateDisplay(); if (settings.vibration && navigator.vibrate) { navigator.vibrate([200, 100, 200]); } } function updateDebugInfo() { if (settings.debugMode) { const fps = Math.round(1000 / (Date.now() - lastUpdateTime)); lastUpdateTime = Date.now(); const debugEl = document.getElementById('debugDisplay'); if (debugEl) { debugEl.textContent = `GPS: ${gpsActive ? 'ACTIVE' : 'WAITING'} | Speed: ${currentSpeed.toFixed(1)} km/h | FPS: ${fps} | Updates: ${updateCount}`; debugEl.style.display = 'block'; } } else { document.getElementById('debugDisplay').style.display = 'none'; } } function handleGPSError(error) { gpsActive = false; updateDebugInfo(); console.log('GPS Error:', error.message); } function toggleDebug() { settings.debugMode = !settings.debugMode; const toggle = document.getElementById('debugToggle'); toggle.classList.toggle('active', settings.debugMode); updateDebugInfo(); } function toggleEffects() { settings.effectsEnabled = !settings.effectsEnabled; const toggle = document.getElementById('effectsToggle'); toggle.classList.toggle('active', settings.effectsEnabled); } function toggleSpeedFilter() { settings.speedFilter = !settings.speedFilter; const toggle = document.getElementById('speedFilterToggle'); toggle.classList.toggle('active', settings.speedFilter); } function toggleVibration() { settings.vibration = !settings.vibration; const toggle = document.getElementById('vibrationToggle'); toggle.classList.toggle('active', settings.vibration); } function toggleAutoSave() { settings.autoSave = !settings.autoSave; const toggle = document.getElementById('autoSaveToggle'); toggle.classList.toggle('active', settings.autoSave); } function resetSettings() { if (confirm('Réinitialiser tous les paramètres ?')) { localStorage.clear(); location.reload(); } } function initSettings() { document.getElementById('colorSpeed').addEventListener('change', (e) => { settings.colorSpeed = e.target.value; updateColors(); }); document.getElementById('colorMaxSpeed').addEventListener('change', (e) => { settings.colorMaxSpeed = e.target.value; updateColors(); }); document.getElementById('colorDistance').addEventListener('change', (e) => { settings.colorDistance = e.target.value; updateColors(); }); document.getElementById('maxSpeedFilter').addEventListener('input', (e) => { settings.maxSpeedFilter = parseInt(e.target.value); document.getElementById('maxSpeedFilterValue').textContent = e.target.value + ' km/h'; }); document.getElementById('updateFreq').addEventListener('input', (e) => { settings.updateFreq = parseInt(e.target.value); document.getElementById('updateFreqValue').textContent = e.target.value + 'ms'; }); document.getElementById('timerDuration').addEventListener('input', (e) => { settings.timerDuration = parseInt(e.target.value); document.getElementById('timerDurationValue').textContent = e.target.value + 's'; lapTimer = settings.timerDuration; if (document.getElementById('timerValue')) { document.getElementById('timerValue').textContent = settings.timerDuration; } }); document.getElementById('gpsAccuracy').addEventListener('change', (e) => { settings.gpsAccuracy = e.target.value; }); document.getElementById('speedUnit').addEventListener('change', (e) => { settings.speedUnit = e.target.value; }); } function updateColors() { const root = document.documentElement; root.style.setProperty('--speed-color', settings.colorSpeed); root.style.setProperty('--max-speed-color', settings.colorMaxSpeed); root.style.setProperty('--distance-color', settings.colorDistance); const speedEl = document.querySelector('.speed-current'); const maxSpeedEl = document.querySelector('.speed-max'); const distanceEl = document.querySelector('.distance'); if (speedEl) speedEl.style.color = settings.colorSpeed; if (maxSpeedEl) maxSpeedEl.style.color = settings.colorMaxSpeed; if (distanceEl) distanceEl.style.color = settings.colorDistance; if (pathLine) { pathLine.setStyle({ color: settings.colorSpeed }); } } function saveSettings() { if (settings.autoSave) { localStorage.setItem('bikeTrackerSettings', JSON.stringify(settings)); } } function loadSettings() { const saved = localStorage.getItem('bikeTrackerSettings'); if (saved) { settings = { ...settings, ...JSON.parse(saved) }; updateColors(); } } window.addEventListener('load', () => { loadSettings(); initSettings(); if (checkAccess()) { setTimeout(initMap, 100); } }); window.addEventListener('resize', () => { setTimeout(checkAccess, 100); }); window.addEventListener('orientationchange', () => { setTimeout(() => { checkAccess(); if (map) { setTimeout(() => map.invalidateSize(), 300); } }, 300); }); window.addEventListener('beforeunload', () => { saveSettings(); }); setInterval(() => { if (settings.autoSave) { saveSettings(); } }, 30000); </script> </body> </html>