/* ──────────────────────────────────────────────────────────────────────────
   Kathy's Weather Almanac — web edition.
   Ports the Flutter app's Almanac design system (see
   WeatherApp/lib/src/ui/theme/almanac_theme.dart): ink on paper, editorial
   serifs, hairline rules. Deliberately not glassmorphism.
   ────────────────────────────────────────────────────────────────────────── */

:root {
  --paper: #F6F1E7;
  --paper-shade: #EDE6D8;
  --ink: #1C1A16;
  --ink-faded: #6B655A;
  --rule: #9A9284;
  --accent: #9E3B2F;
  --gold: #B68235; /* emblem gold (the almanac emblem's sunburst) — the glyphs' sun disc */
  --cloud: #C8C3B7; /* warm light grey — the glyphs' cloud fill, always ink-outlined */

  --serif: "Playfair Display", Georgia, serif;
  --sans: "IBM Plex Sans", "Helvetica Neue", Arial, sans-serif;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

/* Author display rules (e.g. .centered-view's flex) must never override the
   hidden attribute — view switching relies on it. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.5;
  min-height: 100vh;
}

/* About page: the almanac emblem as a fixed watermark behind the text.
   The body's paper background paints on the canvas, so z-index -1 still
   sits above it while staying behind all content. */
.about-page::before {
  content: "";
  position: fixed;
  inset: 0;
  background: url("../assets/kathys-weather-ios-appstore-1024.png") center / min(92vmin, 720px) no-repeat;
  opacity: 0.08;
  pointer-events: none;
  z-index: -1;
}

main, .centered-view {
  /* Wide enough that the hero datum rows (conditions with every optional
     datum showing, icons included; sunrise → moonset → moon) each hold one
     line on a full monitor; narrower viewports shrink and wrap as before. */
  max-width: 980px;
  margin: 0 auto;
  padding: 28px 24px 40px;
}

/* ── Type system ── */

.masthead {
  font-family: var(--serif);
  font-weight: 800;
  font-size: clamp(26px, 5.5vw, 34px);
  letter-spacing: 1.5px;
  text-align: center;
  color: var(--ink);
}

/* Kathy's portrait beside the masthead, cropped round like a columnist
   headshot, sized relative to the masthead type so it scales with it. */
.masthead-icon {
  height: 1.15em;
  width: 1.15em;
  border-radius: 50%;
  border: 1px solid var(--rule);
  object-fit: cover;
  vertical-align: -0.22em;
  margin-right: 12px;
}

/* The icon links home; keep the anchor layout-neutral. */
.masthead-icon-link { text-decoration: none; }
.masthead-icon-link:hover .masthead-icon { border-color: var(--accent); }

.overline {
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 2.2px;
  text-transform: uppercase;
  color: var(--ink-faded);
}

.overline.centered, .centered { text-align: center; }

/* Section titles that keep their written case (e.g. "Kathy's forecast for today:"). */
.overline.mixed-case { text-transform: none; }
.overline.bold { font-weight: 700; }

/* The forecast intro line reads larger, darker and italic against the
   standard overline. The italic is synthesised — the font link carries no
   ital axis for IBM Plex Sans, so the browser shears the upright rather
   than swapping in cursive letterforms. It holds at 15px with the letter
   spacing; adding `1,600` to that link would buy a true italic if the
   slant ever looks mechanical. */
#section-detail .section-title { font-size: 15px; letter-spacing: 1.6px; color: var(--ink); font-style: italic; }

/* Temp and conditions share the line, each centered in its own half. The
   two children take equal widths rather than sizing to their content, so
   the centers stay put as the temperature goes from 5° to 105°. */
.hero-row {
  display: flex;
  align-items: center;
  gap: 16px;
}

.hero-row > * { flex: 1 1 0; min-width: 0; }

.hero-temp {
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(80px, 22vw, 124px);
  line-height: 0.95;
  text-align: center;
  color: var(--ink);
}

.hero-temp .degree { font-size: 0.49em; vertical-align: 0.55em; }

.hero-condition {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 600;
  font-size: 24px;
  color: var(--ink);
}

/* The conditions value is upright bold (the base class stays italic for
   the error view's "CORRECTION" message). */
.hero-condition-value { font-style: normal; font-weight: 700; }

/* Conditions sit right of the temp, centered in that half. */
.hero-row .hero-condition { text-align: center; }

/* Condition glyph riding the conditions line. It stands taller than the
   type, so it hangs by its own centre rather than sitting on the
   baseline — the offset is half the glyph less half the cap height. */
.hero-condition svg { vertical-align: -0.42em; margin-left: 4px; }

/* The storm icon is 48 against the other glyphs' 40, so it needs its own
   offset — measured, not scaled: -0.6em is where its centre lands on the
   type's. */
.hero-condition .storm-svg { vertical-align: -0.6em; }

/* Hairline under the temp/conditions row. */
.hero-rule { margin: 14px auto 10px; }

/* The editorial column sits inside the hero now, between the temperature
   and the datums, with no rule of its own — so its title needs the air
   that rule would have carried, clear of a 124px numeral's descenders. */
#section-detail > .section-title { margin-top: 16px; }

/* Thunder reads as an aside to the forecast prose above it, so it takes
   the faded weight of a datum rather than the body's ink. */
.thunder-note { font-size: 13px; color: var(--ink-faded); margin-top: 5px; }

.body-text { font-size: 14px; line-height: 1.5; color: var(--ink); }

/* The current-conditions paragraph is the one the reader came for, so it
   carries more weight than the prose elsewhere. 600 *is* bold here: the
   font link loads IBM Plex Sans at 400/500/600 only, and 700 measures
   identical to 600 because there is no heavier face to reach for. Asking
   for 700 would change nothing without adding a weight to that link. */
#detail-text { font-weight: 600; }
.body-text a { color: var(--accent); text-decoration-color: var(--rule); }

.day-name {
  font-family: var(--serif);
  font-weight: 700;
  font-size: 18px;
  color: var(--ink);
}

.data-label, .datum-label {
  font-size: 12px;
  font-weight: 500;
  color: var(--ink-faded);
}

.data-value {
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
}

/* ── Rules (newspaper section dividers) ── */

.rule { height: 1px; background: var(--rule); }
.rule.doubled {
  height: auto;
  background: none;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  padding-top: 2px;
}

/* ── Page head ── */

/* The page head (masthead → menu, plus the bulletin) stays pinned while the
   page scrolls. Opaque paper so content passing beneath doesn't show through. */
.page-head {
  position: sticky;
  top: 0;
  z-index: 500;
  background: var(--paper);
  padding: 4px 0 6px;
}

/* Keep in-page anchors (e.g. FIVE-DAY FORECAST) clear of the pinned head. */
html { scroll-padding-top: 170px; }

/* Menu bar under the double rule: left-aligned almanac nav. */
.menu-bar {
  display: flex;
  justify-content: flex-start;
  gap: 20px;
  padding: 7px 0;
  border-bottom: 1px solid var(--rule);
}

.menu-link {
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
  color: var(--ink);
  text-decoration: none;
}

.menu-link:hover { color: var(--accent); text-decoration: underline; }

/* Pushes a menu item (ABOUT) to the right edge of the bar. */
.menu-right { margin-left: auto; }

.dateline {
  display: block;
  width: 100%;
  margin: 8px 0 10px;
  background: none;
  border: none;
  cursor: pointer;
  text-align: center;
}
.dateline:hover { color: var(--accent); }

.bulletin {
  display: block;
  margin-top: 10px;
  padding: 8px 12px;
  background: var(--accent);
  color: var(--paper);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  text-align: center;
  text-decoration: none; /* the banner links to the alerts page */
}

/* ── The Weather Alerts page ── */

.alert-headline {
  font-family: var(--serif);
  font-weight: 700;
  font-size: clamp(20px, 4.5vw, 26px);
  color: var(--accent);
  margin: 6px 0 10px;
}

.alert-description {
  white-space: pre-line; /* keep the NWS paragraph breaks */
  margin-top: 12px;
}

/* All-clear state under the shared page header. */
.clear-section { padding: 36px 0 12px; }
.clear-section .hero-condition { margin: 8px 0; }
.centered-text { text-align: center; }

/* ── Hero ── */

.hero { padding: 12px 0 18px; }

.datum-row {
  display: flex;
  flex-wrap: wrap; /* five datums (with AIR + POLLEN) wrap on narrow phones */
  justify-content: center;
  align-items: center;
  row-gap: 10px;
  margin-top: 14px;
}

.datum {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}

.datum-label { font-size: 9px; font-weight: 600; letter-spacing: 2.2px; }

.datum-divider {
  width: 1px;
  height: 26px;
  background: var(--rule);
  margin: 0 18px;
}

/* ── Sections ── */

section > .section-title { margin: 10px 0 6px; }

.ink-chart { display: block; width: 100%; height: 150px; }

/* Condition glyphs above the hourly bars — one equal-width cell per bar,
   so the flex math mirrors drawInkChart's slot centers. */
.hour-glyph-strip { display: flex; margin-bottom: 2px; }
.hour-glyph-cell { flex: 1; display: flex; justify-content: center; align-items: flex-end; }
/* Narrow screens: show every other glyph but keep all cells for alignment. */
@media (max-width: 520px) {
  .hour-glyph-cell:nth-child(even) svg { visibility: hidden; }
}

/* ── The moon panel ──
   The one photograph among the data: a real lunar disc, cropped round and
   hairlined like the masthead portrait so it reads as a plate set into the
   page rather than a picture pasted on it. The PNGs are transparent outside
   the disc, so the ink backing is what a new moon shows. */
/* Sun and moon sit side by side under one rule, each taking half the
   column, and the moon drops to its own line when both no longer fit.
   The break is content-driven rather than a chosen breakpoint: the basis
   is the widest a panel gets before its text has to wrap — 88px emblem +
   24px gap + 258px for the moon's longest line — so flex-wrap decides,
   and the panels can grow their text without the rule going stale. */
.sky-columns { display: flex; flex-wrap: wrap; gap: 8px 40px; }
.sky-column { flex: 1 1 380px; min-width: 0; }

/* The titles are no longer direct children of a section, so they need
   their own copy of the section-title spacing. */
.sky-column > .section-title { margin: 10px 0 6px; }

.moon-panel, .sun-panel { display: flex; align-items: center; gap: 24px; padding: 4px 0 6px; }

/* The sun panel is the moon's twin: same footprint, same three lines, so
   the two read as a pair. Its emblem is drawn rather than photographed —
   the rayed gold sun of the condition glyph, at plate size. */
.sun-emblem { flex: none; width: 88px; height: 88px; line-height: 0; display: block; }
.sun-emblem-svg { display: block; }

.moon-photo {
  flex: none;
  width: 88px;
  height: 88px;
  border-radius: 50%;
  border: 1px solid var(--rule);
  background: var(--ink);
  display: block;
}

.moon-phase-name, .sun-daylength {
  font-family: var(--serif);
  font-size: 25px;
  line-height: 1.15;
  color: var(--ink);
}

.moon-detail, .sun-detail { font-size: 13px; color: var(--ink-faded); margin-top: 4px; }

/* Dates, clock times and percentages inside a detail line or the thunder
   note (set by setFigures). The weight alone barely reads at 13px against
   the faded ink these lines take, so the figure steps up to full ink as
   well — the pair is what makes it land as a scanning target. And it is
   only ever a figure, never the words around it: three or four bold words
   to a line would read as a second voice rather than an emphasis.
   600 is as heavy as this gets: the font link carries IBM Plex Sans at
   400/500/600, and 700 would measure identical to 600 with no face to
   reach for. */
.figure { font-weight: 600; color: var(--ink); }

/* A link inside a detail line reads as a footnote would: the same faded
   ink as the copy around it, marked only by a hairline underline, and
   taking the accent when a reader reaches for it. */
.moon-detail a, .sun-detail a {
  color: inherit;
  text-decoration-color: var(--rule);
  text-underline-offset: 2px;
}
.moon-detail a:hover, .sun-detail a:hover { color: var(--accent); }

/* The golden/blue line's explainer. setFigures rewrites the line's
   innerHTML on every render, so the popup can't be a child element — it
   rides the paragraph as a pseudo-element reading data-tip, and anchors
   to the panel (the nearest positioned ancestor) rather than the line so
   it can span the column without spilling off a narrow viewport. Dotted
   underline is the affordance; :focus keeps it reachable by keyboard and
   by tap where there's no hover. */
.sun-panel { position: relative; }
.info-tip {
  cursor: help;
  text-decoration: underline dotted;
  text-decoration-color: var(--rule);
  text-underline-offset: 2px;
}
.info-tip:focus { outline: none; }
.info-tip::after {
  content: attr(data-tip);
  position: absolute;
  left: 0;
  top: calc(100% + 8px);
  width: min(340px, 100%);
  padding: 10px 12px;
  background: var(--paper);
  border: 1px solid var(--rule);
  box-shadow: 0 2px 10px rgba(28, 26, 22, 0.18);
  color: var(--ink);
  font-size: 12px;
  line-height: 1.55;
  z-index: 10;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.15s ease;
  pointer-events: none;
}
.info-tip:hover::after, .info-tip:focus::after {
  opacity: 1;
  visibility: visible;
}

@media (max-width: 400px) {
  .moon-panel, .sun-panel { gap: 18px; }
  .moon-photo, .sun-emblem { width: 72px; height: 72px; }
  .moon-phase-name, .sun-daylength { font-size: 21px; }
}

/* Hairline between the conditions datums and the sun-and-moon line. */
.datum-rule { margin-top: 14px; }

/* Second datum line: sunrise → day arc → sunset, then the moon. */
.sun-moon-row { margin-top: 10px; }

/* This row's spacing lives in the row's own column-gap rather than in the
   dividers' margins (the pattern the conditions row uses), so hiding a
   divider leaves the datums either side of it still held apart. Column
   gaps don't apply at a line end, so nothing pads a wrapped line either. */
.sun-moon-row { column-gap: 18px; }
.sun-moon-row .datum-divider { margin: 0; }

/* The divider only exists where the two trios share a line; once the row
   wraps, it would dangle at a line end, so it is hidden below the width
   where the moon trio drops below the sun trio.

   That width is the point where the divider *and* the moon trio still fit
   on the line: a divider is 19px of row, enough to push its own neighbour
   onto the next line, so choosing the threshold without counting it would
   make the divider cause the very wrap that should hide it. That leaves a
   narrow band under the width where the join is a plain gap rather than a
   rule, which is the join the sun and moon already fall back to once
   wrapped. Re-measure if the arc's box or the datum type sizes change —
   this moved from 600px when the MOON datum left the row: the two trios
   plus the divider now need a 587px viewport, and 568px without it. */
@media (max-width: 586px) {
  #datum-moonrise-divider { display: none; }
}
.sun-arc { margin: 0 14px; line-height: 0; }

/* A rise time, its arc, and the matching set time read as one figure, so
   they travel together: the group is the wrapping unit, not the datums
   inside it. When the row runs out of room it breaks between the sun and
   the moon, never mid-arc. An empty group — no sun or no moon data —
   collapses to nothing and leaves the survivor centered. */
.datum-arc-group {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
}

/* A group whose datums are all hidden (no coordinates, or polar day/night)
   drops out entirely — otherwise it would sit there at zero width and still
   collect a column gap, nudging the surviving half off centre. */
.datum-arc-group:not(:has(> :not([hidden]))) { display: none; }

/* On phones the two labels plus the 96px arc only just clear the column,
   so the arc's shoulders tuck in rather than force a break. */
@media (max-width: 400px) {
  .sun-arc { margin: 0 8px; }
}

/* Datum readings with a glyph or gauge ride it beside the text. */
#datum-wind, #datum-precip, #datum-feels, #datum-humidity, #datum-uv, #datum-air {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

/* ── The weather map ── */

/* Isolate each map's stacking context so Leaflet panes/controls (z-index up
   to ~800 internally) can never ride above the sticky page head. */
.leaflet-container { z-index: 0; }

.weather-map {
  height: 320px;
  border: 1px solid var(--rule);
  background: var(--paper-shade);
}

/* Pull the base tiles toward the paper palette. Radar tiles are in their own
   pane (see app.js) so precipitation keeps its color. */
.weather-map .leaflet-tile-pane {
  filter: grayscale(1) sepia(0.18) contrast(0.92) brightness(1.05);
}

.weather-map .leaflet-control-attribution {
  background: rgba(246, 241, 231, 0.85);
  color: var(--ink-faded);
  font-family: var(--sans);
  font-size: 9px;
}
.weather-map .leaflet-control-attribution a { color: var(--ink-faded); }

.weather-map .leaflet-bar { border: 1px solid var(--rule); box-shadow: none; border-radius: 0; }
.weather-map .leaflet-bar a {
  background: var(--paper);
  color: var(--ink);
  border-bottom-color: var(--rule);
  border-radius: 0;
}
.weather-map .leaflet-bar a:hover { background: var(--paper-shade); }

.map-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 10px;
  flex-wrap: wrap;
}

