thermograph/static/day.html
Emi Griffith f34ab7ef96 Add PWA + Web Push delivery for weather alerts (#95)
Make the app installable and deliver existing alert notifications as OS
push, alongside the in-app bell.

Backend:
- PushSubscription model (per-device endpoint + keys, owned by a user) and
  register/unregister/test endpoints under /api/v2/push, cookie-auth scoped
  to the user like the subscription routes.
- push.py: VAPID key management (env -> data/vapid.json -> generated) and a
  pywebpush send helper that reports gone endpoints for pruning. No DB coupling.
- notify.py: after creating an in-app Notification, dispatch Web Push to the
  user's devices (guarded — a push failure never affects the in-app write;
  endpoints reported gone are pruned).
- Serve the .webmanifest with the correct media type.

Frontend:
- manifest.webmanifest + 192/maskable icons; <link rel="manifest"> on all pages.
- sw.js: push + notificationclick handlers (push-only; no fetch caching, so it
  doesn't fight the existing IndexedDB cache). Registered globally in nav.js in
  secure contexts.
- push-client.js + a "Notifications on this device" toggle and test-send on the
  /alerts page, subscribing through the existing cookie-aware apiFetch.

Push and service workers require a secure context, so this is active over HTTPS
(or http://localhost) and cleanly no-ops on a plain-HTTP LAN origin.
2026-07-15 23:21:06 +00:00

71 lines
3.2 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Thermograph — a single day</title>
<meta name="description" content="Pick any point on Earth and see how its recent weather stacks up against ~45 years of local climate history — graded days, calendars, forecasts, and side-by-side comparisons." />
<meta name="theme-color" content="#f0803c" />
<!-- Link previews (Discord, Slack, iMessage…). The server fills the og:url /
og:image origins in from this request's scheme://host + base path —
crawlers need absolute URLs and don't run JS. -->
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Thermograph" />
<meta property="og:title" content="Thermograph — a single day" />
<meta property="og:description" content="Pick any point on Earth and see how its recent weather stacks up against ~45 years of local climate history — graded days, calendars, forecasts, and side-by-side comparisons." />
<meta property="og:url" content="__ORIGIN__/day" />
<meta property="og:image" content="__ORIGIN__/logo.png" />
<meta property="og:image:width" content="512" />
<meta property="og:image:height" content="512" />
<meta property="og:image:alt" content="Thermograph logo" />
<meta name="twitter:card" content="summary" />
<link rel="icon" href="logo.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="logo.png" />
<link rel="manifest" href="manifest.webmanifest" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header>
<div class="brand">
<span class="logo"></span>
<div>
<h1>Thermograph · Day</h1>
<p class="tag">Every percentile boundary for one day vs its typical climate</p>
</div>
<nav class="view-nav" aria-label="Views">
<a href="./" data-view="map">Weekly</a>
<a href="calendar" data-view="calendar">Calendar</a>
<a href="day" data-view="day" class="active">Day</a>
<a href="compare" data-view="compare">Compare</a>
</nav>
</div>
</header>
<main>
<section class="controls">
<div class="find-bar">
<button type="button" id="find-btn" class="find-btn"></button>
<span id="loc-label" class="loc-label" hidden></span>
</div>
<div class="day-nav">
<button type="button" id="prev-day" class="day-step" title="Previous day" aria-label="Previous day"></button>
<label class="day-date">Day
<input id="date-input" type="date" />
</label>
<button type="button" id="next-day" class="day-step" title="Next day" aria-label="Next day"></button>
</div>
</section>
<div id="day-head" class="cal-head" hidden></div>
<div class="placeholder" id="day-placeholder">
<p>Pick a place and a day — or open this from a day on the map or calendar — to see
the value at every tier boundary for that day's typical climate.</p>
</div>
<div id="day-body"></div>
</main>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script type="module" src="day.js"></script>
</body>
</html>