From 82520174173c12a96b7173d9a2779724165df75c Mon Sep 17 00:00:00 2001 From: Nate Emmert Date: Thu, 15 Oct 2026 13:48:13 -0400 Subject: [PATCH] Map tab: full-bleed map with floating glass controls (Apple Weather style) - Map fills everything between the top bar and bottom nav; no page scroll on this tab - Floating round controls (layers, my location, full screen), readout chips top-left, layers sheet beside the controls, weather summary + radar timeline floating at the bottom - Safe-area aware in full-screen mode; zoom buttons tucked away on touch devices Co-Authored-By: Claude Sonnet 5.5 --- app/public/app-ui.css | 69 ++++++++++++++++++++++++++------- app/public/app.js | 4 +- app/public/index.html | 85 ++++++++++++++++++++--------------------- app/public/pwa-safe.css | 4 +- 4 files changed, 102 insertions(+), 60 deletions(-) diff --git a/app/public/app-ui.css b/app/public/app-ui.css index 1cdcf70..18910e4 100644 --- a/app/public/app-ui.css +++ b/app/public/app-ui.css @@ -242,16 +242,59 @@ body:not([data-tab="conditions"]) #loading { display: none; } .topbar { margin-left: calc(-1 * (var(--gutter) + env(safe-area-inset-left, 0px))); margin-right: calc(-1 * (var(--gutter) + env(safe-area-inset-right, 0px))); } .install-dialog { margin-top: max(1rem, env(safe-area-inset-top, 0px)); } -/* ── Map tab: the map is the page (not a 56vh strip); timeline and details sit below it ── */ -body:not(.map-fs) #tab-unified-map .unified-map-wrap { height: auto; min-height: 0; padding: 0.7rem; gap: 0.6rem; border-radius: 16px; } -body:not(.map-fs) #tab-unified-map .unified-map-container { - height: max(540px, calc(100dvh - var(--topbar-h) - 250px)); border-radius: 12px; -} -@media (max-width: 640px) { - body:not(.map-fs) #tab-unified-map .unified-map-container { - height: max(460px, calc(100dvh - var(--topbar-h) - 58px - max(6px, env(safe-area-inset-bottom, 0px)) - 190px)); - } -} -.map-controls-header { padding-bottom: 0.5rem; gap: 0.5rem; } -.map-recenter { font: inherit; font-size: 0.78rem; font-weight: 700; min-height: 40px; padding: 0 0.9rem; border-radius: 999px; border: 1px solid var(--border); background: var(--paper-dark); color: var(--ink); cursor: pointer; margin-left: auto; white-space: nowrap; } -.map-recenter:hover { border-color: var(--accent); color: var(--accent); } +/* ── Map tab: full-bleed map with floating glass controls (Apple-Weather style) ── */ +:root { --nav-h: 0px; --hud-top: 12px; --map-safe-b: 0px; --hud-side: 12px; } +@media (max-width: 640px) { :root { --nav-h: calc(58px + max(6px, env(safe-area-inset-bottom, 0px))); } } +body.map-fs { --hud-top: calc(12px + env(safe-area-inset-top, 0px)); --map-safe-b: env(safe-area-inset-bottom, 0px); } + +.glass { background: color-mix(in srgb, var(--paper-dark) 74%, transparent); backdrop-filter: blur(18px) saturate(1.5); -webkit-backdrop-filter: blur(18px) saturate(1.5); + border: 1px solid color-mix(in srgb, var(--border) 65%, transparent); box-shadow: 0 8px 28px rgba(0, 0, 0, 0.3); } + +/* The tab fills everything between the top bar and the bottom nav; no page scrolling here */ +body:not(.map-fs) #tab-unified-map:not(.hidden) { position: fixed; top: var(--topbar-h); left: 0; right: 0; bottom: var(--nav-h); z-index: 60; animation: none; } +body[data-tab="unified-map"] { overflow: hidden; } +html:has(body[data-tab="unified-map"]) { overflow: hidden; } +#tab-unified-map .unified-map-wrap { position: absolute; inset: 0; padding: 0; border: 0; border-radius: 0; background: transparent; display: block; height: auto !important; min-height: 0; } +#tab-unified-map .unified-map-shell { position: absolute; inset: 0; } +#tab-unified-map .unified-map-container { position: absolute; inset: 0; height: 100% !important; min-height: 0; border: 0; border-radius: 0; } +body.map-fs #tab-unified-map { padding: 0; } + +/* Floating controls, top right */ +.map-hud { position: absolute; top: var(--hud-top); right: max(var(--hud-side), env(safe-area-inset-right, 0px)); display: flex; flex-direction: column; gap: 10px; z-index: 700; } +.hud-btn { width: 46px; height: 46px; padding: 0; display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; cursor: pointer; color: var(--ink); font: inherit; font-size: 1.15rem; + background: color-mix(in srgb, var(--paper-dark) 78%, transparent); backdrop-filter: blur(18px) saturate(1.5); -webkit-backdrop-filter: blur(18px) saturate(1.5); + border: 1px solid color-mix(in srgb, var(--border) 65%, transparent); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); transition: transform .12s, background .15s, color .15s; } +.hud-btn:active { transform: scale(0.94); } +.hud-btn:hover { color: var(--accent); } +.hud-btn.on { background: var(--accent); color: #fff; border-color: var(--accent); } + +/* Readout chips, top left */ +#tab-unified-map .map-overlay-readouts { position: absolute; top: var(--hud-top); left: max(var(--hud-side), env(safe-area-inset-left, 0px)); right: auto; bottom: auto; flex-direction: column; align-items: flex-start; gap: 8px; z-index: 700; } +#tab-unified-map .map-readout-chip { background: color-mix(in srgb, var(--paper-dark) 78%, transparent); backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px); border: 1px solid color-mix(in srgb, var(--border) 65%, transparent); color: var(--ink); font-weight: 700; padding: 0.4rem 0.8rem; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25); } + +/* Layers sheet: floats beside the controls */ +#tab-unified-map .map-layer-menu-panel { position: absolute; top: var(--hud-top); right: calc(max(var(--hud-side), env(safe-area-inset-right, 0px)) + 58px); z-index: 710; width: min(330px, calc(100% - 96px)); max-height: calc(100% - var(--hud-top) - 185px); + overflow: auto; margin: 0; padding: 0.8rem 0.9rem 0.9rem; border-radius: 20px; } +.lm-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.6rem; font-size: 1rem; } +.lm-close { width: 34px; height: 34px; border: none; border-radius: 50%; background: var(--paper-darker); color: var(--ink-light); font: inherit; cursor: pointer; } +.lm-style { display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; font-size: 0.85rem; font-weight: 600; margin-bottom: 0.8rem; } +.lm-style select { flex: 1; max-width: 190px; min-height: 40px; font-size: 16px; } +#tab-unified-map .map-layer-menu-title { font-size: 0.66rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-light); margin-bottom: 0.4rem; } +#tab-unified-map .map-controls-toggle-grid { display: grid; grid-template-columns: 1fr; gap: 2px; } +#tab-unified-map .toggle-control { padding: 0.45rem 0.2rem; min-height: 44px; } + +/* Bottom: weather summary + radar timeline float over the map */ +.map-bottom { position: absolute; left: max(var(--hud-side), env(safe-area-inset-left, 0px)); right: max(var(--hud-side), env(safe-area-inset-right, 0px)); bottom: calc(12px + var(--map-safe-b)); z-index: 650; display: flex; flex-direction: column; gap: 10px; pointer-events: none; } +.map-bottom > * { pointer-events: auto; } +#tab-unified-map .map-timeline-wrap { padding: 0.55rem 0.75rem 0.6rem; border-radius: 18px; border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); } +#tab-unified-map .map-timeline-wrap.hidden, #tab-unified-map .map-data-panel.hidden { display: none; } +#tab-unified-map .map-data-panel { align-self: flex-start; max-width: 100%; display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem 1.1rem; padding: 0.5rem 0.9rem; border-radius: 16px; } +#tab-unified-map .panel-location { font-weight: 800; font-size: 0.9rem; } +#tab-unified-map .panel-data { display: flex; flex-wrap: wrap; gap: 0.2rem 1rem; } +#tab-unified-map .data-item { display: flex; flex-direction: row; align-items: baseline; gap: 0.35rem; font-size: 0.82rem; } +@media (max-width: 640px) { #tab-unified-map .data-item:nth-child(2), #tab-unified-map .data-item:nth-child(4), #tab-unified-map .data-item:nth-child(5) { display: none; } } + +/* MapLibre zoom buttons: tucked under the control stack on desktop, hidden on touch */ +#unified-map .maplibregl-ctrl-top-right { margin-top: calc(var(--hud-top) + 166px); margin-right: max(var(--hud-side), env(safe-area-inset-right, 0px)); } +#unified-map .maplibregl-ctrl-top-right .maplibregl-ctrl { margin: 0; } +@media (pointer: coarse) { #unified-map .maplibregl-ctrl-top-right { display: none; } } diff --git a/app/public/app.js b/app/public/app.js index 5986ece..3622108 100644 --- a/app/public/app.js +++ b/app/public/app.js @@ -2489,12 +2489,14 @@ if (baseMapSelect) { } const layerMenuToggleBtn = document.getElementById('layer-menu-toggle'); +document.getElementById('layer-menu-close')?.addEventListener('click', () => { if (!document.getElementById('layer-menu-panel').classList.contains('hidden')) document.getElementById('layer-menu-toggle').click(); }); const layerMenuPanel = document.getElementById('layer-menu-panel'); if (layerMenuToggleBtn && layerMenuPanel) { layerMenuToggleBtn.addEventListener('click', () => { const willShow = layerMenuPanel.classList.contains('hidden'); layerMenuPanel.classList.toggle('hidden', !willShow); - layerMenuToggleBtn.textContent = willShow ? 'Hide Layer Menu' : 'Show Layer Menu'; + if (!layerMenuToggleBtn.classList.contains('hud-btn')) layerMenuToggleBtn.textContent = willShow ? 'Hide Layer Menu' : 'Show Layer Menu'; + layerMenuToggleBtn.classList.toggle('on', willShow); layerMenuToggleBtn.setAttribute('aria-expanded', willShow ? 'true' : 'false'); }); } diff --git a/app/public/index.html b/app/public/index.html index 0674d00..3485972 100644 --- a/app/public/index.html +++ b/app/public/index.html @@ -248,26 +248,37 @@
+ diff --git a/app/public/pwa-safe.css b/app/public/pwa-safe.css index 1e000db..eccab12 100644 --- a/app/public/pwa-safe.css +++ b/app/public/pwa-safe.css @@ -22,9 +22,7 @@ /* Bottom bars: clear the home indicator and the rounded bottom corners */ .bottom-nav { padding-left: max(8px, env(safe-area-inset-left, 0px)); padding-right: max(8px, env(safe-area-inset-right, 0px)); padding-bottom: max(6px, env(safe-area-inset-bottom, 0px)); } -/* Full-screen map: keep controls off the notch, corners and home indicator */ -body.map-fs #tab-unified-map { padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px) env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px); } -body.map-fs .unified-map-wrap { height: calc(100dvh - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px)); } +/* (Full-screen map: the map runs edge to edge; its floating controls add the insets themselves) */ /* Modal dialogs: stay inside the safe area on short (landscape) screens */ dialog { max-height: calc(100dvh - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px) - 1.5rem); overflow: auto; }