/* The full map: the section lifts off the page and fills the viewport.
   Above the sticky page head (z-index 500). */
#section-map.map-full {
  position: fixed;
  inset: 0;
  z-index: 600;
  margin: 0;
  padding: 10px clamp(16px, 4vw, 48px) 14px;
  background: var(--paper);
  display: flex;
  flex-direction: column;
}
#section-map.map-full .weather-map { flex: 1; height: auto; min-height: 0; }
/* The page beneath must not scroll while the full map is up. */
body.map-full-open { overflow: hidden; }

/* ── Five-day outlook ── */

.day-row {
  border-bottom: 0.6px solid var(--rule);
  padding: 10px 0;
  cursor: pointer;
}

.day-row-head {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  background: none;
  border: none;
  width: 100%;
  padding: 0;
  cursor: pointer;
  text-align: left;
  font-family: var(--sans);
}

.day-row-main { flex: 1; min-width: 0; }

.day-title { display: flex; align-items: center; gap: 8px; }
.day-title svg { flex: none; }

/* ── Condition glyph animation ──
   Subtle loops on the inline SVG glyphs from glyphSvg (js/app.js).
   px inside transforms on SVG children resolve in viewBox units, so the
   same keyframes scale for the hero (26px) and day rows (20px).
   No base rule sets opacity/transform: animation:none restores the
   static glyphs exactly. --glyph-phase desynchronizes the day rows. */

