From b9e6cad1907602218a04554e16530a53b6ce0e95 Mon Sep 17 00:00:00 2001 From: Nate Emmert Date: Mon, 17 Aug 2026 09:44:33 -0400 Subject: [PATCH] Add CSS-based fullscreen for Map tab; fix state desync bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add fullscreen toggle button (⛶/✕) to Map tab controls row - CSS class `map-fs` hides header/nav and expands map to fill screen - Drop unreliable requestFullscreen() API (blocked on iOS/PWA) in favor of pure CSS approach - Fix button state desync: syncBtn() runs on init so stale ✕ state can't persist across reloads - Shrink embedded radar height on mobile (220px) and tune dvh calculations for safe-area insets Co-Authored-By: Claude Sonnet 4.6 --- app/public/app.js | 31 ++++++++++++ app/public/index.html | 3 +- app/public/style.css | 108 ++++++++++++++++++++++++++++-------------- 3 files changed, 106 insertions(+), 36 deletions(-) diff --git a/app/public/app.js b/app/public/app.js index 7e9951e..16ed945 100644 --- a/app/public/app.js +++ b/app/public/app.js @@ -1089,6 +1089,37 @@ const radarFull = radarFullEl ) : { map: null, panTo: () => {}, startPlay: () => {}, stopPlay: () => {}, refresh: () => {} }; +(function initMapFullscreen() { + const fsBtn = document.getElementById('map-fullscreen-btn'); + if (!fsBtn) return; + + function isFs() { return document.body.classList.contains('map-fs'); } + + function syncBtn() { + if (isFs()) { fsBtn.textContent = '✕'; fsBtn.title = 'Exit fullscreen'; } + else { fsBtn.textContent = '⛶'; fsBtn.title = 'Fullscreen'; } + } + + function enterFs() { + document.body.classList.add('map-fs'); + syncBtn(); + setTimeout(() => { unifiedMap && unifiedMap.refresh && unifiedMap.refresh(); }, 120); + } + + function exitFs() { + document.body.classList.remove('map-fs'); + syncBtn(); + setTimeout(() => { unifiedMap && unifiedMap.refresh && unifiedMap.refresh(); }, 120); + } + + // Sync on load in case of stale state + syncBtn(); + + fsBtn.addEventListener('click', () => { + if (isFs()) exitFs(); else enterFs(); + }); +})(); + function createOverviewMap(mapEl) { const map = new maplibregl.Map({ container: mapEl, diff --git a/app/public/index.html b/app/public/index.html index 3f5e3c6..5b4ab45 100644 --- a/app/public/index.html +++ b/app/public/index.html @@ -191,7 +191,7 @@
- +
@@ -257,6 +257,7 @@ +
diff --git a/app/public/style.css b/app/public/style.css index 389ed9d..6647159 100644 --- a/app/public/style.css +++ b/app/public/style.css @@ -1642,18 +1642,85 @@ details[open] .drawer-chevron { transform: rotate(90deg); } } .radar-map-full { - height: calc(100vh - 280px); - min-height: 420px; + height: calc(100dvh - 290px); + min-height: 320px; } .radar-full-wrap { border: var(--surface-border); border-radius: var(--surface-radius-md); background: #1a1f2e; - margin-bottom: 0.5rem; + margin-bottom: 0; overflow: hidden; } +/* Fullscreen toggle button */ +.radar-fs-btn { + background: var(--paper-dark); + border: 1px solid var(--border); + color: var(--ink); + font-size: 1.1rem; + margin-left: auto; + flex-shrink: 0; +} + +/* ── Map fullscreen mode (CSS-based, works on iOS Safari) ── */ +body.map-fs .masthead, +body.map-fs .bottom-nav, +body.map-fs .offline-banner, +body.map-fs .ptr-indicator { + display: none !important; +} + +body.map-fs .app { + padding: 0 !important; +} + +body.map-fs #tab-unified-map { + position: fixed; + inset: 0; + z-index: 9999; + overflow: hidden; +} + +body.map-fs .unified-map-wrap { + height: 100dvh; + padding: 0; + border: none; + border-radius: 0; + background: #1a1f2e; +} + +body.map-fs .map-controls-header { + padding: 0.3rem 0.65rem; + gap: 0; + border-bottom: none; +} + +body.map-fs .map-controls-row label, +body.map-fs .map-controls-row select, +body.map-fs .map-layer-menu { + display: none; +} + +body.map-fs .map-controls-row { + justify-content: flex-end; +} + +body.map-fs #data-panel { + display: none !important; +} + +body.map-fs .unified-map-shell { + flex: 1; + min-height: 0; +} + +body.map-fs #unified-map { + height: 100% !important; + min-height: 0 !important; +} + /* ── Radar controls ── */ .radar-controls { display: flex; @@ -1840,7 +1907,7 @@ details[open] .drawer-chevron { transform: rotate(90deg); } padding-bottom: max(4rem, calc(2rem + env(safe-area-inset-bottom))); } .radar-map-full { - height: calc(100vh - 280px - env(safe-area-inset-top) - env(safe-area-inset-bottom)); + height: calc(100dvh - 290px - env(safe-area-inset-top) - env(safe-area-inset-bottom)); } .map-overview { height: calc(100vh - 470px - env(safe-area-inset-top) - env(safe-area-inset-bottom)); @@ -1942,36 +2009,6 @@ details[open] .drawer-chevron { transform: rotate(90deg); } .tabs { display: none; } .bottom-nav { display: flex; } - #tab-radar-full { - padding-bottom: max(6.4rem, calc(5.7rem + env(safe-area-inset-bottom, 0px))); - } - - .radar-controls-full { - display: grid; - grid-template-columns: auto auto minmax(0, 1fr); - grid-template-areas: - 'play layer ts' - 'timeline timeline timeline'; - gap: 0.45rem 0.5rem; - padding: 0.55rem 0.65rem; - align-items: center; - } - - .radar-controls-full .play-btn { grid-area: play; } - .radar-controls-full .layer-btn { grid-area: layer; } - .radar-controls-full .radar-ts { - grid-area: ts; - min-width: 0; - text-align: right; - font-size: 0.66rem; - } - - .radar-controls-full .radar-timeline { - grid-area: timeline; - width: 100%; - height: 24px; - } - .radar-controls-full .radar-legend { display: none; } .radar-attribution { overflow-wrap: anywhere; @@ -2061,7 +2098,8 @@ details[open] .drawer-chevron { transform: rotate(90deg); } .recs-grid { grid-template-columns: repeat(3, 1fr); } .day-row { grid-template-columns: 80px 36px 1fr 70px; } .day-meta { display: none; } - .radar-map { height: 240px; } + .radar-embed-wrap .radar-map { height: 220px; } + .radar-map-full { height: calc(100dvh - 330px); min-height: 250px; } .legend-bar { width: 50px; } .settings-row { flex-direction: column; align-items: flex-start; gap: 0.5rem; } .settings-value { text-align: left; }