Add maps fullscreen and enforce single-line search controls
This commit is contained in:
1 parent
5003d3ec09
commit
359e76bccb
3 files changed
+124
-30
No files matched your search
+75
-3
@@ -122,6 +122,7 @@ const contrastToggle = document.getElementById('contrast-toggle');
|
|||||||
const contrastStatus = document.getElementById('contrast-status');
|
const contrastStatus = document.getElementById('contrast-status');
|
||||||
const radarOpacityEl = document.getElementById('radar-opacity');
|
const radarOpacityEl = document.getElementById('radar-opacity');
|
||||||
const lightningAgeEl = document.getElementById('lightning-age');
|
const lightningAgeEl = document.getElementById('lightning-age');
|
||||||
|
const mapsFullscreenBtn = document.getElementById('maps-fullscreen-btn');
|
||||||
|
|
||||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
const show = el => el.classList.remove('hidden');
|
const show = el => el.classList.remove('hidden');
|
||||||
@@ -466,6 +467,8 @@ document.querySelectorAll('.tab').forEach(btn => {
|
|||||||
}
|
}
|
||||||
if (btn.dataset.tab === 'maps') {
|
if (btn.dataset.tab === 'maps') {
|
||||||
setTimeout(refreshMapsOverview, 80);
|
setTimeout(refreshMapsOverview, 80);
|
||||||
|
} else {
|
||||||
|
setMapsFullscreen(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -731,9 +734,33 @@ function createOverviewMap(mapEl) {
|
|||||||
|
|
||||||
const radarLayer = L.tileLayer('https://tilecache.rainviewer.com/v2/radar/nowcast_0/256/{z}/{x}/{y}/2/1_1.png', {
|
const radarLayer = L.tileLayer('https://tilecache.rainviewer.com/v2/radar/nowcast_0/256/{z}/{x}/{y}/2/1_1.png', {
|
||||||
maxZoom: 15,
|
maxZoom: 15,
|
||||||
opacity: Math.max(0.15, Math.min(0.9, state.radarOpacity / 100)),
|
opacity: Math.max(0.25, Math.min(0.9, state.radarOpacity / 100)),
|
||||||
}).addTo(map);
|
}).addTo(map);
|
||||||
|
|
||||||
|
let lastRadarFramePath = null;
|
||||||
|
|
||||||
|
async function refreshRadarOverlay() {
|
||||||
|
try {
|
||||||
|
const r = await fetch('/api/radar/times');
|
||||||
|
if (!r.ok) return;
|
||||||
|
const data = await r.json();
|
||||||
|
const frames = [
|
||||||
|
...(data?.radar?.past || []),
|
||||||
|
...(data?.radar?.nowcast || []),
|
||||||
|
];
|
||||||
|
const latest = frames[frames.length - 1];
|
||||||
|
const path = latest?.path;
|
||||||
|
if (!path || path === lastRadarFramePath) return;
|
||||||
|
lastRadarFramePath = path;
|
||||||
|
radarLayer.setUrl(`https://tilecache.rainviewer.com${path}/256/{z}/{x}/{y}/2/1_1.png`);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Maps radar overlay refresh failed', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshRadarOverlay();
|
||||||
|
setInterval(refreshRadarOverlay, 5 * 60 * 1000);
|
||||||
|
|
||||||
let marker = null;
|
let marker = null;
|
||||||
|
|
||||||
function panTo(lat, lon, zoom) {
|
function panTo(lat, lon, zoom) {
|
||||||
@@ -749,11 +776,11 @@ function createOverviewMap(mapEl) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setRadarOpacity(opacityPct) {
|
function setRadarOpacity(opacityPct) {
|
||||||
const normalized = Math.max(0.15, Math.min(0.9, Number(opacityPct) / 100));
|
const normalized = Math.max(0.25, Math.min(0.9, Number(opacityPct) / 100));
|
||||||
radarLayer.setOpacity(normalized);
|
radarLayer.setOpacity(normalized);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { map, panTo, setRadarOpacity };
|
return { map, panTo, setRadarOpacity, refreshRadarOverlay };
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearLightningLayer() {
|
function clearLightningLayer() {
|
||||||
@@ -774,6 +801,7 @@ function refreshMapsOverview() {
|
|||||||
if (!mapsOverview.map) return;
|
if (!mapsOverview.map) return;
|
||||||
|
|
||||||
mapsOverview.map.invalidateSize();
|
mapsOverview.map.invalidateSize();
|
||||||
|
mapsOverview.refreshRadarOverlay();
|
||||||
|
|
||||||
if (!state.currentCity) return;
|
if (!state.currentCity) return;
|
||||||
|
|
||||||
@@ -906,6 +934,50 @@ function renderWindArrows(city) {
|
|||||||
|
|
||||||
const mapsOverview = createOverviewMap(document.getElementById('map-overview'));
|
const mapsOverview = createOverviewMap(document.getElementById('map-overview'));
|
||||||
|
|
||||||
|
function setMapsFullscreen(active) {
|
||||||
|
const wrap = document.querySelector('#tab-maps .maps-wrap');
|
||||||
|
if (!wrap) return;
|
||||||
|
wrap.classList.toggle('is-fullscreen', active);
|
||||||
|
document.body.classList.toggle('map-fullscreen-lock', active);
|
||||||
|
if (mapsFullscreenBtn) {
|
||||||
|
mapsFullscreenBtn.textContent = active ? 'Exit fullscreen' : 'Fullscreen map';
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
if (mapsOverview?.map) mapsOverview.map.invalidateSize();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapsFullscreenBtn) {
|
||||||
|
mapsFullscreenBtn.addEventListener('click', async () => {
|
||||||
|
const wrap = document.querySelector('#tab-maps .maps-wrap');
|
||||||
|
if (!wrap) return;
|
||||||
|
|
||||||
|
if (document.fullscreenElement === wrap) {
|
||||||
|
await document.exitFullscreen().catch(() => {});
|
||||||
|
setMapsFullscreen(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wrap.requestFullscreen) {
|
||||||
|
try {
|
||||||
|
await wrap.requestFullscreen();
|
||||||
|
setMapsFullscreen(true);
|
||||||
|
return;
|
||||||
|
} catch {
|
||||||
|
// Some mobile browsers reject Fullscreen API; fallback below.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setMapsFullscreen(!wrap.classList.contains('is-fullscreen'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('fullscreenchange', () => {
|
||||||
|
const wrap = document.querySelector('#tab-maps .maps-wrap');
|
||||||
|
if (!wrap) return;
|
||||||
|
setMapsFullscreen(document.fullscreenElement === wrap);
|
||||||
|
});
|
||||||
|
|
||||||
// ── Rendering ────────────────────────────────────────────────────────────────
|
// ── Rendering ────────────────────────────────────────────────────────────────
|
||||||
function renderDailyForecast(daily) {
|
function renderDailyForecast(daily) {
|
||||||
const list = document.getElementById('daily-list');
|
const list = document.getElementById('daily-list');
|
||||||
|
|||||||
@@ -158,6 +158,12 @@
|
|||||||
|
|
||||||
<!-- ── TAB: MAPS ── -->
|
<!-- ── TAB: MAPS ── -->
|
||||||
<div id="tab-maps" class="tab-panel hidden">
|
<div id="tab-maps" class="tab-panel hidden">
|
||||||
|
<div class="section-head">
|
||||||
|
<span class="section-label">Maps</span>
|
||||||
|
<span class="section-actions">
|
||||||
|
<button id="maps-fullscreen-btn" class="link-btn" type="button">Fullscreen map</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div class="maps-wrap">
|
<div class="maps-wrap">
|
||||||
<div id="map-overview" class="radar-map map-overview"></div>
|
<div id="map-overview" class="radar-map map-overview"></div>
|
||||||
<div class="maps-controls">
|
<div class="maps-controls">
|
||||||
|
|||||||
+43
-27
@@ -167,6 +167,7 @@ a:hover { text-decoration: underline; }
|
|||||||
.search-row {
|
.search-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex-wrap: nowrap;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
@@ -195,6 +196,7 @@ button, input, select { font-family: var(--ui); }
|
|||||||
}
|
}
|
||||||
|
|
||||||
.search-row button {
|
.search-row button {
|
||||||
|
flex: 0 0 auto;
|
||||||
min-height: 46px;
|
min-height: 46px;
|
||||||
border: none;
|
border: none;
|
||||||
border-left: 1px solid var(--line);
|
border-left: 1px solid var(--line);
|
||||||
@@ -205,18 +207,25 @@ button, input, select { font-family: var(--ui); }
|
|||||||
#search-btn {
|
#search-btn {
|
||||||
background: linear-gradient(180deg, #15445f 0%, #11344c 100%);
|
background: linear-gradient(180deg, #15445f 0%, #11344c 100%);
|
||||||
color: #f2f8fc;
|
color: #f2f8fc;
|
||||||
text-transform: uppercase;
|
min-width: 52px;
|
||||||
letter-spacing: 0.03em;
|
width: 52px;
|
||||||
font-weight: 700;
|
font-size: 0;
|
||||||
min-width: 96px;
|
letter-spacing: 0;
|
||||||
font-size: 0.78rem;
|
text-transform: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-btn::before {
|
||||||
|
content: '🔎';
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#locate-btn,
|
#locate-btn,
|
||||||
#units-btn {
|
#units-btn {
|
||||||
background: var(--surface) !important;
|
background: var(--surface) !important;
|
||||||
color: var(--ink) !important;
|
color: var(--ink) !important;
|
||||||
min-width: 50px;
|
min-width: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#locate-btn {
|
#locate-btn {
|
||||||
@@ -224,34 +233,15 @@ button, input, select { font-family: var(--ui); }
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 390px) {
|
@media (max-width: 390px) {
|
||||||
#search-btn {
|
#search-btn,
|
||||||
min-width: 84px;
|
|
||||||
font-size: 0.72rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
#locate-btn,
|
#locate-btn,
|
||||||
#units-btn {
|
#units-btn {
|
||||||
min-width: 46px;
|
min-width: 46px;
|
||||||
|
width: 46px;
|
||||||
padding: 0 0.5rem;
|
padding: 0 0.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 460px) {
|
|
||||||
#search-btn {
|
|
||||||
min-width: 48px;
|
|
||||||
font-size: 0;
|
|
||||||
letter-spacing: 0;
|
|
||||||
text-transform: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#search-btn::before {
|
|
||||||
content: '🔎';
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.suggestions {
|
.suggestions {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin-top: 0.3rem;
|
margin-top: 0.3rem;
|
||||||
@@ -668,6 +658,32 @@ button, input, select { font-family: var(--ui); }
|
|||||||
padding: 0.35rem 0.55rem;
|
padding: 0.35rem 0.55rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#maps-fullscreen-btn {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.map-fullscreen-lock {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maps-wrap.is-fullscreen {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 520;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
border: none;
|
||||||
|
background: var(--paper);
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: calc(0.65rem + env(safe-area-inset-top, 0px)) 0.65rem calc(0.65rem + env(safe-area-inset-bottom, 0px));
|
||||||
|
}
|
||||||
|
|
||||||
|
.maps-wrap.is-fullscreen .map-overview {
|
||||||
|
height: calc(100dvh - 210px);
|
||||||
|
min-height: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
.wind-legend {
|
.wind-legend {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
Reference in new issue
Block a user