@keyframes glyph-shimmer {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.25; }
}
.glyph-ray {
  animation: glyph-shimmer 3.2s ease-in-out infinite;
  animation-delay: calc(var(--i, 0) * -0.4s + var(--glyph-phase, 0s));
}

@keyframes glyph-shimmer-soft {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}
.glyph--partlyCloudy .glyph-ray {
  animation-name: glyph-shimmer-soft;
  animation-duration: 5.2s;
  animation-delay: calc(var(--i, 0) * -0.65s + var(--glyph-phase, 0s));
}

/* The clear night's three stars (the hero's night set), twinkling on the
   full-sun shimmer's 3.2s bar — the stagger spreads the three across it.
   The crescent itself never moves. */
@keyframes glyph-twinkle {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.15; }
}
.glyph-star {
  animation: glyph-twinkle 3.2s ease-in-out infinite;
  animation-delay: calc(var(--i, 0) * -1.05s + var(--glyph-phase, 0s));
}

@keyframes glyph-fall {
  0%   { transform: translateY(-2.4px); opacity: 0; }
  18%  { opacity: 1; }
  70%  { opacity: 1; }
  100% { transform: translateY(2.4px); opacity: 0; }
}
.glyph-drop {
  animation: glyph-fall 2.4s linear infinite;
  animation-delay: calc(var(--i, 0) * -0.8s + var(--glyph-phase, 0s));
}

