From e85b0fc51c7d8ee2d86df9f88236f569bbcd5d7e Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 29 Apr 2026 22:16:13 +0100 Subject: [PATCH] Added picture option (whilst recording as well) --- dashcam.html | 299 ++++++++++++++++++++++++++++----------------------- 1 file changed, 167 insertions(+), 132 deletions(-) diff --git a/dashcam.html b/dashcam.html index 4d8f2ab..bf6d0b1 100644 --- a/dashcam.html +++ b/dashcam.html @@ -14,35 +14,19 @@ #rec-dot { width: 7px; height: 7px; border-radius: 50%; background: #fff; animation: blink 1s infinite; } @keyframes blink { 0%,100%{opacity:1} 50%{opacity:0.2} } -/* SMALL speedo — top right */ -#speed-display { - position: absolute; top: 12px; right: 12px; - text-align: center; background: rgba(0,0,0,0.45); - border-radius: 12px; padding: 6px 14px; - pointer-events: all; cursor: pointer; - transition: opacity 0.2s; -} +#speed-display { position: absolute; top: 12px; right: 12px; text-align: center; background: rgba(0,0,0,0.45); border-radius: 12px; padding: 6px 14px; pointer-events: all; cursor: pointer; } #speed-display:hover { background: rgba(255,255,255,0.12); } #speed-val { font-size: 44px; font-weight: 700; color: #fff; line-height: 1; } #speed-unit { font-size: 10px; color: #aaa; letter-spacing: 1.5px; margin-top: 1px; } -/* BIG speedo overlay */ -#speedo-overlay { - position: absolute; inset: 0; - background: rgba(0,0,0,0.92); - display: none; flex-direction: column; - align-items: center; justify-content: center; - pointer-events: all; cursor: pointer; - z-index: 10; -} +/* flash effect on snapshot */ +#flash { position: absolute; inset: 0; background: #fff; opacity: 0; pointer-events: none; z-index: 30; transition: opacity 0.05s; } +#flash.on { opacity: 0.85; } + +/* big speedo */ +#speedo-overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.92); display: none; flex-direction: column; align-items: center; justify-content: center; pointer-events: all; cursor: pointer; z-index: 10; } #speedo-overlay.visible { display: flex; } -#speedo-close { - position: absolute; top: 12px; right: 14px; - color: rgba(255,255,255,0.5); font-size: 22px; - cursor: pointer; font-family: var(--font-sans); - pointer-events: all; line-height: 1; - background: none; border: none; -} +#speedo-close { position: absolute; top: 12px; right: 14px; color: rgba(255,255,255,0.5); font-size: 22px; cursor: pointer; font-family: var(--font-sans); pointer-events: all; line-height: 1; background: none; border: none; } #speedo-close:hover { color: #fff; } #big-speed-val { font-size: clamp(80px, 22vw, 160px); font-weight: 700; color: #fff; line-height: 1; text-align: center; } #big-speed-unit { font-size: clamp(14px, 3vw, 22px); color: #aaa; letter-spacing: 3px; margin-top: 8px; text-align: center; } @@ -52,7 +36,8 @@ .bdl { color: #555; font-size: clamp(9px, 1.5vw, 12px); letter-spacing: 0.5px; } .bdv { color: #fff; font-weight: 500; } -#gps-bar { position: absolute; bottom: 48px; left: 0; right: 0; background: rgba(0,0,0,0.5); padding: 5px 12px; display: none; } +/* info bar */ +#gps-bar { position: absolute; bottom: 48px; left: 0; right: 0; background: rgba(0,0,0,0.55); padding: 5px 12px; display: none; } .data-row { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; } .di { font-size: 10px; color: #ccc; display: flex; gap: 4px; align-items: center; } .dl { color: #666; font-size: 9px; letter-spacing: 0.4px; } @@ -64,18 +49,23 @@ #status-bar { position: absolute; bottom: 90px; left: 12px; font-size: 10px; color: rgba(255,255,255,0.4); font-family: var(--font-sans); pointer-events: none; } #placeholder { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #888; gap: 10px; font-family: var(--font-sans); background: #000; } -#btn-bar { position: absolute; bottom: 0; left: 0; right: 0; display: flex; justify-content: center; align-items: center; gap: 10px; padding: 7px 12px; background: rgba(0,0,0,0.5); pointer-events: all; flex-wrap: wrap; z-index: 20; } -.hud-btn { background: rgba(255,255,255,0.1); border: 0.5px solid rgba(255,255,255,0.2); color: #fff; border-radius: 20px; padding: 5px 13px; font-size: 11px; font-family: var(--font-sans); cursor: pointer; letter-spacing: 0.4px; white-space: nowrap; } +/* button bar */ +#btn-bar { position: absolute; bottom: 0; left: 0; right: 0; display: flex; justify-content: center; align-items: center; gap: 8px; padding: 7px 10px; background: rgba(0,0,0,0.5); pointer-events: all; flex-wrap: wrap; z-index: 20; } +.hud-btn { background: rgba(255,255,255,0.1); border: 0.5px solid rgba(255,255,255,0.2); color: #fff; border-radius: 20px; padding: 5px 12px; font-size: 11px; font-family: var(--font-sans); cursor: pointer; letter-spacing: 0.4px; white-space: nowrap; } .hud-btn:hover { background: rgba(255,255,255,0.2); } .hud-btn:disabled { opacity: 0.3; cursor: default; } .hud-btn.primary { background: rgba(29,158,117,0.7); border-color: #1D9E75; } .hud-btn.primary:hover { background: rgba(29,158,117,0.9); } .hud-btn.danger { background: rgba(180,0,0,0.7); border-color: rgba(220,0,0,0.9); } .hud-btn.danger:hover { background: rgba(220,0,0,0.85); } +.hud-btn.active { background: rgba(186,117,23,0.7); border-color: rgba(186,117,23,0.9); } +canvas { display: none; }
+ +
🎥
@@ -95,13 +85,11 @@ 00:00
-
--
MPH
-
--
@@ -137,9 +125,11 @@
- + + + - +
@@ -151,7 +141,7 @@ let useMph = true, maxSpeed = 0; let lastPos = null, totalDist = 0; let mediaRecorder = null, recordedChunks = []; let recInterval = null, recSeconds = 0, isRecording = false; -let speedoOpen = false; +let speedoOpen = false, infoVisible = false; let srtEntries = [], recStartTime = null; let currentGPS = { lat: null, lng: null, alt: null, acc: null, hdg: null, speed: 0 }; @@ -160,14 +150,14 @@ function cvtLabel() { return useMph ? 'mph' : 'kph'; } function haversine(a, b) { const R = 6371000, r = Math.PI / 180; - const dLat = (b.lat - a.lat) * r, dLng = (b.lng - a.lng) * r; + const dLat = (b.lat-a.lat)*r, dLng = (b.lng-a.lng)*r; const x = Math.sin(dLat/2)**2 + Math.cos(a.lat*r)*Math.cos(b.lat*r)*Math.sin(dLng/2)**2; return R * 2 * Math.atan2(Math.sqrt(x), Math.sqrt(1-x)); } function bearingLabel(deg) { if (deg === null || isNaN(deg)) return '--'; - return ['N','NE','E','SE','S','SW','W','NW'][Math.round(deg/45)%8] + ' ' + Math.round(deg) + '\xb0'; + return ['N','NE','E','SE','S','SW','W','NW'][Math.round(deg/45)%8]+' '+Math.round(deg)+'\xb0'; } function pad(v) { return String(Math.floor(v)).padStart(2,'0'); } @@ -176,13 +166,67 @@ function pad3(v) { return String(Math.floor(v)).padStart(3,'0'); } function updateClock() { const n = new Date(); document.getElementById('clock').textContent = pad(n.getHours())+':'+pad(n.getMinutes())+':'+pad(n.getSeconds()); - const days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']; - const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; + const days=['Sun','Mon','Tue','Wed','Thu','Fri','Sat']; + const months=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; document.getElementById('dateline').textContent = days[n.getDay()]+' '+pad(n.getDate())+' '+months[n.getMonth()]+' '+n.getFullYear(); } function setStatus(msg) { document.getElementById('status-bar').textContent = msg; } +function toggleInfo() { + infoVisible = !infoVisible; + const bar = document.getElementById('gps-bar'); + const btn = document.getElementById('info-btn'); + bar.style.display = infoVisible ? 'block' : 'none'; + btn.classList.toggle('active', infoVisible); + btn.textContent = infoVisible ? 'INFO ✓' : 'INFO'; +} + +function takeSnapshot() { + if (!stream) return; + const video = document.getElementById('video'); + const canvas = document.getElementById('snap-canvas'); + canvas.width = video.videoWidth || 1280; + canvas.height = video.videoHeight || 720; + const ctx = canvas.getContext('2d'); + ctx.drawImage(video, 0, 0, canvas.width, canvas.height); + + // stamp telemetry onto image + const n = new Date(); + const dateStr = n.getFullYear()+'-'+pad(n.getMonth()+1)+'-'+pad(n.getDate())+' '+pad(n.getHours())+':'+pad(n.getMinutes())+':'+pad(n.getSeconds()); + const spd = currentGPS.speed !== null ? Math.round(cvt(currentGPS.speed))+' '+cvtLabel().toUpperCase() : '--'; + const lat = currentGPS.lat !== null ? currentGPS.lat.toFixed(6) : '--'; + const lng = currentGPS.lng !== null ? currentGPS.lng.toFixed(6) : '--'; + const alt = currentGPS.alt !== null ? Math.round(currentGPS.alt)+'m' : '--'; + const lines = [dateStr, spd+' '+bearingLabel(currentGPS.hdg), 'LAT '+lat+' LNG '+lng, 'ALT '+alt]; + + ctx.font = 'bold 20px monospace'; + ctx.textBaseline = 'bottom'; + const lineH = 26, pad8 = 10; + const boxH = lines.length * lineH + pad8 * 2; + ctx.fillStyle = 'rgba(0,0,0,0.55)'; + ctx.fillRect(0, canvas.height - boxH, canvas.width, boxH); + ctx.fillStyle = '#ffffff'; + lines.forEach((l, i) => { + ctx.fillText(l, pad8, canvas.height - boxH + pad8 + (i+1)*lineH); + }); + + // flash + const flash = document.getElementById('flash'); + flash.classList.add('on'); + setTimeout(() => flash.classList.remove('on'), 120); + + // download + canvas.toBlob(blob => { + const fname = 'dashcam_snap_'+n.getFullYear()+pad(n.getMonth()+1)+pad(n.getDate())+'_'+pad(n.getHours())+pad(n.getMinutes())+pad(n.getSeconds())+'.jpg'; + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; a.download = fname; a.click(); + setTimeout(() => URL.revokeObjectURL(url), 5000); + setStatus('Snapshot saved'); + }, 'image/jpeg', 0.92); +} + function openSpeedo() { if (isRecording) return; speedoOpen = true; @@ -209,8 +253,7 @@ function syncBigSpeedo() { } function toSRTTime(ms) { - const h = Math.floor(ms/3600000), m = Math.floor((ms%3600000)/60000); - const s = Math.floor((ms%60000)/1000), mil = ms%1000; + const h=Math.floor(ms/3600000), m=Math.floor((ms%3600000)/60000), s=Math.floor((ms%60000)/1000), mil=ms%1000; return pad(h)+':'+pad(m)+':'+pad(s)+','+pad3(mil); } @@ -221,36 +264,35 @@ function captureSRTFrame() { const timeStr = pad(now.getHours())+':'+pad(now.getMinutes())+':'+pad(now.getSeconds()); const spd = currentGPS.speed !== null ? Math.round(cvt(currentGPS.speed)) : '--'; srtEntries.push({ - startMs: elapsed, endMs: elapsed + 1000, + startMs: elapsed, endMs: elapsed+1000, lines: [ - dateStr + ' ' + timeStr, - 'Speed: ' + spd + ' ' + cvtLabel() + ' Hdg: ' + bearingLabel(currentGPS.hdg), - 'Lat: ' + (currentGPS.lat !== null ? currentGPS.lat.toFixed(6) : '--') + ' Lng: ' + (currentGPS.lng !== null ? currentGPS.lng.toFixed(6) : '--'), - 'Alt: ' + (currentGPS.alt !== null ? Math.round(currentGPS.alt)+'m' : '--') + ' Acc: ' + (currentGPS.acc !== null ? Math.round(currentGPS.acc)+'m' : '--') + dateStr+' '+timeStr, + 'Speed: '+spd+' '+cvtLabel()+' Hdg: '+bearingLabel(currentGPS.hdg), + 'Lat: '+(currentGPS.lat!==null?currentGPS.lat.toFixed(6):'--')+' Lng: '+(currentGPS.lng!==null?currentGPS.lng.toFixed(6):'--'), + 'Alt: '+(currentGPS.alt!==null?Math.round(currentGPS.alt)+'m':'--')+' Acc: '+(currentGPS.acc!==null?Math.round(currentGPS.acc)+'m':'--') ] }); } function buildSRT() { - return srtEntries.map((e,i) => (i+1)+'\n'+toSRTTime(e.startMs)+' --> '+toSRTTime(e.endMs)+'\n'+e.lines.join('\n')).join('\n\n')+'\n'; + return srtEntries.map((e,i)=>(i+1)+'\n'+toSRTTime(e.startMs)+' --> '+toSRTTime(e.endMs)+'\n'+e.lines.join('\n')).join('\n\n')+'\n'; } function getFilename() { - const n = new Date(); + const n=new Date(); return 'dashcam_'+n.getFullYear()+pad(n.getMonth()+1)+pad(n.getDate())+'_'+pad(n.getHours())+pad(n.getMinutes())+pad(n.getSeconds()); } function getBestMimeType() { - const types = ['video/webm;codecs=vp9','video/webm;codecs=vp8','video/webm','video/mp4']; - for (const t of types) { if (MediaRecorder.isTypeSupported(t)) return t; } + const types=['video/webm;codecs=vp9','video/webm;codecs=vp8','video/webm','video/mp4']; + for(const t of types){if(MediaRecorder.isTypeSupported(t))return t;} return ''; } function dl(filename, blob) { - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; a.download = filename; a.click(); - setTimeout(() => URL.revokeObjectURL(url), 5000); + const url=URL.createObjectURL(blob), a=document.createElement('a'); + a.href=url; a.download=filename; a.click(); + setTimeout(()=>URL.revokeObjectURL(url),5000); } function toggleRecord() { @@ -258,38 +300,35 @@ function toggleRecord() { const btn = document.getElementById('rec-btn'); const ind = document.getElementById('rec-indicator'); if (!isRecording) { - recordedChunks = []; srtEntries = []; recStartTime = Date.now(); - const mime = getBestMimeType(); - try { mediaRecorder = new MediaRecorder(stream, mime ? { mimeType: mime } : {}); } - catch(e) { mediaRecorder = new MediaRecorder(stream); } - mediaRecorder.ondataavailable = e => { if (e.data && e.data.size > 0) recordedChunks.push(e.data); }; - mediaRecorder.onstop = () => { - const name = getFilename(); - const mime2 = getBestMimeType(); - const ext = mime2.includes('mp4') ? 'mp4' : 'webm'; - dl(name+'.'+ext, new Blob(recordedChunks, { type: mime2 })); - setTimeout(() => dl(name+'.srt', new Blob([buildSRT()], { type: 'text/plain' })), 800); - recordedChunks = []; + recordedChunks=[]; srtEntries=[]; recStartTime=Date.now(); + const mime=getBestMimeType(); + try { mediaRecorder=new MediaRecorder(stream,mime?{mimeType:mime}:{}); } + catch(e) { mediaRecorder=new MediaRecorder(stream); } + mediaRecorder.ondataavailable=e=>{if(e.data&&e.data.size>0)recordedChunks.push(e.data);}; + mediaRecorder.onstop=()=>{ + const name=getFilename(), mime2=getBestMimeType(), ext=mime2.includes('mp4')?'mp4':'webm'; + dl(name+'.'+ext, new Blob(recordedChunks,{type:mime2})); + setTimeout(()=>dl(name+'.srt',new Blob([buildSRT()],{type:'text/plain'})),800); + recordedChunks=[]; setStatus('Saved video + SRT'); }; mediaRecorder.start(1000); - isRecording = true; recSeconds = 0; - ind.style.display = 'flex'; - btn.innerHTML = '▮▮ SAVE'; + isRecording=true; recSeconds=0; + ind.style.display='flex'; + btn.innerHTML='▮▮ SAVE'; btn.classList.add('danger'); captureSRTFrame(); - recInterval = setInterval(() => { + recInterval=setInterval(()=>{ recSeconds++; - document.getElementById('rec-timer').textContent = pad(Math.floor(recSeconds/60))+':'+pad(recSeconds%60); + document.getElementById('rec-timer').textContent=pad(Math.floor(recSeconds/60))+':'+pad(recSeconds%60); captureSRTFrame(); - }, 1000); + },1000); setStatus('Recording...'); } else { - mediaRecorder.stop(); - isRecording = false; + mediaRecorder.stop(); isRecording=false; clearInterval(recInterval); - ind.style.display = 'none'; - btn.innerHTML = '● RECORD'; + ind.style.display='none'; + btn.innerHTML='● REC'; btn.classList.remove('danger'); setStatus('Saving...'); } @@ -297,85 +336,81 @@ function toggleRecord() { function startGPS() { if (!navigator.geolocation) { setStatus('GPS unavailable'); return; } - document.getElementById('gps-bar').style.display = 'block'; - watchId = navigator.geolocation.watchPosition(pos => { - const c = pos.coords; - currentGPS = { lat: c.latitude, lng: c.longitude, alt: c.altitude, acc: c.accuracy, hdg: c.heading, speed: c.speed !== null ? c.speed : 0 }; - const conv = cvt(currentGPS.speed); - if (conv > maxSpeed) maxSpeed = conv; - const cur = { lat: c.latitude, lng: c.longitude }; - if (lastPos) { const d = haversine(lastPos, cur); if (d < 200) totalDist += d; } - lastPos = cur; - document.getElementById('speed-val').textContent = Math.round(conv); - document.getElementById('lat-val').textContent = c.latitude.toFixed(5); - document.getElementById('lng-val').textContent = c.longitude.toFixed(5); - document.getElementById('alt-val').textContent = c.altitude !== null ? Math.round(c.altitude)+'m' : '--'; - document.getElementById('acc-val').textContent = Math.round(c.accuracy)+'m'; - document.getElementById('hdg-val').textContent = bearingLabel(c.heading); - document.getElementById('dist-val').textContent = (totalDist/1000).toFixed(2)+'km'; - document.getElementById('gps-dot').className = 'active'; + watchId=navigator.geolocation.watchPosition(pos=>{ + const c=pos.coords; + currentGPS={lat:c.latitude,lng:c.longitude,alt:c.altitude,acc:c.accuracy,hdg:c.heading,speed:c.speed!==null?c.speed:0}; + const conv=cvt(currentGPS.speed); + if(conv>maxSpeed) maxSpeed=conv; + const cur={lat:c.latitude,lng:c.longitude}; + if(lastPos){const d=haversine(lastPos,cur);if(d<200)totalDist+=d;} + lastPos=cur; + document.getElementById('speed-val').textContent=Math.round(conv); + document.getElementById('lat-val').textContent=c.latitude.toFixed(5); + document.getElementById('lng-val').textContent=c.longitude.toFixed(5); + document.getElementById('alt-val').textContent=c.altitude!==null?Math.round(c.altitude)+'m':'--'; + document.getElementById('acc-val').textContent=Math.round(c.accuracy)+'m'; + document.getElementById('hdg-val').textContent=bearingLabel(c.heading); + document.getElementById('dist-val').textContent=(totalDist/1000).toFixed(2)+'km'; + document.getElementById('gps-dot').className='active'; syncBigSpeedo(); setStatus(''); - }, err => { - setStatus('GPS: ' + err.message); - document.getElementById('gps-dot').className = ''; - }, { enableHighAccuracy: true, maximumAge: 1000, timeout: 15000 }); + }, err=>{ + setStatus('GPS: '+err.message); + document.getElementById('gps-dot').className=''; + },{enableHighAccuracy:true,maximumAge:1000,timeout:15000}); } async function startDashcam() { - const btn = document.getElementById('start-btn'); - btn.disabled = true; btn.textContent = '...'; - if (isRecording) toggleRecord(); + const btn=document.getElementById('start-btn'); + btn.disabled=true; btn.textContent='...'; + if(isRecording) toggleRecord(); closeSpeedo(); try { - if (stream) stream.getTracks().forEach(t => t.stop()); - stream = await navigator.mediaDevices.getUserMedia({ - video: { facingMode, width: { ideal: 1920 }, height: { ideal: 1080 } }, audio: false - }); - document.getElementById('video').srcObject = stream; - document.getElementById('placeholder').style.display = 'none'; - document.getElementById('top-left').style.display = 'flex'; - document.getElementById('flip-btn').disabled = false; - document.getElementById('rec-btn').disabled = false; - btn.textContent = 'RESTART'; btn.disabled = false; - if (watchId !== null) { navigator.geolocation.clearWatch(watchId); watchId = null; } - maxSpeed = 0; totalDist = 0; lastPos = null; - currentGPS = { lat: null, lng: null, alt: null, acc: null, hdg: null, speed: 0 }; + if(stream) stream.getTracks().forEach(t=>t.stop()); + stream=await navigator.mediaDevices.getUserMedia({video:{facingMode,width:{ideal:1920},height:{ideal:1080}},audio:false}); + document.getElementById('video').srcObject=stream; + document.getElementById('placeholder').style.display='none'; + document.getElementById('top-left').style.display='flex'; + document.getElementById('flip-btn').disabled=false; + document.getElementById('rec-btn').disabled=false; + document.getElementById('snap-btn').disabled=false; + document.getElementById('info-btn').disabled=false; + btn.textContent='RESTART'; btn.disabled=false; + if(watchId!==null){navigator.geolocation.clearWatch(watchId);watchId=null;} + maxSpeed=0; totalDist=0; lastPos=null; + currentGPS={lat:null,lng:null,alt:null,acc:null,hdg:null,speed:0}; startGPS(); - if (clockInterval) clearInterval(clockInterval); - clockInterval = setInterval(updateClock, 1000); + if(clockInterval) clearInterval(clockInterval); + clockInterval=setInterval(updateClock,1000); updateClock(); setStatus('Acquiring GPS...'); } catch(e) { - setStatus('Camera: ' + e.message); - btn.textContent = 'START'; btn.disabled = false; + setStatus('Camera: '+e.message); + btn.textContent='START'; btn.disabled=false; } } -function switchCamera() { facingMode = facingMode === 'environment' ? 'user' : 'environment'; startDashcam(); } +function switchCamera() { facingMode=facingMode==='environment'?'user':'environment'; startDashcam(); } function toggleUnit() { - useMph = !useMph; - maxSpeed = useMph ? maxSpeed / 3.6 * 2.23694 : maxSpeed / 2.23694 * 3.6; - const unit = useMph ? 'MPH' : 'KPH'; - document.getElementById('speed-unit').textContent = unit; - document.getElementById('big-speed-unit').textContent = unit; + useMph=!useMph; + maxSpeed=useMph?maxSpeed/3.6*2.23694:maxSpeed/2.23694*3.6; + const unit=useMph?'MPH':'KPH'; + document.getElementById('speed-unit').textContent=unit; + document.getElementById('big-speed-unit').textContent=unit; } function toggleFullscreen() { - const app = document.getElementById('app'); - const btn = document.getElementById('fs-btn'); - if (!app.classList.contains('fullscreen')) { - if (app.requestFullscreen) app.requestFullscreen().catch(()=>{}); - app.classList.add('fullscreen'); - btn.textContent = '\u2715 EXIT'; + const app=document.getElementById('app'), btn=document.getElementById('fs-btn'); + if(!app.classList.contains('fullscreen')){ + if(app.requestFullscreen) app.requestFullscreen().catch(()=>{}); + app.classList.add('fullscreen'); btn.textContent='\u2715'; } else { - if (document.exitFullscreen && document.fullscreenElement) document.exitFullscreen().catch(()=>{}); - app.classList.remove('fullscreen'); - btn.textContent = '\u26f6 FULL'; + if(document.exitFullscreen&&document.fullscreenElement) document.exitFullscreen().catch(()=>{}); + app.classList.remove('fullscreen'); btn.textContent='\u26f6'; } - document.addEventListener('fullscreenchange', () => { - if (!document.fullscreenElement) { app.classList.remove('fullscreen'); btn.textContent = '\u26f6 FULL'; } - }, { once: true }); + document.addEventListener('fullscreenchange',()=>{ + if(!document.fullscreenElement){app.classList.remove('fullscreen');btn.textContent='\u26f6';} + },{once:true}); }