Map tab: full-bleed map with floating glass controls (Apple Weather style)
Publish Container / publish (push) Successful in 2m58s

- 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 <noreply@anthropic.com>
This commit is contained in:
Nate EmmertandClaude Sonnet 5.5 committed 2026-10-15 13:48:13 -04:00
1 parent 4f47d67a7e
commit 8252017417
4 files changed
+101 -59

No files matched your search

+56 -13
View File
@@ -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; } }
+3 -1
View File
@@ -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');
});
}
+41 -42
View File
@@ -248,25 +248,36 @@
<div id="station-live" class="sl"></div>
</div>
<!-- ── TAB: MAP (full-bleed map, floating glass controls) ── -->
<div id="tab-unified-map" class="tab-panel hidden">
<div class="unified-map-wrap">
<!-- Map Controls -->
<div class="map-controls-header">
<div class="map-controls-row">
<label for="base-map-select" class="control-label">Base Map:</label>
<select id="base-map-select" class="map-select">
<div class="unified-map-shell">
<div id="unified-map" class="unified-map-container"></div>
<div class="map-overlay-readouts" id="map-overlay-readouts">
<span class="map-readout-chip" id="overlay-temp">Temp --</span>
<span class="map-readout-chip" id="overlay-aqi">AQI --</span>
<span class="map-readout-chip" id="overlay-sunmoon">Sun -- • Moon --</span>
</div>
<div class="map-hud" role="toolbar" aria-label="Map controls">
<button id="layer-menu-toggle" class="hud-btn" type="button" aria-expanded="false" aria-label="Map layers" title="Layers">
<svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true"><path d="M12 3 3 8l9 5 9-5-9-5Z" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linejoin="round"/><path d="m3 12 9 5 9-5M3 16l9 5 9-5" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linejoin="round" stroke-linecap="round"/></svg>
</button>
<button id="map-recenter-btn" class="hud-btn" type="button" aria-label="My location" title="My location">
<svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true"><path d="M21 3 3 10.5l7 2.5 2.5 7L21 3Z" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linejoin="round"/></svg>
</button>
<button id="map-fullscreen-btn" class="hud-btn radar-fs-btn" type="button" aria-label="Full screen" title="Full screen">⛶</button>
</div>
<div id="layer-menu-panel" class="map-layer-menu-panel glass hidden" role="dialog" aria-label="Map layers">
<div class="lm-head"><b>Map</b><button type="button" id="layer-menu-close" class="lm-close" aria-label="Close layers">✕</button></div>
<label class="lm-style" for="base-map-select"><span>Style</span> <select id="base-map-select" class="map-select">
<option value="dark">Dark</option>
<option value="light">Light</option>
<option value="satellite">Satellite</option>
</select>
<button id="map-recenter-btn" class="map-recenter" type="button" title="Back to my location">📍 My location</button>
<button id="map-fullscreen-btn" class="radar-btn radar-fs-btn" title="Fullscreen">⛶</button>
</div>
<div class="map-layer-menu">
<button id="layer-menu-toggle" class="map-layer-menu-toggle" type="button" aria-expanded="false">Show Layer Menu</button>
<div id="layer-menu-panel" class="map-layer-menu-panel hidden">
<div class="map-layer-menu-title">Map Layers</div>
</select></label>
<div class="map-layer-menu-title">Layers</div>
<div class="map-controls-toggle-grid">
<label class="toggle-control">
<span class="toggle-label">📡 Radar</span>
@@ -347,35 +358,9 @@
</label>
</div>
</div>
</div>
</div>
<!-- Main Map -->
<div class="unified-map-shell">
<div id="unified-map" class="unified-map-container"></div>
<div class="map-overlay-readouts" id="map-overlay-readouts">
<span class="map-readout-chip" id="overlay-temp">Temp --</span>
<span class="map-readout-chip" id="overlay-aqi">AQI --</span>
<span class="map-readout-chip" id="overlay-sunmoon">Sun -- • Moon --</span>
</div>
</div>
<!-- Radar/Satellite Timeline Controls -->
<div class="map-timeline-wrap hidden" id="timeline-controls">
<div class="radar-controls">
<button id="timeline-play" class="radar-btn play-btn" title="Play/Pause">▶</button>
<div class="radar-timeline" id="timeline-slider"></div>
<span id="timeline-timestamp" class="radar-ts">--</span>
</div>
<div class="radar-legend">
<span class="legend-label">Light</span>
<div class="legend-bar"></div>
<span class="legend-label">Heavy</span>
</div>
</div>
<!-- Data Panel -->
<div class="map-data-panel hidden" id="data-panel">
<div class="map-bottom">
<div class="map-data-panel glass hidden" id="data-panel">
<div class="panel-location">
<span id="panel-location-name">--</span>
</div>
@@ -401,6 +386,20 @@
<span id="panel-cloud">--</span>
</div>
</div>
</div>
<div class="map-timeline-wrap glass hidden" id="timeline-controls">
<div class="radar-controls">
<button id="timeline-play" class="radar-btn play-btn" title="Play/Pause">▶</button>
<div class="radar-timeline" id="timeline-slider"></div>
<span id="timeline-timestamp" class="radar-ts">--</span>
</div>
<div class="radar-legend">
<span class="legend-label">Light</span>
<div class="legend-bar"></div>
<span class="legend-label">Heavy</span>
</div>
</div>
</div>
</div>
</div>
</div>
+1 -3
View File
@@ -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; }