@keyframes glyph-flash {
  0%, 82%, 100% { opacity: 1; }
  85%           { opacity: 0.1; }
  88%           { opacity: 1; }
  91%           { opacity: 0.3; }
  94%           { opacity: 1; }
}
.glyph-bolt {
  animation: glyph-flash 5.5s linear infinite;
  animation-delay: var(--glyph-phase, 0s);
}

@keyframes glyph-drift {
  0%   { transform: translate(0, -2px); opacity: 0; }
  20%  { opacity: 1; }
  55%  { transform: translate(0.7px, 0.3px); }
  80%  { opacity: 1; }
  100% { transform: translate(-0.3px, 2.2px); opacity: 0; }
}
.glyph-flake {
  animation: glyph-drift 4.2s ease-in-out infinite;
  animation-delay: calc(var(--i, 0) * -1.4s + var(--glyph-phase, 0s));
}

@keyframes glyph-fog-drift {
  0%, 100% { transform: translateX(-0.6px); }
  50%      { transform: translateX(0.6px); }
}
.glyph-fog-line {
  animation: glyph-fog-drift 6s ease-in-out infinite;
  animation-delay: calc(var(--i, 0) * -2s + var(--glyph-phase, 0s));
}

/* The hero's thunderstorm (design 3A). One 2.4s bar carries the whole
   strike — wash, glow, bolt and the cloud's recoil — so they stay in step;
   the rain runs on its own faster loop. */
