screentinker/Examples/PIP-Weather-Radar/radar-overlay.html
ScreenTinker b8c127f766 Examples/weather-radar: keep the map centred and bounded
The auto-framing fitted the view to whatever warning polygons were active, so a
storm a few counties away pulled the frame out to cover it and the configured
area shrank to an unreadable corner of a half-state view. On signage that is
read at a glance, a map that wanders is worse than one that shows less.

Framing is now centred and bounded:

- The map never pans. The centre stays on the configured point and only the
  zoom responds, because the box handed to fitBounds is symmetric about home.
- Zoom-out is capped at `max_counties` (default 2) county-widths in every
  direction, with longitude scaled by cos(lat) so the budget is the same
  distance on the ground north and south.
- Warnings entirely outside that box are not chased at all; the configured view
  is held. Warnings clearing returns to it rather than staying parked on the
  last storm.
- A floor on the frame keeps one small cell overhead from zooming to street
  level, and fit padding drops to 24px, which on a PiP-sized overlay was
  discarding a third of the width per side.

The overlay assets are served max-age=14400, so a player that had already
loaded them kept the old copy for four hours and silently ignored a redeploy.
The page URL and its script tag now carry a version, documented to be bumped
together.

Tests cover the invariants against the shipped frameFor source rather than a
copy of it: centred after reframing, capped at the county budget, small cells
floored, distant storms not chased.
2026-07-27 12:26:08 -05:00

60 lines
3.2 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Live Weather Radar</title>
<!-- Vendored Leaflet, served same-origin so the server CSP (script-src 'self') allows it. -->
<link rel="stylesheet" href="/leaflet.css">
<style>
html, body { margin: 0; height: 100%; background: #0b0d10; overflow: hidden; }
#map { position: absolute; inset: 0; background: #0b0d10; }
/* darker, calmer Leaflet attribution to match the TV look */
.leaflet-control-attribution { background: rgba(10,12,16,.6) !important; color: #9aa3ad !important; font-size: 10px; }
.leaflet-control-attribution a { color: #c9d2db !important; }
.hud { position: absolute; left: 0; right: 0; top: 0; z-index: 1000; pointer-events: none;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; color: #fff; }
.bar { display: flex; align-items: center; gap: 14px; padding: 12px 18px;
background: linear-gradient(180deg, rgba(8,10,13,.92), rgba(8,10,13,0)); }
.live { display: flex; align-items: center; gap: 9px; font-weight: 800; letter-spacing: .06em;
text-transform: uppercase; font-size: clamp(15px, 2.4vw, 24px); }
.dot { width: 13px; height: 13px; border-radius: 50%; background: #FF2D2D;
box-shadow: 0 0 0 0 rgba(255,45,45,.7); animation: pulse 1.2s ease-out infinite; }
@keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(255,45,45,.7) } 70% { box-shadow: 0 0 0 12px rgba(255,45,45,0) } 100% { box-shadow: 0 0 0 0 rgba(255,45,45,0) } }
.area { font-weight: 600; font-size: clamp(13px, 2vw, 20px); opacity: .92; }
.spacer { flex: 1; }
.clock { font-variant-numeric: tabular-nums; font-weight: 600; font-size: clamp(12px, 1.8vw, 18px);
color: #cfe8ff; opacity: .9; }
.chips { display: flex; flex-wrap: wrap; gap: 8px; padding: 0 18px 10px; }
.chip { pointer-events: none; font-size: clamp(11px, 1.6vw, 15px); font-weight: 700; color: #0b0d10;
padding: 4px 10px; border-radius: 999px; box-shadow: 0 2px 8px rgba(0,0,0,.4); }
.chip.none { background: #2c3340; color: #aeb6c0; font-weight: 600; }
.legend { position: absolute; right: 12px; bottom: 26px; z-index: 1000; pointer-events: none;
background: rgba(10,12,16,.72); border-radius: 10px; padding: 8px 10px; font-family: system-ui, sans-serif;
color: #dfe6ee; font-size: 11px; line-height: 1.5; }
.legend .row { display: flex; align-items: center; gap: 7px; }
.legend .sw { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }
</style>
</head>
<body>
<div id="map"></div>
<div class="hud">
<div class="bar">
<span class="live"><span class="dot"></span>Live Radar</span>
<span class="area" id="area"></span>
<span class="spacer"></span>
<span class="clock" id="clock"></span>
</div>
<div class="chips" id="chips"></div>
</div>
<div class="legend" id="legend"></div>
<script src="/leaflet.js"></script>
<!-- ?v= is a cache-buster, NOT decoration. These assets are served max-age=14400, so a
player that loaded the old file keeps it for four hours and silently ignores a
redeploy. Bump this AND `ASSET_V` in radar.js together whenever the JS changes. -->
<script src="/radar-overlay.js?v=2"></script>
</body>
</html>