@keyframes storm-bolt {
  0%, 2% { opacity: 0.35; }
  5%     { opacity: 1; }
  9%     { opacity: 0.3; }
  13%    { opacity: 1; }
  30%    { opacity: 1; }
  46%, 100% { opacity: 0.35; }
}
@keyframes storm-glow {
  0%, 3%    { opacity: 0; transform: scale(0.9); }
  6%        { opacity: 0.85; transform: scale(1.3); }
  13%       { opacity: 0; transform: scale(1.6); }
  17%       { opacity: 0.6; transform: scale(1.25); }
  27%, 100% { opacity: 0; transform: scale(1.55); }
}
@keyframes storm-flash {
  0%, 3%    { opacity: 0; }
  6%        { opacity: 0.55; }
  12%       { opacity: 0; }
  16%       { opacity: 0.35; }
  24%, 100% { opacity: 0; }
}
@keyframes storm-shudder {
  0%, 4%    { transform: translate(0, 0); }
  7%        { transform: translate(0, -1.2px); }
  11%       { transform: translate(0, 0.6px); }
  16%, 100% { transform: translate(0, 0); }
}
@keyframes storm-rain {
  0%   { transform: translateY(-4px); opacity: 0; }
  25%  { opacity: 1; }
  72%  { opacity: 1; }
  100% { transform: translateY(6px); opacity: 0; }
}
.storm-strike { animation: storm-bolt 2.4s linear infinite; }
.storm-jolt { animation: storm-shudder 2.4s ease-out infinite; }
.storm-flash { filter: blur(6px); opacity: 0; animation: storm-flash 2.4s linear infinite; }
.storm-glow {
  filter: blur(2.5px);
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
  animation: storm-glow 2.4s linear infinite;
}
.storm-rain { animation: storm-rain 1.4s linear infinite; }
.storm-rain--2 { animation-delay: -0.7s; }

/* The meteor emblem (design 2A). The original keyframes ran the fade to
   opacity 0 at both ends, so the emblem blinked out entirely every 2.8s —
   louder than anything else here, where rays shimmer and rain falls but
   nothing ever disappears. The trough is lifted to 0.35: the meteors still
   arrive and pass, but the mark holds the page throughout. */
@keyframes meteor-fall {
  0%   { transform: translate(19px, -19px); opacity: 0.35; }
  18%  { opacity: 1; }
  70%  { opacity: 1; }
  100% { transform: translate(-19px, 19px); opacity: 0.35; }
}
.meteor-fall { animation: meteor-fall 2.8s linear infinite; }
.meteor-fall--1 { animation-delay: -1.05s; }
.meteor-fall--2 { animation-delay: -1.9s; }

@keyframes meteor-twinkle {
  0%, 100% { opacity: 0.35; }
  50%      { opacity: 1; }
}
.meteor-star { animation: meteor-twinkle 3.4s ease-in-out infinite; }
.meteor-star--1 { animation-delay: -1.1s; }
.meteor-star--2 { animation-delay: -2.2s; }
.meteor-star--3 { animation-delay: -2.8s; }

#day-list .day-row:nth-child(2) { --glyph-phase: -0.9s; }
#day-list .day-row:nth-child(3) { --glyph-phase: -1.7s; }
#day-list .day-row:nth-child(4) { --glyph-phase: -2.6s; }
#day-list .day-row:nth-child(5) { --glyph-phase: -3.4s; }

@media (prefers-reduced-motion: reduce) {
  .glyph-ray, .glyph-drop, .glyph-bolt, .glyph-flake, .glyph-fog-line,
  .glyph-star {
    animation: none;
  }
  /* The meteors and stars hold at full strength rather than at whatever
     opacity the loop had reached. */
  .meteor-fall, .meteor-star { animation: none; opacity: 1; }
  /* The storm settles: cloud and rain stand still, the bolt stays lit, and
     the two blurred layers behind it go out rather than freezing mid-flash. */
  .storm-rain, .storm-jolt { animation: none; }
  .storm-strike { animation: none; opacity: 1; }
  .storm-flash, .storm-glow { animation: none; opacity: 0; }
}

.day-short { margin-top: 2px; font-size: 12px; font-weight: 500; color: var(--ink-faded); }

/* Low° · gradient range bar · high° (fill positioned inline by app.js). */
.day-temps { display: flex; align-items: center; gap: 8px; margin-top: 4px; flex: none; }
.day-high { font-size: 16px; font-weight: 700; color: var(--ink); min-width: 34px; }
.day-low { font-size: 14px; font-weight: 600; color: var(--ink-faded); min-width: 34px; text-align: right; }

.temp-bar {
  position: relative;
  width: 110px;
  height: 5px;
  flex: none;
  border-radius: 999px;
  background: var(--paper-shade);
  overflow: hidden;
}
.temp-bar-fill { position: absolute; top: 0; bottom: 0; border-radius: 999px; }

@media (max-width: 520px) { .temp-bar { width: 64px; } }

.day-chevron {
  margin-top: 4px;
  color: var(--ink-faded);
  transition: transform 0.2s ease;
  flex: none;
}
.day-row.expanded .day-chevron { transform: rotate(180deg); }

.day-detail {
  overflow: hidden;
  max-height: 0;
  transition: max-height 0.25s cubic-bezier(0.215, 0.61, 0.355, 1);
}
.day-detail-inner { padding-top: 10px; }

.hour-title { font-size: 9px; margin-bottom: 4px; }

.hour-row {
  display: flex;
  align-items: baseline;
  padding: 3px 0;
  gap: 0;
}
.hour-time { width: 52px; flex: none; font-size: 12px; font-weight: 500; color: var(--ink-faded); }
.hour-temp { width: 40px; flex: none; font-size: 14px; font-weight: 600; }
.hour-short {
  flex: 1;
  min-width: 0;
  font-size: 12px;
  font-weight: 500;
  color: var(--ink-faded);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hour-precip { flex: none; font-size: 12px; font-weight: 500; color: var(--accent); }

/* ── First run / loading / error ── */

.centered-view {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: stretch;
  gap: 8px;
  min-height: 100vh;
  padding-top: 0;
  padding-bottom: 10vh;
}

.firstrun-form {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  margin-top: 32px;
}

.zip-input {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 600;
  font-size: 24px;
  color: var(--ink);
  background: none;
  border: none;
  border-bottom: 1px solid var(--rule);
  text-align: center;
  padding: 6px 4px;
  width: min(320px, 80%);
  border-radius: 0;
}
.zip-input:focus { outline: none; border-bottom-color: var(--accent); }
.zip-input::placeholder {
  font-family: var(--sans);
  font-style: normal;
  font-weight: 400;
  font-size: 14px;
  color: var(--ink-faded);
}
.zip-input.small { font-size: 18px; width: 160px; }

.overline-button {
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 2.2px;
  text-transform: uppercase;
  color: var(--accent);
  background: none;
  border: none;
  cursor: pointer;
  padding: 8px;
}
.overline-button.faded { color: var(--ink-faded); }
.overline-button.bold { font-weight: 700; }
.overline-button:hover { text-decoration: underline; }

.spinner {
  width: 28px;
  height: 28px;
  margin: 0 auto 16px;
  border: 2px solid var(--paper-shade);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.9s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ── Footer ── */

/* ── Ad slots (old-newspaper classified style; see js/ads.js) ── */

.ad-slot {
  margin: 22px auto;
  max-width: 420px;
  border: 3px double var(--rule);
  padding: 14px 18px 16px;
  text-align: center;
}

.ad-overline { letter-spacing: 0.3em; color: var(--ink-faded); }

.ad-name {
  font-family: var(--serif);
  font-weight: 700;
  font-size: 20px;
  color: var(--ink);
  margin-top: 4px;
}

.ad-tagline {
  font-family: var(--serif);
  font-style: italic;
  font-size: 14px;
  color: var(--ink-faded);
  margin: 4px 0 10px;
}

.ad-cta { display: inline-block; }

/* ── The pocket edition (a <span> badge is a placeholder; an <a> is a live store link) ── */

.app-badges {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  margin-top: 12px;
}
.app-badge {
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 2.2px;
  text-transform: uppercase;
  color: var(--ink-faded);
  border: 1px solid var(--rule);
  padding: 10px 16px;
}
a.app-badge {
  color: var(--ink);
  text-decoration: none;
}
a.app-badge:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.page-foot {
  margin-top: 24px;
  text-align: center;
}

/* ── The Tropics page ── */

.storm-row {
  border-bottom: 0.6px solid var(--rule);
  padding: 14px 0;
}
.storm-row:last-child { border-bottom: none; }

.storm-datums { justify-content: flex-start; margin-top: 8px; }

.tropics-map { margin-top: 4px; }

.map-footnote { margin-top: 6px; text-align: left; }

/* Storm name labels on the track map, in the almanac voice. */
.storm-label {
  background: rgba(246, 241, 231, 0.9);
  border: 1px solid var(--rule);
  border-radius: 0;
  box-shadow: none;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  padding: 1px 5px;
}

/* Publisher's mark on the About page, inline with the publisher line. */
.publisher-mark-link { text-decoration: none; }
.publisher-mark {
  height: 28px;
  width: auto;
  vertical-align: middle;
  margin-left: 6px;
}

/* Links styled as overline buttons (back links on the Tropics page). */
a.overline-button { text-decoration: none; display: inline-block; }

.footnote {
  margin-top: 12px;
  font-size: 11px;
  color: var(--ink-faded);
  text-align: center;
  line-height: 1.7;
}
.footnote a { color: var(--ink-faded); }
.footnote a:hover { color: var(--accent); }

/* ── Dialog ── */

dialog {
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--rule);
  padding: 24px 28px;
  min-width: 260px;
}
dialog::backdrop { background: rgba(28, 26, 22, 0.45); }
dialog h2 { margin-bottom: 12px; }
.dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 4px;
  margin-top: 16px;
}

/* Larger canvas breathing room on desktop */
@media (min-width: 700px) {
  .ink-chart { height: 170px; }
  .weather-map { height: 380px; }
}
