:root {
  /* Dark is the site's default, so declare it: without this the browser paints
     scrollbars and native controls (the calendar's month inputs, autofill) from
     the OS preference, which the header's theme button can now disagree with. */
  color-scheme: dark;

  --bg: #0f1216;
  --surface: #171b21;
  --surface-2: #1e242c;
  --border: #2a323c;
  --text: #e7ecf2;
  --muted: #9aa6b2;
  --accent: #f0803c;
  /* Discord's brand blurple. Fixed in both schemes on purpose: it identifies the
     provider, so it is their colour rather than one of ours to re-theme. */
  --discord: #5865f2;

  /* temperature grades: 9-step diverging cold -> green -> hot (colorblind-safe).
     rec-* are the "Near Record" danger tiers — deliberately dark/saturated. */
  --rec-cold: #0a2f6b;
  --very-cold: #2166ac;
  --cold: #4393c3;
  --cool: #92c5de;
  --normal: #4a9d5b;   /* middle of the diverging temp scale = green */
  --warm: #f6c18a;
  --hot: #ef9351;
  --very-hot: #dd6b52;
  --rec-hot: #8f0e20;

  /* precipitation: "dry" + 9 rain-intensity tiers, light green -> teal -> dark navy */
  --dry: #c9a24a;
  --wet-1: #d9f0d3;
  --wet-2: #b7e2b1;
  --wet-3: #8fd18f;
  --wet-4: #5cba9f;
  --wet-5: #35a1c0;   /* middle of the rain scale */
  --wet-6: #2b8cbe;
  --wet-7: #226bb3;
  --wet-8: #164a97;
  --wet-9: #08306b;
}

/* The light palette, reachable two ways: the reader picked light on the header's
   theme button (theme.js writes data-theme on <html>), or they never picked
   anything and their OS asks for light.
   The `:not([data-theme="dark"])` is what makes an explicit choice outrank the
   system — without it nobody on a light OS could ever force dark. It is also why
   only the light half is written twice: `:root` above still holds dark, so the
   dark override needs no block of its own.
   THE TWO BLOCKS ARE DELIBERATELY IDENTICAL. Change one, change the other. */
:root[data-theme="light"] {
  color-scheme: light;
  --bg: #f4f6f9;
  --surface: #ffffff;
  --surface-2: #eef1f5;
  --border: #dde3ea;
  --text: #1a2029;
  --muted: #5b6673;
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    color-scheme: light;
    --bg: #f4f6f9;
    --surface: #ffffff;
    --surface-2: #eef1f5;
    --border: #dde3ea;
    --text: #1a2029;
    --muted: #5b6673;
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  background: var(--bg);
  color: var(--text);
  line-height: 1.45;
}

header {
  padding: 18px 24px;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}
/* Full width, unlike `main`, which stays a centred column — and one fixed height.
   Both are deliberate, and both replaced something that was implicit:

   The row was capped at 1152px to line the logo up with the content edges, but a
   signed-in header needs ~1254px (lockup + seven tabs + an account button
   carrying an email + theme + °F/°C) and the cap held until the 1680px step. From
   1200 to 1679 — most laptops — the wordmark rendered UNDER the nav. The width to
   fix it was already there and empty: ~450px of it at 1600px.

   min-height fixes the other half. The bar used to be as tall as whatever was
   inside it: 46px nav pills gave 83px, but once the nav folds into the hamburger
   the tallest thing left is the 43px mark and it shrank to 72px. */
.brand { display: flex; align-items: center; gap: 14px; min-height: 46px; }
/* flex-grow MUST stay 0. Flexible lengths resolve before auto margins, so a
   growing title block swallows the free space and .view-nav's margin-inline:auto
   gets nothing to centre with — the tabs end up hard right. Sized to its content
   and pinned by min-width, the lockup leaves that slack for the nav, and can
   never be crushed under it. padding-right is the floor on the gap when the row
   is tight; the 1199px block drops it, where the nav folds away. */
.brand > div:not(.unit-toggle):not(.acct) { flex: 0 0 auto; min-width: max-content; padding-right: 24px; }
.logo {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  position: relative;
  top: -2px;   /* optical: tile centers on the wordmark's cap height */
}
.logo svg { width: 43px; height: 43px; display: block; }
h1 { margin: 0; font-size: 22px; letter-spacing: -0.02em; }
/* Brand wordmark: monospace stack (instrument/data feel, no webfonts) beside
   the mark, larger than the old Inter treatment. The mark lives INSIDE the h1
   (and inside its home link on server-rendered pages) so glyph and wordmark
   are one lockup, aligned to each other on every page.

   Matched on .site-name as well as h1: the homepage renders the brand as a <p>
   so its hero headline can own the page's only h1. Without the class here the
   lockup falls back to block layout, which baseline-aligns the mark instead of
   centring it (it rides ~8px high) and drops the monospace wordmark.

   line-height:1 is load-bearing for vertical centring. The lockup is a flex
   child of .brand alongside the nav, °F/°C and account controls; leaving the
   inherited 1.45 gives the text box ~13px of half-leading that the mark and the
   36px-tall control pills do not have, so align-items:center centres the BOXES
   and the wordmark still reads low against the nav. Collapsing the leading makes
   the box the glyphs' own height, and the row lines up. */
.brand h1,
.brand .site-name {
  font-family: ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, "Liberation Mono", monospace;
  font-weight: 700;
  font-size: 30px;
  line-height: 1;
  letter-spacing: -0.04em;
  display: flex;
  align-items: center;
  gap: 10px;
}
/* The whole lockup is the home link on every page, so it inherits the wordmark's
   colour rather than looking like body copy that happens to be a link. */
.brand h1 a,
.brand .site-name a {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--text);
  text-decoration: none;
}
.brand h1 a:hover,
.brand .site-name a:hover { color: var(--accent); }
/* The header is ONE height on every page. It used to come in two — a tall
   variant carrying a per-page tagline under the wordmark, and a `compact`
   variant Calendar opted into because it had none. Both the taglines and the
   compact class are gone: a header that changes height as you move between
   tabs makes the whole nav row jump, which is worse than either height. Resist
   reintroducing a per-page variant; change this block and every page follows. */

main { max-width: 1200px; margin: 0 auto; padding: 20px 24px 60px; }

/* Larger monitors: widen the content column in steps (1440p, 2K, 4K) so pages
   use the screen instead of a 1200px strip. Each page's own grids (normals,
   calendar months, day cards) flow into the extra room. */
@media (min-width: 1680px) {
  main { max-width: 1440px; }
}
@media (min-width: 2400px) {
  main { max-width: 1640px; }
}
@media (min-width: 3400px) {
  main { max-width: 1880px; }
}

.controls { margin-bottom: 18px; display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-start; }
/* ---- shared control recipes ----
   One home for the accent-button and text-input look every view reuses. The
   16px input font is load-bearing (below it iOS zooms on focus); buttons keep
   their ≥44px touch targets in the per-component rules, which carry only each
   control's differences from these. */
.date-row button, .range-refresh, .find-btn, .mp-search button, .mp-confirm {
  border-radius: 10px; border: none; cursor: pointer;
  background: var(--accent); color: #1a1206; font-weight: 600;
}
.date-row input, .cal-range input, .day-date input, .mp-search input {
  padding: 9px 12px; border-radius: 10px; border: 1px solid var(--border);
  background: var(--surface); color: var(--text); font-size: 16px;
}
.date-row button { padding: 11px 18px; }

.date-row { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.date-row label { font-size: 13px; color: var(--muted); display: flex; flex-direction: column; gap: 4px; }
/* "Today" quick-reset chip beside the target date. Outlined when the target is a
   past date (a clickable "jump to today"); filled accent while already on today. */
.date-row .today-chip {
  align-self: flex-end; padding: 9px 14px; border-radius: 10px; min-height: 40px;
  font-size: 13px; font-weight: 600; cursor: pointer;
  background: transparent; color: var(--muted); border: 1px solid var(--border);
  transition: background .12s ease, color .12s ease, border-color .12s ease;
}
.date-row .today-chip:hover { border-color: var(--accent); color: var(--text); }
.date-row .today-chip.active {
  background: var(--accent); color: #1a1206; border-color: var(--accent); cursor: default;
}

.hint { color: var(--muted); font-size: 12.5px; max-width: 220px; }

.panel {
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  padding: 20px;
}

.placeholder p { color: var(--text); }
.muted { color: var(--muted); font-size: 13.5px; }

/* --- results --- */
.loc-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; margin-bottom: 16px; }
.loc-head h2 { margin: 0 0 2px; font-size: 18px; }
.loc-head .meta { color: var(--muted); font-size: 12.5px; margin: 0; }
/* Context stacked top-down, one fact per line, instead of dot-joined tags. */
.loc-meta { list-style: none; margin: 4px 0 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.loc-meta li, .loc-coords { color: var(--muted); font-size: 12.5px; line-height: 1.35; }
/* Day Detail/Calendar/Score's place name + coordinates (issue #52) live
   inline in the shared .find-bar, next to "Change location" — not a separate
   block below it, so no view's own content gets pushed down by however tall
   its .controls row happens to be. .loc-label already supplies the right
   look (bold, normal text color) and .find-bar's own flex row + gap:12px
   lays out the star (a sibling, not a child, of #loc-label — see
   bookmarks.js::paintIndicator()) and .loc-coords beside it. .loc-name is
   otherwise a plain marker class paintIndicator() looks for.
   Tried align-self:baseline on .loc-name/.loc-coords to fix a Firefox-only
   sub-pixel baseline gap between the two differently-sized text runs — reverted:
   mixing align-self:baseline with the row's own align-items:center (which the
   button and the star+nickname unit rely on) shifts the shared flex line's
   cross-size, throwing THEIR centering off as a side effect. Chrome already
   renders this correctly; not worth another cross-browser tuning fight over a
   sub-pixel gap in one browser (see .bkm-indicator's own comment on this). */
.share { display: flex; gap: 6px; flex-shrink: 0; flex-wrap: wrap; }
.share button {
  padding: 6px 11px; border-radius: 8px; border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text); font-size: 12px; cursor: pointer;
  white-space: nowrap; text-decoration: none; display: inline-flex; align-items: center; gap: 5px;
}

/* Inline monochrome icons (currentColor) used in place of emoji. */
.ic { width: 1em; height: 1em; vertical-align: -0.14em; flex-shrink: 0; }
.ic-lg { width: 26px; height: 26px; }
.wx { width: 1.15em; height: 1.15em; vertical-align: -0.2em; }
.share button:hover { border-color: var(--accent); }

/* Call-to-action into the 2-year calendar — a compact button-sized link, so the
   graded results stay the page's focal point. */
.cta-cal {
  display: inline-flex; align-items: center; gap: 9px;
  margin: 0 0 20px; padding: 10px 16px; min-height: 44px;
  border: none; border-radius: 10px; text-decoration: none;
  font-size: 14px; font-weight: 700;
  color: #1a1206; background: linear-gradient(135deg, #f79556, #ea722c);
  box-shadow: 0 2px 8px rgba(240, 128, 60, .25);
  transition: transform .12s ease, box-shadow .12s ease, filter .12s ease;
}
.cta-cal .ic-lg { width: 18px; height: 18px; }
.cta-cal:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(240, 128, 60, .35); filter: brightness(1.04); }
.cta-cal:active { transform: translateY(0); box-shadow: 0 2px 6px rgba(240, 128, 60, .3); }
.cta-cal:focus-visible { outline: 3px solid var(--text); outline-offset: 2px; }
.cta-cal-icon { line-height: 1; flex-shrink: 0; display: inline-flex; }
.cta-cal-arrow { font-size: 16px; font-weight: 800; flex-shrink: 0; }

#chart-wrap { position: relative; margin-bottom: 22px; }
#chart-wrap svg { width: 100%; height: auto; display: block; border-radius: 10px; touch-action: pan-y; }
/* High / Low / Weather metric toggle above the trend chart; active tab takes the
   metric's own color so the control matches the chart it drives. */
.chart-toggle {
  display: flex; flex-wrap: wrap; border: 1px solid var(--border); border-radius: 10px;
  overflow: hidden; margin-bottom: 10px;
}
.chart-toggle button {
  padding: 8px 18px; background: var(--surface); color: var(--text); border: none;
  cursor: pointer; font-size: 13.5px; font-weight: 600; min-height: 40px;
}
.chart-toggle button + button { border-left: 1px solid var(--border); }
/* Beyond phones the switch shrinks to its tabs and sits centered over the chart
   (phones keep the full-width 4-per-row grid from the 640px rules). */
@media (min-width: 641px) {
  .chart-toggle { width: fit-content; margin-left: auto; margin-right: auto; }
}
.chart-toggle button.active[data-metric="tmax"] { background: var(--very-hot); color: #1a1206; }
.chart-toggle button.active[data-metric="tmin"] { background: var(--cold); color: #10222f; }
.chart-toggle button.active[data-metric="feels"] { background: var(--accent); color: #1a1206; }
.chart-toggle button.active[data-metric="humid"] { background: #2ea9bf; color: #04222a; }
.chart-toggle button.active[data-metric="wind"] { background: #5aa9a3; color: #08201e; }
.chart-toggle button.active[data-metric="gust"] { background: #8f86d8; color: #100c26; }
.chart-toggle button.active[data-metric="precip"] { background: var(--wet-6); color: #fff; }
.chart-toggle button.active[data-metric="dry"] { background: #c9a24a; color: #241a04; }
.chart-legend {
  display: flex; flex-wrap: wrap; gap: 14px; font-size: 12px; color: var(--muted); margin-bottom: 8px;
}
.chart-legend span { display: inline-flex; align-items: center; gap: 6px; }
.chart-legend i { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }
.chart-legend i.swatch-band { border: 1px solid; border-radius: 3px; }
.chart-legend .legend-note { font-style: italic; opacity: .85; }
.chart-tip {
  position: absolute; z-index: 10; pointer-events: none;
  background: var(--surface); border: 1px solid var(--border); border-radius: 9px;
  padding: 8px 10px; font-size: 12px; line-height: 1.5; min-width: 148px;
  box-shadow: 0 8px 24px rgba(0,0,0,.35);
}
.chart-tip .tt-date { font-weight: 700; margin-bottom: 3px; }
.chart-tip .tt-grade { color: var(--muted); }
.chart-tip .tt-norm { color: var(--muted); margin-top: 3px; border-top: 1px solid var(--border); padding-top: 3px; }

/* Flex (not fixed 3-col) so 7 cards fill each row evenly — no lonely trailing card. */
.normals {
  display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 22px;
}
.normal-card {
  flex: 1 1 130px;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 11px; padding: 12px 14px;
  text-decoration: none; color: var(--text); cursor: pointer;
  transition: border-color .12s ease, background .12s ease;
}
.normal-card:hover { border-color: var(--accent); background: var(--surface); }
/* Title on the left, grade status pinned to the top-right corner of the card. */
.normal-card .nc-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; margin-bottom: 6px; }
.normal-card h3 { margin: 0; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
/* Big value is the day's own reading, tinted by its grade (lifted toward the text
   color so dark tiers stay legible); the line under it shows the normal median. */
.normal-card .big { font-size: 22px; font-weight: 800; color: color-mix(in oklab, var(--cat, var(--text)), var(--text) 15%); }
.normal-card .range { font-size: 12px; color: var(--muted); margin-top: 2px; }
.normal-card .nc-grade { font-size: 11px; font-weight: 700; text-align: right; line-height: 1.2; color: color-mix(in oklab, var(--cat, var(--muted)), var(--text) 28%); }

.section-title {
  font-size: 12px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--muted); margin: 4px 0 12px;
}

/* Plain-language "what to expect" insights (home results + calendar). A soft
   card of friendly climatology sentences with a leading emoji per line. */
.insights, .cal-insights {
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 12px; padding: 12px 14px; margin: 4px 0 16px;
  border-left: 3px solid var(--accent);
}
.insight-lead {
  font-size: 12px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--muted); margin: 0 0 8px;
}
.insight-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.insight-list li {
  display: flex; align-items: baseline; gap: 8px;
  font-size: 15px; line-height: 1.45; color: var(--text);
}
.insight-list li b { color: var(--text); font-weight: 700; }
.ins-emoji { flex-shrink: 0; font-size: 16px; line-height: 1; }
/* Tint the rain line's accent stripe to the mid rain-scale blue. */
.insight-list li.ins-rain { border-left: 2px solid var(--wet-5); padding-left: 8px; margin-left: -2px; }

/* Wide screens: let the insight sentences flow in two columns. */
@media (min-width: 900px) {
  .insight-list { grid-template-columns: 1fr 1fr; gap: 8px 20px; }
}

/* grade-scale key (low -> high, relative categories) — swatch + name only;
   the percentile figures live on the full guide, not here. */
.tier-key { display: flex; flex-wrap: wrap; gap: 6px 14px; margin: -2px 0 8px; }
.tk-seg { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; }
.tk-swatch { width: 14px; height: 10px; border-radius: 3px; flex-shrink: 0; }
.tk-label { font-weight: 600; }
.tk-note { margin: 0 0 16px; font-size: 12.5px; }

/* --- header °F/°C unit toggle (sits at the top-right, left of the tabs) --- */
/* No margin-left: auto here. It used to be what shoved this to the far right,
   but auto margins SHARE the row's free space — with one here and one on each
   side of .view-nav the slack split three ways and the tabs sat left of centre.
   The nav's right-hand auto margin already pushes this whole cluster (account,
   theme, °F/°C) to the right edge, so a second claim on the space only fights it. */
.unit-toggle {
  align-self: center; flex-shrink: 0; display: inline-flex; gap: 2px;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 11px; padding: 3px;
}
.unit-toggle button {
  border: 0; background: transparent; color: var(--muted); cursor: pointer;
  font-size: 14px; font-weight: 700; padding: 8px 12px; border-radius: 8px;
  min-height: 38px; min-width: 44px; line-height: 1;
}
.unit-toggle button.active { background: var(--accent); color: #fff; }
.unit-toggle button:hover:not(.active) { color: var(--text); }
/* The view switcher already claims the right edge; with the toggle to its left,
   drop its auto-margin so the two sit together in the corner. */

/* --- header theme button (cycles Auto -> Light -> Dark, theme.js) ---
   One square chip rather than a third segmented control: the header row already
   carries the °F/°C segments, the account chip, the bell and seven view tabs, and
   a 3-segment switch here overflows it around 1100px. Sized and coloured as the
   account chip so the top-right cluster reads as one set of controls. */
.theme-toggle {
  align-self: center; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  width: 40px; height: 40px; padding: 0;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text);
  border-radius: 11px; cursor: pointer;
}
.theme-toggle:hover { border-color: var(--accent); color: var(--accent); }
.theme-toggle svg { display: block; }
/* The state's name rides along only in the phone dropdown, where the control
   stretches to a full-width row and a lone icon would read as a mystery button.
   On desktop the same sentence is the button's aria-label and tooltip. */
.theme-label { display: none; font-size: 14px; font-weight: 600; }

/* --- account entry (header) + auth modal --- */
/* The account button is the header's shock absorber. Everything else in the row
   is a fixed size or must not shrink — the lockup is pinned by min-width:
   max-content above, the tabs are white-space: nowrap — so if the row ever runs
   short, this is what gives, and .acct-name ellipsises the address. That is the
   whole point of the min-width: 0 chain below: without it the flex items refuse
   to shrink past their content and the overflow lands on the wordmark instead,
   which is how the signed-in header used to render "Thermog" under the nav. */
.acct { align-self: center; flex-shrink: 1; min-width: 0; position: relative; }
.acct-menu, .acct-btn { min-width: 0; }
.acct-btn {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text);
  border-radius: 11px; padding: 8px 12px; min-height: 40px; font-size: 14px; font-weight: 600;
}
.acct-btn:hover { border-color: var(--accent); }
.acct-btn svg { width: 18px; height: 18px; flex-shrink: 0; }
.acct-name { max-width: 16ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.acct-menu { position: relative; }
.acct-pop {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 1100; min-width: 180px;
  display: flex; flex-direction: column; padding: 6px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 16px 40px rgba(0, 0, 0, .45);
}
.acct-pop[hidden] { display: none; }
.acct-pop-link {
  display: block; text-align: left; padding: 10px 12px; border-radius: 8px; min-height: 40px;
  background: transparent; border: 0; color: var(--text); cursor: pointer;
  font: inherit; font-size: 14px; text-decoration: none;
}
.acct-pop-link:hover { background: var(--surface-2); }

/* Notification bell + dropdown. */
.acct { display: inline-flex; align-items: center; gap: 8px; }
.notif { position: relative; }
.notif-btn { position: relative; padding: 8px; min-width: 40px; justify-content: center; }
.notif-badge {
  position: absolute; top: -4px; right: -4px; min-width: 18px; height: 18px; padding: 0 4px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent); color: #1a1206; font-size: 11px; font-weight: 800;
  border-radius: 999px; line-height: 1;
}
.notif-badge[hidden] { display: none; }
.notif-pop {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 1100;
  width: min(360px, calc(100vw - 24px));
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 16px 40px rgba(0, 0, 0, .45); overflow: hidden;
}
.notif-pop[hidden] { display: none; }
.notif-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 14px; border-bottom: 1px solid var(--border); font-weight: 700; font-size: 14px;
}
.notif-readall {
  background: none; border: 0; color: var(--accent); cursor: pointer;
  font: inherit; font-size: 12px; font-weight: 600;
}
.notif-list { list-style: none; margin: 0; padding: 4px; max-height: 60vh; overflow-y: auto; }
.notif-empty { padding: 20px 14px; text-align: center; font-size: 13px; }
.notif-item { padding: 10px 12px; border-radius: 9px; cursor: default; }
.notif-item.is-unread { cursor: pointer; background: color-mix(in srgb, var(--accent) 10%, transparent); }
.notif-item.is-unread:hover { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.notif-item + .notif-item { margin-top: 2px; }
.notif-item-title { font-weight: 700; font-size: 13px; display: flex; align-items: center; gap: 6px; }
.notif-item.is-unread .notif-item-title::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); flex-shrink: 0;
}
.notif-item-body { font-size: 12px; color: var(--muted); margin-top: 3px; }
.notif-item-time { font-size: 11px; margin-top: 3px; }
.notif-manage {
  display: block; padding: 10px 14px; border-top: 1px solid var(--border);
  font-size: 13px; font-weight: 600; color: var(--accent); text-decoration: none;
}
.notif-manage:hover { background: var(--surface-2); }

/* Auth modal reuses .mp-overlay/.mp-modal chrome; this styles its form body. */
.acct-modal { max-width: 400px; }
.acct-form { display: flex; flex-direction: column; gap: 14px; padding: 16px; }
.acct-form label { display: flex; flex-direction: column; gap: 5px; font-size: 13px; color: var(--muted); }
.acct-form input {
  padding: 11px 14px; border-radius: 10px; border: 1px solid var(--border);
  background: var(--bg); color: var(--text); font-size: 16px;
}
.acct-form input:focus { outline: none; border-color: var(--accent); }
.acct-submit {
  margin-top: 2px; padding: 12px 16px; min-height: 44px; border: none; border-radius: 10px;
  background: var(--accent); color: #1a1206; font-weight: 700; font-size: 15px; cursor: pointer;
}
.acct-submit:hover { filter: brightness(1.05); }
.acct-submit:disabled { opacity: .6; cursor: progress; }
.acct-error {
  margin: 0; padding: 9px 12px; border-radius: 8px; font-size: 13px;
  background: color-mix(in srgb, var(--rec-hot, #d64545) 20%, transparent);
  color: var(--text); border: 1px solid var(--rec-hot, #d64545);
}
.acct-error[hidden] { display: none; }
/* "or / Continue with Discord" under the submit button. Hidden unless the server
   reports Discord configured (account.js sets [hidden]). */
.acct-alt { display: flex; flex-direction: column; gap: 10px; }
.acct-alt[hidden] { display: none; }
.acct-or {
  display: flex; align-items: center; gap: 10px;
  font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .06em;
}
.acct-or::before, .acct-or::after {
  content: ""; flex: 1; height: 1px; background: var(--border);
}
.acct-oauth-row { display: flex; flex-direction: column; gap: 8px; }
.acct-oauth-login {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  padding: 12px 16px; min-height: 44px; border-radius: 10px;
  font-weight: 700; font-size: 15px; text-decoration: none;
  border: 1px solid transparent;
}
.acct-oauth-login:hover { filter: brightness(1.08); }
.acct-oauth-login svg { width: 20px; height: 20px; flex: none; }
.acct-oauth-login.is-discord {
  background: var(--discord); color: #fff;
}
.acct-oauth-login.is-discord svg { height: 15px; }
/* Google's branding terms require their mark on white (or their own grey), with a
   visible border — so this one button is deliberately light in both schemes and
   does not follow the surface tokens. */
.acct-oauth-login.is-google {
  background: #fff; color: #1f1f1f; border-color: #747775;
}
.acct-oauth-login.is-google:hover { filter: none; background: #f2f2f2; }

.acct-pop-note { display: block; padding: 9px 12px; font-size: 13px; }
.acct-switch { margin: 0; font-size: 13px; color: var(--muted); text-align: center; }
.acct-switch button {
  background: none; border: 0; color: var(--accent); cursor: pointer;
  font: inherit; font-weight: 600; padding: 0; text-decoration: underline;
}

/* One-off status toast (e.g. "email verified") — neutral for both success and
   failure; the text says which, not the color. */
.acct-toast {
  position: fixed; left: 50%; bottom: 24px; z-index: 1200;
  transform: translateX(-50%);
  max-width: calc(100vw - 32px); padding: 12px 18px; border-radius: 10px;
  background: var(--surface); border: 1px solid var(--border); color: var(--text);
  font-size: 14px; box-shadow: 0 12px 30px rgba(0, 0, 0, .4);
}
.acct-toast[hidden] { display: none; }

/* Undo toast for an accidentally-removed nicknamed bookmark (bookmarks.js's
   offerUndo()) — same chrome as .acct-toast, plus an inline action button. */
.bkm-toast {
  position: fixed; left: 50%; bottom: 24px; z-index: 1200;
  transform: translateX(-50%);
  max-width: calc(100vw - 32px); border-radius: 10px;
  background: var(--surface); border: 1px solid var(--border); color: var(--text);
  font-size: 14px; box-shadow: 0 12px 30px rgba(0, 0, 0, .4);
}
.bkm-toast[hidden] { display: none; }
/* Clips the depletion bar to a properly rounded corner — the bar is only 3px
   tall, and a corner needs both adjoining edges at least as long as the
   radius to render as an actual quarter-circle; asking a 3px-tall strip for a
   10px corner just gets clamped to whatever fits, producing a visibly
   tighter curve than .bkm-toast's real one. Cutting the bar with a same-size,
   same-radius wrapper (overflow:hidden + border-radius:inherit) sidesteps
   that entirely, and doing it on an inner wrapper rather than .bkm-toast
   itself keeps the tooltip arrow (a ::before on .bkm-toast, sitting just
   outside its box) from being clipped along with it. */
.bkm-toast-inner {
  position: relative; display: flex; align-items: center; gap: 14px;
  padding: 10px 10px 10px 18px; overflow: hidden; border-radius: inherit;
}
.bkm-toast-msg { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bkm-toast-undo {
  flex-shrink: 0; background: none; border: 0; color: var(--accent); cursor: pointer;
  font: inherit; font-weight: 700; padding: 6px 10px; border-radius: 8px;
}
.bkm-toast-undo:hover { background: var(--surface-2); }
/* Countdown bar for the undo window — shrinks from full width to nothing
   over UNDO_MS (bookmarks.js sets animation-duration inline to stay in sync
   with that one constant, rather than a second copy of "8s" living here).
   scaleX from the left edge, not width, so it's one composited layer instead
   of a layout-triggering resize every frame. Corners are handled entirely by
   .bkm-toast-inner's clip, not here. */
.bkm-toast-bar {
  position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
  background: var(--accent); transform-origin: left; transform: scaleX(1);
}
.bkm-toast-bar.bkm-toast-bar-run {
  animation-name: bkm-toast-deplete; animation-timing-function: linear; animation-fill-mode: forwards;
}
@keyframes bkm-toast-deplete { to { transform: scaleX(0); } }
/* Anchored to a clicked star (bookmarks.js's positionUndoToast()) instead of
   sitting bottom-center — top/left come from JS, so only clear the base
   rule's bottom-center placement here. The ::before arrow is a rotated
   square with two borders dropped, the standard CSS-tooltip-pointer trick;
   which two borders (and which edge it sits on) flips with .bkm-toast-above,
   depending on whether the toast landed below or above its anchor. */
.bkm-toast.bkm-toast-anchored { bottom: auto; transform: none; }
.bkm-toast-anchored::before {
  content: ""; position: absolute; left: 50%; width: 10px; height: 10px;
  background: var(--surface); transform: translateX(-50%) rotate(45deg);
}
.bkm-toast-anchored:not(.bkm-toast-above)::before {
  top: -6px; border-top: 1px solid var(--border); border-left: 1px solid var(--border);
}
.bkm-toast-anchored.bkm-toast-above::before {
  bottom: -6px; border-bottom: 1px solid var(--border); border-right: 1px solid var(--border);
}

/* --- alerts page (subscriptions) --- */
.alerts-loading { color: var(--muted); }
.alerts-gate {
  max-width: 480px; margin: 24px auto; padding: 24px; text-align: center;
  display: flex; flex-direction: column; gap: 12px; align-items: center;
}
.alerts-gate h2 { margin: 0; }
.alerts-gate { max-width: 520px; }
.alerts-lede { margin: 0; }
/* The "what are alerts" steps read as a left-aligned list inside the centered card. */
.alerts-explain {
  text-align: left; align-self: stretch; margin: 2px 0; padding-left: 24px;
  display: flex; flex-direction: column; gap: 10px; line-height: 1.5;
}
.alerts-explain li { padding-left: 4px; }
.alerts-eg { margin: 0; font-style: italic; }
.alerts-gate-note { margin: 0; font-size: 13px; }
.alerts-gate .acct-submit { max-width: 280px; }
.alerts-toolbar {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 16px;
}
.alerts-toolbar .find-btn { padding: 11px 18px; min-height: 44px; }
.alerts-note { margin: 0; font-size: 13px; }

/* Per-device push toggle, above the account-wide alert list. */
.push-bar {
  border: 1px solid var(--border); background: var(--surface); border-radius: 14px;
  padding: 12px 16px; margin-bottom: 16px;
}
.push-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
}
.push-status { font-size: 14px; font-weight: 600; }
.push-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.push-toggle { padding: 9px 16px; min-height: 44px; }
.push-test-btn {
  min-height: 44px; padding: 9px 14px; border-radius: 10px; cursor: pointer;
  border: 1px solid var(--border); background: transparent; color: var(--text); font-size: 14px;
}
.push-test-btn:hover { border-color: var(--accent); }
.push-note { margin: 0; }

.sub-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 14px; }
@media (min-width: 720px) { .sub-list { grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); } }
.sub-empty { padding: 20px; border: 1px dashed var(--border); border-radius: 12px; text-align: center; }
.sub-card {
  border: 1px solid var(--border); background: var(--surface); border-radius: 14px;
  padding: 16px; display: flex; flex-direction: column; gap: 12px;
}
.sub-card.is-paused { opacity: .62; }
.sub-card.is-busy { pointer-events: none; opacity: .6; }
.sub-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
/* Column, not a wrapping row: the kind badge (subCard() only — bkmCard() has
   just the name here, so this is a no-op for it) always starts its own line,
   rather than trailing the name when it fits and jumping to a third line only
   once a long name wraps to two (issue #52). */
.sub-title { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; min-width: 0; }
.sub-name { font-weight: 700; font-size: 15px; }
.sub-kind {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  padding: 3px 8px; border-radius: 999px; white-space: nowrap;
}
.sub-kind-observed { background: color-mix(in srgb, var(--accent) 20%, transparent); color: var(--accent); }
.sub-kind-forecast { background: var(--surface-2); color: var(--muted); border: 1px solid var(--border); }
.sub-remove {
  flex-shrink: 0; border: 1px solid var(--border); background: transparent; color: var(--muted);
  cursor: pointer; border-radius: 9px; min-width: 40px; min-height: 40px;
  display: inline-flex; align-items: center; justify-content: center;
}
.sub-remove svg { width: 17px; height: 17px; }
.sub-remove:hover { border-color: var(--rec-hot, #d64545); color: var(--rec-hot, #d64545); }
.sub-metrics { display: flex; flex-wrap: wrap; gap: 6px; }
.sub-metric {
  font-size: 12px; padding: 3px 9px; border-radius: 999px;
  background: var(--surface-2); color: var(--text); border: 1px solid var(--border);
}
.sub-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; justify-content: space-between; }
.sub-thr { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 13px; color: var(--muted); }
.sub-active-lbl { display: inline-flex; align-items: center; gap: 7px; font-size: 14px; cursor: pointer; }
.sub-active-lbl input { width: 18px; height: 18px; accent-color: var(--accent); }

/* Segmented control shared by the threshold + kind pickers. */
.seg { display: inline-flex; gap: 2px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px; padding: 3px; }
.seg button {
  border: 0; background: transparent; color: var(--muted); cursor: pointer;
  font: inherit; font-weight: 700; font-size: 13px; padding: 7px 11px; border-radius: 7px;
  min-height: 38px; min-width: 40px; line-height: 1;
}
.seg button.active { background: var(--accent); color: #1a1206; }
.seg button:hover:not(.active) { color: var(--text); }

/* Alert editor modal (reuses .mp-overlay / .mp-modal chrome). */
.sub-editor { max-width: 460px; }
.sub-form { display: flex; flex-direction: column; gap: 16px; padding: 16px; overflow-y: auto; }
.sub-form-loc { margin: 0; font-weight: 700; font-size: 15px; }
.sub-field { border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px; margin: 0; display: flex; flex-direction: column; gap: 10px; }
.sub-field legend { padding: 0 6px; font-size: 13px; color: var(--muted); }
.sub-kind-help { margin: 0; font-size: 12px; }
.metric-checks { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 14px; }
.metric-check { display: inline-flex; align-items: center; gap: 8px; font-size: 14px; cursor: pointer; min-height: 32px; }
.metric-check input { width: 18px; height: 18px; accent-color: var(--accent); flex-shrink: 0; }
.thr-range { width: 100%; accent-color: var(--accent); height: 28px; }
.two-sided-lbl { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--muted); cursor: pointer; }
.two-sided-lbl input { width: 18px; height: 18px; accent-color: var(--accent); flex-shrink: 0; }
.sub-form-error {
  margin: 0; padding: 9px 12px; border-radius: 8px; font-size: 13px;
  background: color-mix(in srgb, var(--rec-hot, #d64545) 20%, transparent);
  color: var(--text); border: 1px solid var(--rec-hot, #d64545);
}
.sub-form-error[hidden] { display: none; }
@media (max-width: 640px) { .metric-checks { grid-template-columns: 1fr; } }

/* --- shared header view switcher (Map · Calendar · Day) --- */
/* The bar is split into equal, fully-clickable segments (one per page) with a
   clear divider between them, so the whole width is a set of obvious tabs. */
/* Three zones: brand left, tabs centre, account/theme/°F°C right. An auto margin
   on BOTH sides of the nav splits the row's free space evenly, parking it between
   the lockup and the control cluster — which is the header's centre only while
   those two are the same width. Signed in they are not, so this lands a few dozen
   px off; the min-width:1440px block below upgrades to true centring wherever
   there is provably room for it.

   This stays the base case because it never overlaps: in flow, the nav still
   pushes the controls right and yields when the row is tight. */
.view-nav {
  margin-inline: auto; align-self: center; display: inline-flex; gap: 0;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 11px; padding: 3px;
}
/* 14px of side padding, not 18px. Seven tabs multiply it by seven, and .brand is
   capped at 1152px until the 1680px step — at 1200–1679 the row (lockup + nav +
   theme + °F/°C + account + gaps) came to exactly 1152 with the tabs at 18px, so
   there was no slack for the brand lockup to breathe and the wordmark sat against
   the first tab. Four pixels a side returns 56px to that band. */
.view-nav a {
  flex: 1; color: var(--muted); text-decoration: none; font-size: 14px; font-weight: 600;
  padding: 8px 14px; border-radius: 8px; white-space: nowrap; position: relative;
  display: inline-flex; align-items: center; justify-content: center; min-height: 38px;
}
/* Divider line between adjacent tabs. Hidden around the highlighted (active) tab
   so its pill reads clean against the bar. */
.view-nav a + a::before {
  content: ""; position: absolute; left: 0; top: 16%; bottom: 16%;
  width: 2px; border-radius: 2px; background: var(--border);
}
.view-nav a.active::before,
.view-nav a.active + a::before { background: transparent; }
.view-nav a:hover { color: var(--text); }
.view-nav a.active { background: var(--accent); color: #fff; }

/* Hamburger wrapper. On desktop the menu and its panel are transparent
   (display:contents), so the nav pills plus the injected °F/°C toggle, bell and
   account render inline in the header row and the hamburger summary is hidden;
   on phones (the 640px block) the panel becomes a dropdown behind the hamburger. */
.nav-menu { display: contents; }
.nav-panel { display: contents; }
/* A closed <details> hides its non-summary content; force the panel visible on
   desktop (the 640px block re-hides it behind the hamburger on phones). */
.nav-panel > .view-nav { display: inline-flex; }
.nav-menu::details-content { display: contents; }
/* The header's visual order — tabs · account · theme · °F/°C — set explicitly
   rather than left to the DOM, because the three controls are injected by
   whichever of their modules runs first and that differs per page: the SSR pages
   load units.js and account.js from base_scripts_default before base_tail's
   theme.js, the SPA shells load theme.js first.
   The phone dropdown stacks in this same order, so the 1199px block does not
   restate it. */
.nav-panel > .view-nav { order: 1; }
.nav-panel > .acct { order: 2; }
.nav-panel > .theme-toggle { order: 3; }
.nav-panel > .unit-toggle { order: 4; }
/* True centring — on the header rather than between its neighbours. Taking the
   nav out of flow is what buys that, and also what costs it the ability to push
   the controls right or refuse to overlap them, so it is gated on the width where
   the arithmetic says it cannot collide: the right cluster is at most 388px
   (account + theme + °F/°C, .acct-name capped at 16ch), which a centred 575px nav
   clears from 1428px up. Hence 1440. Below it the flow centring above stands.

   .acct takes the auto margin here because with .view-nav absolute, nothing else
   is left to push the cluster to the right edge. */
@media (min-width: 1440px) {
  .brand { position: relative; }
  .nav-panel > .view-nav {
    position: absolute; left: 50%; transform: translateX(-50%); margin-inline: 0;
  }
  .nav-panel > .acct { margin-left: auto; }
}

.nav-toggle { display: none; list-style: none; cursor: pointer; }
.nav-toggle::-webkit-details-marker { display: none; }
.nav-toggle svg { display: block; }

/* --- calendar subpage --- */
.metric-toggle {
  display: flex; flex-wrap: wrap; border: 1px solid var(--border); border-radius: 10px; overflow: hidden;
}
.metric-toggle button {
  padding: 10px 16px; background: var(--surface); color: var(--text);
  border: none; cursor: pointer; font-size: 14px; min-height: 44px; white-space: nowrap;
}
.metric-toggle button + button { border-left: 1px solid var(--border); }
.metric-toggle button.active { background: var(--accent); color: #1a1206; font-weight: 600; }

/* Metric selector block — sits below the time filters, labeling the grid it colors. */
.metric-block { margin: 0 0 18px; }
.metric-block .cal-filter-label { display: block; margin: 0 0 8px; }
/* Beyond phones the selector shrinks to its tabs and sits centered over the
   month grid (scoped under .metric-block — the Compare page's basis toggle
   shares .metric-toggle and must keep filling its param cell). */
@media (min-width: 641px) {
  .metric-block { display: flex; flex-direction: column; align-items: center; }
  .metric-block .cal-filter-label { text-align: center; }
}

/* Month/year range picker (calendar). 16px inputs avoid iOS zoom-on-focus. */
.cal-range { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 12px; }
/* Range label + "up to 3 years" hint share one row spanning both input columns. */
.cal-range-head {
  grid-column: 1 / -1;
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
}
.cal-range label {
  display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--muted);
}
.cal-range input { width: 100%; padding: 8px 10px; border-radius: 8px; min-height: 40px; }
.cal-range .hint { margin: 0; flex: 0 0 auto; max-width: none; font-size: 12px; color: var(--muted); }
/* Appears under the pickers once the user edits a date; no request is made until
   it's tapped. Spans both columns so it reads as a clear confirm step. */
.range-refresh {
  grid-column: 1 / -1; margin-top: 2px;
  padding: 10px 16px; min-height: 44px; border-radius: 9px;
  font-size: 15px; font-weight: 700; border: 1px solid var(--accent);
}
.range-refresh:hover { filter: brightness(1.05); }

/* Time-filter panel: month/year range + season + year checklists, stacked in a
   tidy column on phones. */
.cal-filters {
  display: flex; flex-direction: column; gap: 16px;
  max-width: 560px; margin: 0 0 18px; padding: 14px 16px;
  border: 1px solid var(--border); border-radius: 12px; background: var(--surface);
}
/* Season + year dropdowns fill the row evenly — no dead space to their right. */
.cal-seasonyear { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
/* Desktop: the panel spans the content width as one row — the date range on the
   left, the season/year/weather dropdowns filling the rest — instead of a narrow
   stacked box with dead space beside it. */
@media (min-width: 960px) {
  .cal-filters { max-width: none; flex-direction: row; align-items: flex-start; gap: 14px 28px; }
  .cal-range { flex: 0 1 400px; }
  .cal-seasonyear { flex: 1 1 500px; grid-template-columns: 1fr 1fr 1.6fr; align-content: start; }
  /* A head row mirroring the Range label, so both halves read as label + controls. */
  .cal-seasonyear::before {
    content: "Filter by"; grid-column: 1 / -1;
    font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
    color: var(--muted); font-weight: 700; line-height: 1.45;
  }
  /* Weather joins the same row as Seasons + Years (it only spans the full width
     in the stacked phone layout — the extra .cal-seasonyear qualifier outranks
     that base rule); its panel keeps a usable two-column width, anchored right. */
  .cal-seasonyear .cal-weather { grid-column: auto; }
  .cal-seasonyear .cal-weather .cal-dd-panel { left: auto; right: 0; min-width: 420px; }
}
.cal-filter-label {
  font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); font-weight: 700; margin-right: 2px;
}

.cal-head { margin-bottom: 16px; }
.cal-head h2 { margin: 0 0 2px; font-size: 18px; }
.cal-head .meta { color: var(--muted); font-size: 12.5px; margin: 0; }
/* "loading N more blocks…" note while a long span streams in chunk by chunk. */
.cal-loading { color: var(--accent); font-weight: 600; }
/* Nudge to interact with the grid — wording covers both tap (mobile) and click. */
.cal-hint {
  display: inline-flex; align-items: center; gap: 6px;
  margin: 10px 0 0; padding: 6px 11px; font-size: 13px; font-weight: 600;
  color: var(--accent); background: color-mix(in oklab, var(--accent), transparent 88%);
  border: 1px solid color-mix(in oklab, var(--accent), transparent 70%);
  border-radius: 999px;
}

/* Months flow into as many columns as fit (one on phones, several on monitors),
   so a multi-year span reads as a year-at-a-glance wall instead of one long
   thin strip. Cells stay square via aspect-ratio. */
.calendar {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 20px 24px; margin-bottom: 22px; align-items: start;
}
.cal-month { width: 100%; }
.cal-month h4 { margin: 0 0 6px; font-size: 14px; color: var(--text); font-weight: 700; }
.cal-dows, .cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; }
.cal-dows { margin-bottom: 3px; }
.cal-dows span { font-size: 11px; color: var(--muted); text-align: center; line-height: 1; }
.cal-cell { position: relative; aspect-ratio: 1; border-radius: 6px; background: var(--surface-2); }
.cal-cell.empty { background: transparent; }
.cal-cell.filled { cursor: pointer; }
/* Day-of-month number, corner-anchored. White with a dark halo so it reads on any
   tier color; on transparent (no-record) tiles fall back to a plain muted number. */
.cal-daynum {
  position: absolute; top: 2px; left: 4px; font-size: 10px; font-weight: 700;
  line-height: 1; color: #fff; pointer-events: none;
  text-shadow: 0 0 2px rgba(0, 0, 0, .9), 0 1px 1px rgba(0, 0, 0, .7);
}
.cal-cell.empty .cal-daynum { color: var(--muted); text-shadow: none; }
.cal-cell.filled:hover { outline: 2px solid var(--text); outline-offset: 1px; }

/* Fixed positioning so the tooltip tracks the viewport (clientX/Y) even when the
   page is scrolled far down the calendar. */
#cal-tip { position: fixed; max-width: calc(100vw - 20px); }
/* Mini grid of the day's stats: metric name · value(+pct) · category. */
#cal-tip .tt-grid {
  display: grid; grid-template-columns: auto auto minmax(0, 1fr);
  column-gap: 12px; row-gap: 3px; align-items: baseline;
}
#cal-tip .tt-name { font-weight: 700; }
#cal-tip .tt-val { white-space: nowrap; }
#cal-tip .tt-pct { color: var(--muted); }
/* Dry streak has no percentile, so its text spans the value + category columns. */
#cal-tip .tt-span2 { grid-column: 2 / -1; }
/* The stat currently being viewed is bolded and separated from the rest, so it's
   easy to match a cell's color to the stat it represents. The border spans all
   three columns because it's set on every cell of the active row. */
#cal-tip .tt-name.tt-r-active, #cal-tip .tt-val.tt-r-active { font-weight: 800; }
#cal-tip .tt-r-active { padding-bottom: 5px; border-bottom: 1px solid var(--border); }
/* Category (grade) text, right-aligned, is tinted by --cat — the color that
   represents that day's value for the metric. The active calendar metric gets it
   bold and near full saturation; the others are lifted toward --text so they stay
   readable and read as secondary. color-mix keeps every tier legible on both. */
#cal-tip .tt-cat {
  justify-self: end; text-align: right; font-weight: 600;
  color: color-mix(in oklab, var(--cat, var(--muted)), var(--text) 50%);
}
#cal-tip .tt-cat-active {
  font-weight: 800;
  color: color-mix(in oklab, var(--cat, var(--muted)), var(--text) 12%);
}
/* Plain-language "what the day was like" summary title, sits just under the date. */
#cal-tip .tt-type {
  font-size: 15px; font-weight: 800; margin: 2px 0 6px;
  padding-bottom: 6px; border-bottom: 1px solid var(--border);
}

/* --- single-day detail subpage --- */
.day-weather { margin: 2px 0 4px; font-size: 16px; font-weight: 800; }
.day-nav { display: flex; align-items: flex-end; gap: 8px; }
.day-date { font-size: 13px; color: var(--muted); display: flex; flex-direction: column; gap: 4px; }
.day-step {
  min-width: 44px; min-height: 44px; padding: 0 12px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text);
  font-size: 22px; line-height: 1; cursor: pointer;
}
.day-step:hover:not(:disabled) { border-color: var(--accent); }
.day-step:disabled { opacity: .4; cursor: default; }

/* Metric cards: one column on phones, side by side on monitors so the page
   doesn't run as a single long strip of full-width ladders. minmax(0, 1fr)
   (not bare 1fr, whose min is the content) so one wide ladder row can't inflate
   the shared column track past the viewport and push the whole page off-center. */
#day-body { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; align-items: start; }
@media (min-width: 1000px) { #day-body { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 3400px) { #day-body { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.ladder-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 13px; padding: 16px 16px 12px;
}
.ladder-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.ladder-head h3 { margin: 0; font-size: 15px; }
.ladder-summary { display: inline-flex; align-items: baseline; gap: 8px; }
.day-obs {
  font-size: 20px; font-weight: 800;
  color: color-mix(in oklab, var(--cat, var(--text)), var(--text) 12%);
}
.day-obs-meta { font-size: 12.5px; color: var(--muted); }
.ladder-note {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  margin: 4px 0 12px; font-size: 12px; color: var(--muted);
}
.ladder-note-left { min-width: 0; }
/* Historical range: bold, pinned to the right edge of the card. */
.ladder-range { font-weight: 700; color: var(--text); white-space: nowrap; }

/* Tier ladder: swatch · label · percentile range · value range · observed marker.
   Fixed column widths (not auto) so every row shares the same tracks — the
   percentile ranges and value ranges line up down the whole ladder. The negative
   margin cancels the row's own padding so the swatches sit on the same left edge
   as the card title and the subheader note above. */
.ladder { display: flex; flex-direction: column; gap: 3px; }
.ladder-row {
  display: grid; grid-template-columns: 14px 128px 64px 1fr 60px;
  align-items: center; column-gap: 10px; padding: 6px 8px; margin: 0 -8px;
  border-radius: 8px; font-size: 13px;
}
.ladder-row.active {
  background: color-mix(in oklab, var(--accent), transparent 86%);
  outline: 1px solid color-mix(in oklab, var(--accent), transparent 55%);
}
.ladder-sw { width: 14px; height: 14px; border-radius: 4px; }
.ladder-label { font-weight: 700; color: color-mix(in oklab, var(--cat, var(--text)), var(--text) 22%); }
.ladder-pct { color: var(--muted); font-size: 12px; }
/* Value ranges may wrap (long unit strings like "8.1 g/m³–12.9 g/m³" on narrow
   phones) — nowrap here would widen the row past the card and scroll the page. */
.ladder-val { justify-self: end; text-align: right; font-variant-numeric: tabular-nums; }
/* Observed marker: stays in the rightmost column but reads from its left edge,
   so the arrow sits right beside the value range it points at. */
.ladder-mk { justify-self: start; min-width: 0; }
/* Markers are unit-less ("◀ 11.5") so nowrap can't push them past the card —
   the widest (precip, ◀ 0.25") bleeds a few px into the row padding at most. */
.ladder-mark { color: var(--accent); font-weight: 800; white-space: nowrap; font-size: 12.5px; }


/* Compact "graded days" table: a ROW per metric, a COLUMN per day. Wide runs
   scroll horizontally in their own container; the metric-label column is pinned.

   --rd-label-col is the design *minimum* for that pinned column, applied as a
   min-width on the labels; app.js sizes the track itself to max-content, so
   labels needing more room than the minimum (metric "km/h", or a font
   substituted for the un-hosted Inter) get it instead of spilling into the first
   day column. The cue below is then offset in JS from the measured result.
   --rd-gap is the grid gutter both halves assume. */
:root { --rd-label-col: 70px; --rd-gap: 3px; }
.rd-scroll { position: relative; overflow-x: auto; margin: 0 0 6px; padding-bottom: 4px; }
.rd-grid { display: grid; gap: var(--rd-gap); align-items: stretch; min-width: min-content; }
/* Simple scroll-orientation cue for the timeline: oldest at left, newest at
   right. The offset here is the floor-width fallback; app.js overwrites it with
   the label column's measured width once the grid has laid out. */
.rd-orient {
  display: flex; justify-content: space-between; margin: 0 0 8px;
  padding-left: calc(var(--rd-label-col) + var(--rd-gap));
  font-size: 11.5px; font-weight: 600; letter-spacing: .03em; color: var(--muted);
}
/* Forecast (future) columns: accent-tinted headers. The .rd-today column is the
   graded target day (the orange chart marker), flagged with an accent left edge;
   forecast columns sit to the right of today. */
.rd-colh.rd-fc b { color: var(--accent); }
.rd-colh.rd-fc span { color: color-mix(in oklab, var(--accent), var(--muted) 45%); }
.rd-colh.rd-today b { color: var(--accent); }
.rd-c.rd-fc { opacity: .9; }
.rd-colh.rd-today,
.rd-c.rd-today { box-shadow: inset 2px 0 0 var(--accent); }
/* The pinned column has to paint across the grid gap as well as its own track:
   its background is what hides the cells scrolling underneath, and a bare track
   leaves the gap as a 3px slit those tinted cells show through. Negative margin
   widens the painted box by the gap; the padding puts it back so the text still
   stops 6px short of the seam. */
.rd-corner,
.rd-mlabel {
  position: sticky; left: 0; background: var(--surface);
  min-width: var(--rd-label-col);
  margin-right: calc(var(--rd-gap) * -1);
  padding-right: calc(6px + var(--rd-gap));
}
.rd-corner { z-index: 3; }
.rd-mlabel {
  z-index: 2;
  /* Two tracks, name left and unit right, so every unit shares a right edge and
     the gutter into the first day column is the same on all seven rows. Baseline
     rather than centre: the unit is a smaller font than the name, so centring
     parked it off the name's baseline — by a different amount on the humidity
     row, whose <sup> makes its box taller still. align-content centres that one
     line inside the full-height grid cell. */
  display: grid; grid-auto-flow: column; grid-auto-columns: max-content;
  justify-content: space-between; column-gap: 6px;
  align-content: center; align-items: baseline;
  font-size: 11px; line-height: 1; font-weight: 700;
  text-transform: uppercase; letter-spacing: .03em;
  color: var(--muted);
}
.rd-mname { white-space: nowrap; }
/* Wind/rain cells are bare numbers to keep the grid narrow, so the row label
   carries the unit — quieter than the label itself, and lowercase since "km/h"
   and "mm" are unit symbols, not words. */
.rd-unit {
  font-weight: 500; text-transform: none; letter-spacing: 0;
  font-size: 10px; opacity: .75; white-space: nowrap;
}
/* Browsers shrink <sup> by default (~83%), which makes the humidity row's
   cubic exponent illegible on top of the already-small unit text. Zero
   line-height keeps the raised glyph out of the line box, so the humidity row's
   baseline still matches the six rows without a superscript. */
.rd-unit sup {
  font-size: 100%; line-height: 0;
}
.rd-colh {
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  gap: 0; padding-bottom: 3px; font-size: 10px; color: var(--muted);
  line-height: 1.15; white-space: nowrap; cursor: pointer;
}
.rd-colh b { color: var(--text); font-size: 11.5px; font-weight: 700; }
.rd-c {
  display: flex; align-items: center; justify-content: center;
  min-height: 30px; border-radius: 6px; font-size: 12px; font-weight: 700;
  font-variant-numeric: tabular-nums; cursor: pointer;
  background: color-mix(in oklab, var(--c, var(--surface-2)), transparent 74%);
  color: color-mix(in oklab, var(--c, var(--text)), var(--text) 28%);
}
.rd-c[data-date]:hover { filter: brightness(1.18); }

/* grade colors */
.g-rec-cold { color: var(--rec-cold); } .m-rec-cold { background: var(--rec-cold); }
.g-very-cold { color: var(--very-cold); } .m-very-cold { background: var(--very-cold); }
.g-cold { color: var(--cold); } .m-cold { background: var(--cold); }
.g-cool { color: var(--cool); } .m-cool { background: var(--cool); }
.g-normal { color: var(--normal); } .m-normal { background: var(--normal); }
.g-warm { color: var(--warm); } .m-warm { background: var(--warm); }
.g-hot { color: var(--hot); } .m-hot { background: var(--hot); }
.g-very-hot { color: var(--very-hot); } .m-very-hot { background: var(--very-hot); }
.g-rec-hot { color: var(--rec-hot); } .m-rec-hot { background: var(--rec-hot); }
.g-dry { color: var(--dry); } .m-dry { background: var(--dry); }
.g-wet-1 { color: var(--wet-1); } .m-wet-1 { background: var(--wet-1); }
.g-wet-2 { color: var(--wet-2); } .m-wet-2 { background: var(--wet-2); }
.g-wet-3 { color: var(--wet-3); } .m-wet-3 { background: var(--wet-3); }
.g-wet-4 { color: var(--wet-4); } .m-wet-4 { background: var(--wet-4); }
.g-wet-5 { color: var(--wet-5); } .m-wet-5 { background: var(--wet-5); }
.g-wet-6 { color: var(--wet-6); } .m-wet-6 { background: var(--wet-6); }
.g-wet-7 { color: var(--wet-7); } .m-wet-7 { background: var(--wet-7); }
.g-wet-8 { color: var(--wet-8); } .m-wet-8 { background: var(--wet-8); }
.g-wet-9 { color: var(--wet-9); } .m-wet-9 { background: var(--wet-9); }

/* "Near Record" danger tiers: fill the whole grade cell to make it pop. */
.error { color: #e06a6a; font-size: 14px; }
.spinner { color: var(--muted); font-size: 14px; }
/* --- phones / narrow screens --- */

/* --- Find button + current-place label (opens the modal map picker) --- */
.find-bar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; flex: 1 1 340px; }
.find-btn {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 11px 18px; font-weight: 700; font-size: 15px; min-height: 44px;
}
.find-btn svg { width: 18px; height: 18px; flex-shrink: 0; }
.find-btn:hover { filter: brightness(1.05); }
.loc-label { color: var(--text); font-weight: 600; font-size: 14px; }

/* Weekly panel is now full width (the map lives in the picker, not inline). */

/* Link styling for the compact legend notes + guide page. */
.tk-note a, .hint a, .guide-back a { color: var(--accent); text-decoration: none; font-weight: 600; }
.tk-note a:hover, .hint a:hover, .guide-back a:hover { text-decoration: underline; }

/* --- shared modal location picker --- */
.mp-overlay {
  position: fixed; inset: 0; z-index: 1000;
  background: rgba(0, 0, 0, .55);
  display: flex; align-items: center; justify-content: center; padding: 16px;
}
.mp-overlay[hidden] { display: none; }
.mp-modal {
  width: 100%; max-width: 560px; max-height: 92vh;
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 16px; overflow: hidden; box-shadow: 0 24px 60px rgba(0, 0, 0, .5);
}
.mp-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 16px; border-bottom: 1px solid var(--border);
}
.mp-head h2 { margin: 0; font-size: 16px; }
.mp-close {
  border: none; background: transparent; color: var(--muted); cursor: pointer;
  font-size: 26px; line-height: 1; padding: 0 4px; min-width: 40px; min-height: 40px;
}
.mp-close:hover { color: var(--text); }
.mp-search { position: relative; display: flex; gap: 8px; padding: 12px 16px 8px; }
.mp-search input { flex: 1; min-width: 0; padding: 11px 14px; background: var(--bg); }
.mp-search button { padding: 11px 16px; min-height: 44px; }
.mp-sug {
  position: absolute; top: 100%; left: 16px; right: 16px; z-index: 5;
  list-style: none; margin: 4px 0 0; padding: 4px; max-height: 240px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: 0 12px 30px rgba(0, 0, 0, .4);
}
.mp-sug li {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 12px; border-radius: 7px; cursor: pointer; font-size: 14px;
}
.mp-sug li:hover, .mp-sug li.active { background: var(--surface-2); }
.mp-sug-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mp-sug .sub, .mp-saved-go .sub { color: var(--muted); font-size: 12px; }
/* Coordinates (issue #189) inherit .sub's dimmed look via the shared class
   (<span class="sub mp-coords">) above — desktop-only: a phone-width row has
   no space left for a third piece of text once the name/place line wraps. */
@media (max-width: 640px) { .mp-coords { display: none; } }
/* Overrides the broad ".mp-search button" accent-button rule above (it
   catches these too, since the suggestion list sits inside the search form) —
   needs to out-specificity it, not just come later in the file. */
.mp-sug .mp-sug-star {
  flex-shrink: 0; background: none; border: 0; color: var(--muted); cursor: pointer;
  font-weight: normal; padding: 3px; border-radius: 5px; display: inline-flex;
  min-height: 0;   /* .mp-search button's 44px touch-target floor doesn't apply here */
}
.mp-sug .mp-sug-star:hover { color: var(--text); background: var(--border); }
.mp-sug .mp-sug-star[aria-pressed="true"] { color: var(--accent); }
.mp-sug .mp-sug-star svg { width: 15px; height: 15px; display: block; }
/* Saved-locations list + import nudge, shown between the search box and the
   map when there's anything to show (issue #142: this used to be Weekly-only
   UI next to the Find button; folding it into the shared picker puts it on
   every view). */
.mp-saved { position: relative; padding: 0 16px 8px; }
.mp-saved[hidden] { display: none; }
.mp-saved-toggle {
  display: flex; align-items: center; justify-content: space-between; width: 100%;
  background: none; border: 1px solid var(--border); border-radius: 10px; cursor: pointer;
  color: var(--text); font: inherit; font-size: 13px; font-weight: 600;
  padding: 9px 12px;
}
.mp-saved-toggle[hidden] { display: none; }
.mp-saved-toggle:hover { border-color: var(--accent); }
.mp-saved-toggle svg { flex-shrink: 0; transition: transform .15s ease; }
.mp-saved-toggle[aria-expanded="true"] svg { transform: rotate(180deg); }
/* Floats over the map instead of pushing it down, same as .mp-sug. */
.mp-saved-list {
  position: absolute; top: 100%; left: 16px; right: 16px; z-index: 5;
  list-style: none; margin: 4px 0 0; padding: 0; max-height: 240px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: 0 12px 30px rgba(0, 0, 0, .4);
}
.mp-saved-list[hidden] { display: none; }
.mp-saved-list:empty { display: none; }
.mp-saved-item { display: flex; align-items: center; gap: 2px; }
.mp-saved-item:not(:last-child) { border-bottom: 1px solid var(--border); }
.mp-saved-item:hover { background: var(--surface-2); }
.mp-saved-go {
  flex: 1 1 auto; min-width: 0; text-align: left; background: none; border: 0; cursor: pointer;
  color: var(--text); font: inherit; font-size: 13.5px; padding: 9px 10px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mp-saved-btn {
  flex-shrink: 0; background: none; border: 0; color: var(--muted); cursor: pointer;
  padding: 7px; margin-right: 4px; border-radius: 6px; display: inline-flex;
}
.mp-saved-btn:hover { color: var(--text); background: var(--border); }
.mp-import {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap;
  background: var(--surface-2); border: 1px solid var(--border); border-left: 3px solid var(--accent);
  border-radius: 10px; padding: 9px 12px; margin: 0 0 8px; font-size: 13px;
}
.mp-import[hidden] { display: none; }
.mp-import .mp-import-actions { display: flex; gap: 8px; flex-shrink: 0; }
.mp-import button {
  border-radius: 8px; border: 1px solid var(--border); cursor: pointer;
  padding: 6px 11px; font-size: 12.5px; font-weight: 600; background: var(--surface); color: var(--text);
}
.mp-import button.mp-import-yes { background: var(--accent); color: #1a1206; border-color: var(--accent); }

/* "This is a saved location" indicator (issue #207) — bookmarks.js::indicatorHTML(),
   dropped next to wherever each of the five views renders its place name.
   inline-flex + align-items:center centers the icon against ITS OWN nickname
   text — a self-contained pairing with no font-metric guesswork, unlike
   trying to align an inline icon against a *different*, pre-existing text
   run's baseline (which is what the vertical-align tuning kept fighting).
   Wherever this needs to line up with unrelated text next to it (Day/Calendar/
   Score's own .loc-name, Compare's place name), that's solved by making THAT
   container flex with align-items:center too, so both sit as peer flex items
   centered on the same row — see .find-bar, .loc-name and .cmp-name-primary. */
.bkm-indicator {
  display: inline-flex; align-items: center; gap: 4px; flex-shrink: 0;
  font: inherit; font-size: 0.8em; font-weight: 600; line-height: 1.35; white-space: nowrap;
  /* Now a <button> (was a <span>) so it can be clicked to toggle — reset the
     browser's own button chrome rather than inherit it, so this still looks
     exactly like the plain inline label it was, not a form control. */
  background: none; border: none; padding: 0; margin: 0; cursor: pointer;
  color: var(--muted);   /* unsaved: outline star, muted */
}
.bkm-indicator[aria-pressed="true"] { color: var(--accent); }   /* saved: filled star, accent */
.bkm-indicator:hover { color: var(--accent); }
.bkm-indicator:disabled { opacity: .5; cursor: default; }
.bkm-indicator svg { width: 1.5em; height: 1.5em; flex-shrink: 0; }
/* Matches .loc-coords's own line-height (style.css, "Context stacked..." rule)
   — two visually-similar secondary-text elements with different line-box
   heights round to a different sub-pixel vertical center per browser engine;
   giving them the same line-height removes that mismatch without touching
   align-items/align-self (issue #52 coordinates-vs-nickname alignment). */
.bkm-nick { color: var(--text); line-height: 1.35; }
.mp-map {
  flex: 1 1 auto; min-height: 300px; margin: 4px 16px; z-index: 0;
  border-radius: 12px; overflow: hidden; border: 1px solid var(--border);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .25), 0 6px 18px rgba(0, 0, 0, .25);
}
.mp-map .leaflet-container { background: var(--surface-2); }
/* Base map only (roads/land/water): deepen the roads and push saturation. The
   labels layer (.mp-labels) is left unfiltered so city names keep full contrast. */
.mp-map .mp-base { filter: saturate(1.3) contrast(1.12) brightness(0.92); }
/* Branded accent teardrop pin (an L.divIcon wrapping the Find-button glyph),
   replacing Leaflet's default blue raster marker. */
.mp-pin { background: none; border: none; filter: drop-shadow(0 3px 4px rgba(0, 0, 0, .45)); }
.mp-pin svg { width: 32px; height: 32px; display: block; }
.mp-pin path { fill: var(--accent); stroke: rgba(0, 0, 0, .35); stroke-width: 1.25; }
.mp-pin circle { fill: #fff; stroke: none; }
/* Theme the Leaflet zoom buttons + attribution to sit in the app's surfaces
   instead of Leaflet's stock white chrome. */
.mp-map .leaflet-bar { border: none; box-shadow: 0 2px 8px rgba(0, 0, 0, .3); }
.mp-map .leaflet-bar a {
  background: var(--surface); color: var(--text); border-bottom-color: var(--border);
}
.mp-map .leaflet-bar a:hover { background: var(--surface-2); color: var(--accent); }
.mp-map .leaflet-control-attribution {
  background: color-mix(in srgb, var(--surface) 80%, transparent);
  color: var(--muted); backdrop-filter: blur(2px); border-top-left-radius: 6px;
}
.mp-map .leaflet-control-attribution a { color: var(--accent); }
.mp-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 16px 16px; flex-wrap: wrap;
}
.mp-hint { color: var(--muted); font-size: 12.5px; flex: 1 1 auto; min-width: 0; }
.mp-star {
  display: inline-flex; align-items: center; gap: 6px; border-radius: 10px;
  border: 1px solid var(--border); cursor: pointer; background: var(--surface-2);
  color: var(--text); font-weight: 600; font-size: 13px; padding: 9px 12px; min-height: 44px;
}
.mp-star:hover { border-color: var(--accent); }
.mp-star[aria-pressed="true"] { color: var(--accent); border-color: var(--accent); }
.mp-star:disabled { opacity: .45; cursor: default; }
.mp-star svg { width: 16px; height: 16px; }
.mp-confirm { padding: 11px 18px; min-height: 44px; font-weight: 700; }
.mp-confirm:disabled { opacity: .45; cursor: default; }

/* --- calendar Season/Year filters as compact dropdowns (native <details>) --- */
.cal-dd { position: relative; min-width: 0; }
.cal-dd summary {
  display: flex; align-items: center; gap: 8px; cursor: pointer; user-select: none;
  list-style: none; padding: 8px 12px; min-height: 40px;
  border: 1px solid var(--border); border-radius: 9px; background: var(--surface);
}
.cal-dd summary::-webkit-details-marker { display: none; }
.cal-dd summary .cal-filter-label { flex: 0 0 auto; }
.cal-dd[open] summary { border-color: var(--accent); }
/* Summary value truncates rather than wrapping to a second line in the narrow cell. */
.cal-dd-sum {
  flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-size: 13px; color: var(--text); font-weight: 600;
}
/* Push the caret to the right edge so summaries read cleanly at full width. */
.cal-dd-caret { margin-left: auto; color: var(--muted); font-size: 11px; transition: transform .15s ease; }
.cal-dd[open] .cal-dd-caret { transform: rotate(180deg); }
.cal-dd-panel {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 20; min-width: 168px;
  display: flex; flex-direction: column; gap: 2px; padding: 6px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: 0 12px 30px rgba(0, 0, 0, .35);
}
.cal-dd-opt {
  display: flex; align-items: center; gap: 9px; cursor: pointer;
  font-size: 14px; padding: 8px 10px; border-radius: 7px; min-height: 40px;
}
.cal-dd-opt:hover { background: var(--surface-2); }
.cal-dd-opt input { accent-color: var(--accent); width: 17px; height: 17px; margin: 0; }
/* Group headings inside a dropdown panel (the weather filter's Feel / Sky halves). */
.cal-dd-group {
  font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); font-weight: 700; padding: 8px 10px 2px;
}
/* The weather filter spans the full filter row beneath Seasons + Years, and its
   panel is two columns so 13 checkboxes don't make a skyscraper on phones. */
.cal-weather { grid-column: 1 / -1; }
.cal-weather .cal-dd-panel {
  left: 0; right: 0; display: grid; grid-template-columns: 1fr 1fr; column-gap: 6px;
}
.cal-weather .cal-dd-group { grid-column: 1 / -1; }

/* --- season-primary time filter (shared: calendar Seasons dropdown + compare) ---
   Seasons are the primary, color-coded selector; each expands to its three months
   (the nested suboption). One month Set backs both; a season checkbox is checked when
   all three of its months are on, indeterminate (native dash) when one or two are. */
:root {
  --season-winter: #5b9bd5; --season-spring: #6fbf73;
  --season-summer: #e6a52c; --season-fall: #c56a3a;
}
.cal-season-panel { min-width: 212px; max-height: 320px; overflow-y: auto; gap: 4px; }
.season-row { border-radius: 8px; }
.season-row.expanded { background: color-mix(in oklab, var(--season) 12%, transparent); }
.season-head { display: flex; align-items: center; gap: 8px; }
.season-check {
  display: flex; align-items: center; gap: 9px; flex: 1 1 auto; cursor: pointer;
  padding: 8px 10px; border-radius: 7px; min-height: 40px; font-size: 14px;
}
.season-check:hover { background: var(--surface-2); }
.season-check input { accent-color: var(--season); width: 17px; height: 17px; margin: 0; flex: 0 0 auto; }
.season-sw {
  width: 12px; height: 12px; border-radius: 3px; background: var(--season); flex: 0 0 auto;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .25);
}
.season-name { font-weight: 600; }
/* "Full year" select-all, above the four seasons — a divider separates it, and its
   swatch runs the four season colors so it reads as "everything". */
.season-allyear { --season: var(--accent); border-bottom: 1px solid var(--border); border-radius: 0; margin-bottom: 2px; }
.season-allyear .allyear-sw {
  background: linear-gradient(90deg, var(--season-winter), var(--season-spring), var(--season-summer), var(--season-fall));
  box-shadow: none;
}
.season-expand {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 4px; cursor: pointer;
  background: transparent; border: 1px solid var(--border); border-radius: 7px;
  color: var(--muted); font-size: 12px; padding: 6px 9px; min-height: 34px;
}
.season-expand:hover { color: var(--text); border-color: var(--season); }
.season-caret { font-size: 10px; transition: transform .15s ease; }
.season-expand[aria-expanded="true"] .season-caret { transform: rotate(180deg); }
.season-months {
  display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 2px;
  padding: 2px 4px 6px; margin: 0 0 4px 12px; border-left: 2px solid var(--season);
}
.season-months .month-opt { font-size: 13px; padding: 7px 4px; min-height: 38px; justify-content: flex-start; }
.season-months .month-opt input { accent-color: var(--season); }
.cmp-season-dd .cal-dd { display: block; }

/* Mobile filter sheet — the pill, scrim and grip stay hidden above the phone
   breakpoint (switched on inside the max-width:640px block); desktop keeps the filter
   panel inline. */
.filter-fab, .filter-scrim, .filter-sheet-grip { display: none; }

/* Category totals above the grid: a compact distribution strip. Each category is a
   column captioned with its name over its value (share by default, raw count when
   "Show count" is ticked, in that category's own status color) above a thin status-
   colored line rising from a shared baseline (height ∝ share). Every category draws
   a line — including the neutral "Normal" center of the temperature scale. */
.cal-totals { max-width: 960px; margin: 0 auto 18px; }
/* Header row: the "N days shown — by X" title, with the share/count toggle at the
   far end. Wraps to its own line on narrow screens rather than crushing the title. */
.ct-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px 16px; flex-wrap: wrap; }
.ct-head .section-title { margin-bottom: 4px; }
.ct-count-toggle {
  display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0;
  font-size: 12.5px; color: var(--muted); cursor: pointer; user-select: none;
  margin-bottom: 4px;
}
.ct-count-toggle input { accent-color: var(--accent); width: 15px; height: 15px; margin: 0; cursor: pointer; }
/* Connected category bar chart: one bar per tier, low→high, sitting on a shared
   baseline and nearly touching so they read as one chart. The tier's average value
   sits above its bar, the min–max range inside it, and the share + name below.
   Bar height encodes frequency (within the metric's scale group). */
/* Wraps the unit label + the scrolling strip. The label sits outside .ct-strip's
   overflow so it stays put when a wide (precip) strip scrolls. */
.ct-plot { position: relative; }
/* The metric's unit, shown once at the top-right of the strip — the values on the
   bars are bare so they don't clip a narrow phone column. */
.ct-unit {
  text-align: right; margin: 6px 2px -2px; height: 1em;
  font-size: 11px; font-weight: 700; line-height: 1;
  color: var(--muted); font-variant-numeric: tabular-nums;
}
.ct-strip {
  display: flex; align-items: flex-end; gap: 1px; margin: 10px 0 6px; overflow-x: auto;
}
.ct-col {
  flex: 1 1 0; min-width: 38px;
  display: flex; flex-direction: column; justify-content: flex-end; align-items: stretch;
  text-align: center;
}
/* Average value, pinned just above each bar's top. */
.ct-top {
  min-height: 1.25em; margin-bottom: 3px; font-size: 10.5px; font-weight: 700; line-height: 1;
  color: color-mix(in oklab, var(--c, var(--muted)), var(--text) 45%);
  white-space: nowrap; overflow: hidden;
}
.ct-bar {
  width: 100%; background: var(--c); border-radius: 4px 4px 0 0;
  display: flex; align-items: center; justify-content: center;
}
.ct-bar-0 { height: 2px; opacity: .35; }
/* The tier's low–high span, directly under the average and above the bar. It used
   to sit inside the bar as a dark pill; a column is ~38px on a phone, so it clipped
   there however small the type got. Out here it gets the whole column width, and
   needs no scrim to stay legible over nine different tier colours. */
.ct-range {
  min-height: 1.1em; margin-bottom: 3px;
  font-size: 9px; font-weight: 600; line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--muted); opacity: .9;
  white-space: nowrap; overflow: hidden;
}
/* Share + category name, below the bar. */
.ct-cap { display: flex; flex-direction: column; align-items: center; gap: 1px; margin-top: 5px; }
.ct-num {
  font-size: 12px; font-weight: 700; line-height: 1;
  color: color-mix(in oklab, var(--c, var(--muted)), var(--text) 40%);
}
.ct-num-zero { font-weight: 600; opacity: .38; }
.ct-label {
  min-height: 2.2em; font-size: 10px; line-height: 1.1; font-weight: 600;
  color: var(--muted); overflow: hidden;
  /* The strip's columns sit 1px apart, so long neighbouring names (e.g. "Very
     Heavy" next to "Extreme") ran into each other and read as one word. */
  padding: 0 2px;
}

/* Days filtered out by the weather filter fade back so matches pop. */
.cal-cell.dim { opacity: .15; }

/* --- guide / legend page --- */
.guide { max-width: 760px; }
.guide-block { margin: 0 0 26px; }
.guide-block h2 { font-size: 16px; margin: 0 0 10px; }
.guide-block p { font-size: 14px; margin: 0 0 10px; }
.guide-scale { display: flex; flex-wrap: wrap; gap: 8px 18px; }
.guide-seg { display: inline-flex; align-items: center; gap: 8px; font-size: 13.5px; }
.guide-sw { width: 22px; height: 14px; border-radius: 4px; flex-shrink: 0; }
.guide-lbl { font-weight: 600; }
.guide-rng { color: var(--muted); font-size: 12px; }
.guide-metrics { display: grid; grid-template-columns: max-content 1fr; gap: 8px 18px; margin: 0; }
.guide-metrics dt { font-weight: 700; color: var(--text); }
.guide-metrics dd { margin: 0; color: var(--muted); font-size: 14px; }
.guide-back { margin-top: 8px; }


/* ===== Compare view ===== */
.cmp-locs { flex: 1 1 340px; }
.cmp-loc-list { list-style: none; margin: 8px 0 12px; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.cmp-loc-list:empty { margin: 0; }
.cmp-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 999px; padding: 5px 5px 5px 14px; font-size: 14px;
}
.cmp-chip.loading { color: var(--muted); }
.cmp-chip.pending { color: var(--muted); border-style: dashed; }
.cmp-chip.error { border-color: var(--very-hot); color: var(--very-hot); }

/* Add-location + Refresh sit together; Refresh only shows while changes are pending. */
.cmp-loc-actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.cmp-refresh-btn {
  padding: 11px 18px; border-radius: 10px; cursor: pointer;
  font-weight: 700; font-size: 15px; min-height: 44px;
  background: var(--accent); color: #1a1206; border: 1px solid var(--accent);
}
.cmp-refresh-btn[hidden] { display: none; }
.cmp-refresh-btn:hover { filter: brightness(1.05); }
.cmp-chip-name {
  font-weight: 600; max-width: 44vw;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cmp-chip-x {
  border: none; background: transparent; color: var(--muted); cursor: pointer;
  font-size: 20px; line-height: 1; width: 30px; height: 30px; border-radius: 999px;
}
.cmp-chip-x:hover { color: var(--text); background: var(--border); }

/* Parameter panel: comfort target, band, judged temperature, date range. */
.cmp-params {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 18px 24px; margin: 0 0 22px; padding: 16px;
  border: 1px solid var(--border); border-radius: 12px; background: var(--surface);
}
.cmp-params[hidden] { display: none; }
.cmp-param > label { display: block; font-size: 13px; color: var(--muted); margin: 0 0 8px; }
.cmp-param > label b { color: var(--text); font-size: 15px; }
.cmp-param input[type="range"] {
  width: 100%; height: 44px; margin: 0; accent-color: var(--accent); background: transparent;
}
.cmp-param .hint { display: block; margin: 4px 0 0; font-size: 12px; color: var(--muted); max-width: none; }
.cmp-basis-block .metric-toggle { margin-top: 2px; }
.cmp-basis-block .metric-toggle button { flex: 1 0 auto; padding: 10px 14px; }
.cmp-range { align-content: start; }

/* Ranked result cards. */
.cmp-results { display: grid; gap: 14px; }
.cmp-card { border: 1px solid var(--border); border-radius: 12px; background: var(--surface); padding: 16px 18px; }
.cmp-card-top { display: flex; align-items: center; gap: 12px; }
/* Rank badge: a filled disc — accent for the winner, muted for the rest. */
.cmp-rank {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center; font-size: 13px; font-weight: 800;
  background: var(--surface-2); color: var(--muted);
}
.cmp-card-name { flex: 1 1 auto; min-width: 0; }
/* Name hierarchy: the lead segment bold, the rest (city · region · country) muted
   beneath — both truncate rather than wrap so the card head stays one tidy block. */
.cmp-name { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
/* flex-wrap so the coordinate span (white-space:nowrap below, an unbreakable
   unit) drops to its own line whole when the row is too narrow for it,
   instead of forcing the row -- and the page -- wider. */
.cmp-name-primary { display: flex; align-items: center; gap: 4px; min-width: 0; flex-wrap: wrap; }
.cmp-name b { min-width: 0; font-size: 16px; font-weight: 700; line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Coordinates on the same line as the primary name (issue #52) — sized to
   match .bkm-indicator's own nickname text, but bold like Weekly's own
   .loc-meta coordinate line (a bare <b>, so browser-default bold ≈ 700, not
   .bkm-indicator's 600). A plain class, not a <b>, so it's never caught by
   .cmp-name b above (any nested <b> inherits the primary name's 16px/700,
   which a nested <b> here kept colliding with). */
.cmp-name-coords { font-size: 12.8px; font-weight: 700; line-height: 1.35; color: var(--muted); white-space: nowrap; }
.cmp-name-sub { font-size: 12.5px; color: var(--muted); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmp-daycount { font-size: 12px; color: var(--muted); }
.cmp-big { flex: 0 0 auto; text-align: right; }
.cmp-big b { display: block; font-size: 22px; color: var(--normal); line-height: 1.1; }
.cmp-big span { font-size: 10.5px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }

/* Winner: accent frame + badge, and a "Best match" flag on the subline. */
.cmp-win { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.cmp-win .cmp-rank { background: var(--accent); color: #1a1206; }
.cmp-best { color: var(--accent); font-weight: 700; }

/* Diverging comfort bar: a fixed center marks the comfort zone; colder days grow
   left of it, warmer days grow right. Both halves share one 0–100% scale, so a
   cold-skewed place and a hot-skewed one mirror each other and compare at a glance. */
/* Full-width day-distribution bar: five temperature buckets, cold to hot, together
   100% of the days, in the site's own temperature colors. min-width keeps a thin
   sliver visible; the key below spells out every color and share. */
.cmp-bar { display: flex; height: 20px; border-radius: 6px; overflow: hidden; margin: 14px 0 10px; background: var(--surface-2); }
.cmp-seg { height: 100%; min-width: 2px; }
.cmp-seg-vcold { background: var(--very-cold); }
.cmp-seg-cool { background: var(--cool); }
.cmp-seg-comf { background: var(--normal); }
.cmp-seg-warm { background: var(--warm); }
.cmp-seg-vhot { background: var(--very-hot); }

/* Key under the bar: swatch + plain name + share for every bucket the place has, in
   the same cold-to-hot order as the bar — so a 1% span is named as clearly as a 60% one. */
.cmp-key { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 12.5px; color: var(--muted); }
.cmp-key-item { display: inline-flex; align-items: center; gap: 6px; }
.cmp-key-item b { color: var(--text); font-weight: 700; }
.cmp-sw { width: 12px; height: 12px; border-radius: 3px; flex: 0 0 auto; }
/* Desktop: spread the labels across the bar's width, each centred under its own
   segment; spreadKeys() then de-collides them and keeps the run inside the edges. */
@media (min-width: 641px) {
  .cmp-key { position: relative; display: block; height: 18px; }
  .cmp-key-item { position: absolute; transform: translateX(-50%); white-space: nowrap; }
}

.cmp-bands { margin: 8px 0 0; font-size: 12.5px; color: var(--muted); }
.cmp-bands b { color: var(--text); }

/* Baseline strip: every place's typical basis temperature on one shared °F axis,
   with the comfort range shaded, so the places' baselines line up side by side. */
.cmp-baseline { margin: 0 0 18px; padding: 14px 16px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); }
.cmp-baseline[hidden] { display: none; }
.cmp-bl-head { font-size: 14px; font-weight: 700; }
.cmp-bl-head .hint { display: block; font-weight: 400; font-size: 11.5px; color: var(--muted); margin: 2px 0 12px; max-width: none; }
.cmp-bl-rows { display: grid; gap: 10px; }
.cmp-bl-row { display: grid; grid-template-columns: 120px 1fr; align-items: center; gap: 12px; }
.cmp-bl-name { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmp-bl-track { position: relative; height: 26px; }
.cmp-bl-tol { position: absolute; top: 0; bottom: 0; border-radius: 3px; background: color-mix(in srgb, var(--normal) 9%, transparent); }
.cmp-bl-band {
  position: absolute; top: 0; bottom: 0; border-radius: 3px;
  background: color-mix(in srgb, var(--normal) 20%, transparent);
  outline: 1px solid color-mix(in srgb, var(--normal) 50%, transparent); outline-offset: -1px;
}
.cmp-bl-spread { position: absolute; top: 17px; height: 4px; transform: translateY(-50%); border-radius: 2px; opacity: .6; }
.cmp-bl-dot { position: absolute; top: 17px; width: 12px; height: 12px; border-radius: 50%; transform: translate(-50%, -50%); border: 2px solid var(--surface); }
.cmp-bl-val { position: absolute; top: 1px; transform: translateX(-50%); font-size: 10.5px; font-weight: 700; color: var(--text); white-space: nowrap; }
.cmp-bl-spread.cmp-below, .cmp-bl-dot.cmp-below { background: var(--cold); }
.cmp-bl-spread.cmp-comfort, .cmp-bl-dot.cmp-comfort { background: var(--normal); }
.cmp-bl-spread.cmp-above, .cmp-bl-dot.cmp-above { background: var(--hot); }
.cmp-bl-axis { display: flex; justify-content: space-between; margin: 8px 0 0 132px; font-size: 11px; color: var(--muted); }

/* Loading affordances: a spinning ring (chips + header) and shimmering skeleton
   cards, so a still-loading place is obvious and the results don't pop in cold. */
@keyframes cmp-spin { to { transform: rotate(360deg); } }
@keyframes cmp-shimmer { 100% { background-position: -200% 0; } }
.cmp-spinner {
  display: inline-block; width: 14px; height: 14px; border-radius: 50%; box-sizing: border-box;
  border: 2px solid var(--border); border-top-color: var(--accent);
  animation: cmp-spin .7s linear infinite; vertical-align: -2px;
}
.cmp-spinner-chip { width: 12px; height: 12px; }
.cmp-chip-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--muted); opacity: .55; }
.cmp-loading { display: flex; align-items: center; gap: 8px; margin: 0 0 14px; color: var(--muted); font-size: 14px; }
.cmp-loading-inline { display: inline-flex; align-items: center; gap: 6px; }
.cmp-loading-inline .cmp-spinner { width: 12px; height: 12px; }

.cmp-skel-top { display: flex; align-items: center; gap: 12px; }
.cmp-skel-badge, .cmp-skel-name, .cmp-skel-big, .cmp-skel-bar {
  display: block; border-radius: 6px;
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--border) 37%, var(--surface-2) 63%);
  background-size: 200% 100%; animation: cmp-shimmer 1.3s ease-in-out infinite;
}
.cmp-skel-badge { width: 26px; height: 26px; border-radius: 50%; flex: 0 0 auto; }
.cmp-skel-name { flex: 1 1 auto; height: 16px; }
.cmp-skel-big { width: 56px; height: 22px; flex: 0 0 auto; }
.cmp-skel-bar { height: 16px; margin-top: 16px; }
@media (prefers-reduced-motion: reduce) {
  .cmp-spinner { animation-duration: 1.4s; }
  .cmp-skel-badge, .cmp-skel-name, .cmp-skel-big, .cmp-skel-bar { animation: none; }
}

/* Comfort vs tolerance shown as two separate labels, and a temperature marker above
   each of the four handles (the band borders). */
/* Comfort/tolerance panel — pulled out of the params grid so it stays inline above
   the results (and out of the mobile filter sheet); a full-width bordered card. */
.cmp-comfort-wrap {
  margin: 0 0 18px; padding: 14px 16px 8px;
  border: 1px solid var(--border); border-radius: 12px; background: var(--surface);
}
.cmp-comfort-wrap[hidden] { display: none; }
/* The basis toggle now rides the comfort card (a scoring control, above the results). */
.cmp-comfort-wrap .cmp-basis-block { margin-top: 12px; }
/* Sheet-only duplicate metric selectors: hidden on desktop (the inline copy shows),
   revealed inside the filter sheet on phones (see the max-width:640px block). */
.cmp-dist-metric-block, .cal-metric-sheet { display: none; }
.cal-metric-sheet .cal-filter-label { display: block; margin: 0 0 8px; }
.cmp-range-labels { display: flex; flex-wrap: wrap; gap: 4px 18px; margin: 0 0 6px; font-size: 13px; color: var(--muted); }
.cmp-rl b { color: var(--text); font-size: 15px; margin-left: 2px; }
.cmp-rl-comfort b { color: var(--accent); }
.cmp-ticks { position: relative; height: 17px; }
.cmp-tick { position: absolute; transform: translateX(-50%); font-size: 11px; font-weight: 700; color: var(--text); white-space: nowrap; }
.cmp-tick.tol { color: var(--muted); font-weight: 600; }

/* Dual-handle comfort slicer: two range inputs overlaid on a shared track, with a
   fill marking the selected span. Only the thumbs take pointer events, so both
   handles stay grabbable even where the inputs overlap. */
.dual-range { position: relative; height: 44px; }
.dual-range .dual-track {
  position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%);
  height: 6px; border-radius: 3px; background: var(--surface-2);
}
.dual-range .dual-fill { position: absolute; top: 0; bottom: 0; border-radius: 3px; background: var(--accent); }
.dual-range input[type="range"] {
  position: absolute; left: 0; top: 0; width: 100%; height: 100%; margin: 0;
  -webkit-appearance: none; appearance: none; background: transparent; pointer-events: none;
}
.dual-range input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; pointer-events: auto;
  width: 22px; height: 22px; border-radius: 50%; background: var(--accent);
  border: 2px solid var(--surface); box-shadow: 0 1px 3px rgba(0, 0, 0, .35); cursor: pointer;
}
.dual-range input[type="range"]::-moz-range-thumb {
  pointer-events: auto; width: 22px; height: 22px; border-radius: 50%; background: var(--accent);
  border: 2px solid var(--surface); box-shadow: 0 1px 3px rgba(0, 0, 0, .35); cursor: pointer;
}
.dual-range input[type="range"]::-webkit-slider-runnable-track { background: transparent; border: none; }
.dual-range input[type="range"]::-moz-range-track { background: transparent; border: none; }

/* Quad-handle variant: an outer tolerance range around the inner comfort range on
   the same track. The tolerance fill sits behind the comfort fill; tolerance thumbs
   are hollow (a ring) so they read as secondary, and the comfort thumbs stack above
   them so the inner handles always win where two thumbs overlap. */
.quad-range .dual-fill-tol { background: color-mix(in srgb, var(--accent) 28%, transparent); }
.quad-range #cmp-lo, .quad-range #cmp-hi { z-index: 2; }
.quad-range input.thumb-tol::-webkit-slider-thumb { width: 18px; height: 18px; background: var(--surface); border: 2px solid var(--accent); }
.quad-range input.thumb-tol::-moz-range-thumb { width: 18px; height: 18px; background: var(--surface); border: 2px solid var(--accent); }

/* Climate distribution below the ranked cards: a metric toggle + share/count toggle,
   then one calendar-style distribution strip (.ct-strip, shared) per location. */
.cmp-dist { margin: 24px 0 8px; }
.cmp-dist[hidden] { display: none; }
.cmp-dist .ct-head { align-items: center; }
.cmp-dist-controls { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px; }
.cmp-dist-controls .cal-filter-label { margin: 0; }
.cmp-dist .metric-toggle { margin: 0; }
/* minmax(0,1fr) + the row's min-width:0 let the track shrink below the strip's
   min-content, so the connected bar chart scrolls inside its own .ct-strip
   (overflow-x:auto) instead of widening the whole page on a phone. */
.cmp-dist-body { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; margin-top: 14px; }
.cmp-dist-row {
  min-width: 0;
  border: 1px solid var(--border); border-radius: 12px; background: var(--surface); padding: 12px 16px 6px;
}
.cmp-dist-name { display: flex; align-items: baseline; gap: 8px; font-size: 14px; font-weight: 700; }
.cmp-dist-days { font-size: 12px; font-weight: 400; color: var(--muted); }

/* --- phones / narrow screens — every mobile override lives in this one
   block (they used to be scattered across four) --- */
@media (max-width: 640px) {
  .calendar { gap: 16px; }
  .metric-toggle { width: 100%; }
  /* 7 metrics wrap to ~4 per row on a phone instead of overflowing one line. */
  .metric-toggle button { flex: 1 0 25%; padding: 11px 4px; font-size: 12px; }
  /* Compress the day tooltip so it fits narrow phone screens without cutting off. */
  #cal-tip { padding: 7px 9px; min-width: 0; font-size: 11.5px; line-height: 1.4; }
  #cal-tip .tt-type { font-size: 13.5px; margin: 1px 0 5px; padding-bottom: 5px; }
  #cal-tip .tt-grid { column-gap: 8px; row-gap: 2px; }
  /* Day ladder on mobile: tighten the fixed tracks but keep every column (including
     the percentile range) so the ranges still line up down the ladder. Slack moved
     from the pct/marker tracks to the value column keeps the longest range
     ("17.3–19.9 g/m³", ~88px) on one line down to ~375px screens; narrower than
     that it wraps at the unit. The label track can't give ("Above Normal" ≈ 89px). */
  .day-nav { width: 100%; }
  .day-date { flex: 1; }
  .ladder-row { grid-template-columns: 13px 90px 46px 1fr 42px; column-gap: 8px; font-size: 12.5px; }
}

/* The full inline header (logo + name + °F/°C + theme + Sign in + seven nav tabs)
   only fits comfortably on a wide screen. So below 1200px the nav, °F/°C, theme
   and account fold into a hamburger dropdown, leaving a clean header (logo +
   name + hamburger). The body keeps its ≤640px rules.

   The fold used to happen at 1080px. That left 1081–1199px inline with no slack —
   the wordmark already overflowed its box by ~15px there before the theme button
   existed, and the button's 40px made it unmissable. This breakpoint predates the
   taglines' removal and outlived it: the control row, not the tagline, is what
   actually runs out of width, so the threshold stands on its own. */
@media (max-width: 1199px) {
  /* No header padding override here. It used to tighten the sides to 16px at
     this breakpoint, which put a visible step in the lockup's inset exactly
     where the nav swaps for the hamburger, and left the header out of line with
     `main` (24px) for the whole 641–1199 band. The sides now follow the content
     column instead: 24px down to 640px, 14px below that. */
  h1 { font-size: 19px; }

  /* One compact row: logo + name on the left, the hamburger flush-right. The nav,
     °F/°C toggle, theme button, bell and account all fold into the hamburger
     dropdown below, so the header never grows a second control-bar row. */
  /* The hamburger is fixed (out of flow) — reserve its corner so the lockup
     can't slide beneath it. */
  .brand { flex-wrap: nowrap; align-items: center; gap: 10px; padding-right: 56px; }
  .brand > div:not(.unit-toggle):not(.acct) { flex: 1 1 auto; min-width: 0; padding-right: 0; }
  .logo { align-self: center; margin-top: 0; }

  /* A real box again (not display:contents) so it anchors the absolute dropdown
     and holds the hamburger — fixed to the viewport's top-right so nav follows
     the user down the page. Below the filter-sheet scrim (950) and the map
     modal (1000) so overlays still cover it. */
  /* Inset and centred against the header, not eyeballed. right:24px is the
     header's own side padding, so the hamburger sits exactly as far from the
     edge as the brand mark does on the left — and, because the inline control
     row ends on that same 24px, the right-hand inset does not move when the nav
     swaps for the hamburger. The 640px block moves both together.

     top:19px centres the 44px button on the 46px brand row (18px of header
     padding + the 1px the button is shorter than the row), so it lines up with
     the wordmark instead of riding high. It stays FIXED rather than absolute:
     the point is that it follows the reader down the page, so these numbers
     align it with the header at rest, which is when it is seen beside it. */
  .nav-menu { display: inline-flex; position: fixed; top: 19px; right: 24px; z-index: 940; }
  .nav-toggle {
    display: inline-flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; border: 1px solid var(--border); border-radius: 12px;
    background: var(--surface-2); color: var(--text);
    box-shadow: 0 2px 10px rgba(0, 0, 0, .25);
  }
  .nav-menu[open] .nav-toggle { border-color: var(--accent); color: var(--accent); }

  /* The panel is the dropdown: right-anchored and clamped to the viewport so it
     never runs off-screen (this also fixes the SEO/city pages, whose only panel
     item is the nav). Contents stack nav → °F/°C → account top-to-bottom. */
  .nav-panel {
    display: flex; flex-direction: column; align-items: stretch; gap: 6px;
    position: absolute; right: 0; top: calc(100% + 8px); z-index: 60;
    margin: 0; padding: 8px; width: min(208px, calc(100vw - 32px));
    background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
    box-shadow: 0 16px 40px rgba(0, 0, 0, .45);
  }
  .nav-menu:not([open]) .nav-panel { display: none; }
  /* The dropdown stacks top-to-bottom in the same order the desktop cluster
     reads left-to-right, so the order values from the base block stand as-is. */

  /* Nav links: a compact, left-aligned list with an inset hairline between each
     entry (suppressed around the active pill so nothing crosses it). The whole
     group is hairline-separated from the controls below — the :not(:last-child)
     keeps SEO pages, which have nav only, divider-free. */
  .view-nav {
    display: flex; flex-direction: column; align-items: stretch; align-self: stretch;
    gap: 0; margin: 0; width: auto;
    background: transparent; border: 0; border-radius: 0; padding: 0;
  }
  .nav-panel > .view-nav:not(:last-child) {
    padding-bottom: 6px; margin-bottom: 4px; border-bottom: 1px solid var(--border);
  }
  .nav-panel .view-nav a {
    min-height: 40px; padding: 8px 12px; justify-content: flex-start;
    border-radius: 8px; position: relative;
  }
  .nav-panel .view-nav a + a::before { display: none; }
  .nav-panel .view-nav a:not(:last-child)::after {
    content: ""; position: absolute; left: 12px; right: 12px; bottom: 0;
    height: 1px; background: var(--border);
  }
  .nav-panel .view-nav a.active::after,
  .nav-panel .view-nav a:has(+ a.active)::after { display: none; }

  /* °F/°C toggle: full-width segmented control inside the dropdown. */
  .nav-panel > .unit-toggle { margin: 0; width: 100%; align-self: stretch; }
  .nav-panel > .unit-toggle button { flex: 1; }

  /* Theme button: a labelled row like the nav entries above it, not the square
     icon chip of the desktop header — there is width to spend here, and "Auto"
     is worth spelling out. */
  .nav-panel > .theme-toggle {
    margin: 0; width: 100%; align-self: stretch; justify-content: flex-start;
    height: auto; min-height: 40px; padding: 8px 12px; border-radius: 10px;
  }
  .nav-panel > .theme-toggle .theme-label { display: inline; }

  /* Account row (bell + account/Sign in): a compact chip that hugs its label and
     sits beside the bell, centered in the dropdown, rather than a full-width bar.
     The 40px min-height stays for the tap target; padding and truncation tighten
     so the long email no longer dominates. Popovers open below, clamped to the
     viewport so the notification list never overflows. */
  .nav-panel > .acct { margin: 0; width: 100%; align-self: stretch; justify-content: center; }
  .nav-panel > .acct .acct-btn:not(.notif-btn) { padding: 8px 10px; }
  .nav-panel > .acct .acct-name { max-width: 12ch; }
  /* The bell's notification list would anchor to the small bell button and spill
     off the left edge; drop .notif's positioning context so it anchors to the
     panel instead — dropping below the menu, right-aligned and within the
     viewport. The account menu already fits; just clamp its width. */
  .nav-panel .notif { position: static; }
  .nav-panel .notif-pop { right: 0; width: min(340px, calc(100vw - 24px)); }
  .acct-pop { max-width: calc(100vw - 40px); }
}

@media (max-width: 640px) {
  main { padding: 14px 14px 40px; }
  /* The header tracks the content column's side padding, so the lockup and the
     hamburger stay level with the page below them. Vertical padding is untouched
     — the bar is one height at every width. */
  header { padding: 18px 14px; }
  .nav-menu { right: 14px; }
  .controls { gap: 12px; margin-bottom: 14px; }

  /* Map + panel stack (from the 900px rule); shrink the map so results are
     reachable with less scrolling on a phone. */
  .panel { padding: 16px; }

  /* Give buttons a comfortable touch target (~44px). */
  .date-row button { padding: 12px 16px; min-height: 44px; }
  .share button { padding: 9px 12px; font-size: 12.5px; min-height: 38px; }

  .date-row { gap: 10px; width: 100%; }
  .hint { max-width: none; flex-basis: 100%; }

  .loc-head { flex-wrap: wrap; }
  .share { flex-wrap: wrap; }

  /* Keep the normal cards legible when very narrow. */
  .normals { gap: 8px; }
  .normal-card { padding: 10px 11px; }
  .normal-card .big { font-size: 19px; }

  /* Compact table tuned for phone width. */
  /* Chart metric toggle: force an even 4-per-row grid so the buttons line up
     (without this the flex-wrap sizes each to its label and the rows go ragged). */
  .chart-toggle { width: 100%; }
  .chart-toggle button { flex: 1 0 25%; padding: 9px 4px; font-size: 12px; }
  .rd-c { min-height: 30px; font-size: 11px; border-radius: 5px; }
  /* Smaller label text buys back grid width, so the track it sits in has to come
     down with it — one variable now, rather than the font here and the column
     width in app.js. */
  :root { --rd-label-col: 62px; }
  .rd-mlabel { font-size: 10px; column-gap: 4px; padding-right: calc(4px + var(--rd-gap)); }
  .rd-colh { font-size: 9px; }
  .rd-colh b { font-size: 10.5px; }

  .mp-overlay { padding: 0; }
  /* Full-screen sheet. Size to the *dynamic* viewport (dvh) — plain 100vh on
     iOS Safari is the large-viewport height (toolbars retracted), so with the
     chrome showing the footer's "Use this location" button falls behind it and
     gets cut off. dvh tracks the currently-visible height, and the flex map
     gives up the space so the footer stays on-screen. vh kept as a fallback. */
  .mp-modal {
    max-width: none; border-radius: 0; border: none;
    max-height: 100vh; height: 100vh;
    max-height: 100dvh; height: 100dvh;
  }
  /* Clear the home-indicator inset once the bottom toolbar retracts. */
  .mp-foot { padding-bottom: calc(16px + env(safe-area-inset-bottom)); }
  .find-bar { width: 100%; }
  .guide-metrics { grid-template-columns: 1fr; gap: 2px 0; }
  .guide-metrics dt { margin-top: 10px; }

  /* Tighter distribution columns so a 9-tier temp strip fits a phone without
     scrolling (precip's 10th column still scrolls inside its own .ct-strip).
     The low–high line shrinks to match: at 34px a column has room for "17–62"
     comfortably, and for precip's ".04–.46" once the leading zero is dropped. */
  .ct-col { min-width: 34px; }
  .ct-range { font-size: 8px; }

  .cmp-params { grid-template-columns: 1fr; }
  .cmp-basis-block .metric-toggle button { flex: 1 0 22%; padding: 10px 4px; }
  /* Reveal the synced metric selectors inside the filter sheet on phones. */
  .cmp-dist-metric-block, .cal-metric-sheet { display: block; }

  /* --- filters reachable from anywhere: a floating pill slides the page's filter
     panel up as a bottom sheet (compare #cmp-params, calendar #cal-filters) --- */
  .filter-fab {
    display: none; position: fixed; z-index: 900;
    left: 50%; transform: translateX(-50%);
    bottom: calc(14px + env(safe-area-inset-bottom));
    align-items: center; gap: 7px; padding: 9px 16px; min-height: 40px;
    background: color-mix(in srgb, var(--surface) 92%, transparent);
    color: var(--text); border: 1px solid var(--border); border-radius: 999px;
    box-shadow: 0 6px 20px rgba(0, 0, 0, .38); backdrop-filter: blur(6px);
    font-size: 13px; font-weight: 600; cursor: pointer;
  }
  .filter-fab.ready { display: inline-flex; }
  .filter-fab .ff-ic { width: 16px; height: 16px; }
  .filter-fab .ff-dot {
    width: 8px; height: 8px; border-radius: 50%; background: var(--accent);
    box-shadow: 0 0 0 2px var(--surface);
  }
  .filter-scrim { display: block; position: fixed; inset: 0; z-index: 950; background: rgba(0, 0, 0, .5); }
  .filter-scrim[hidden] { display: none; }

  /* The filter panel becomes a bottom sheet, off-screen until the pill opens it.
     id selectors keep these above the pages' own class-based mobile rules. */
  #cmp-params, #cal-filters {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 960;
    max-width: none; margin: 0; border-radius: 16px 16px 0 0;
    max-height: 85dvh; overflow-y: auto; transform: translateY(110%);
    padding: 16px 16px calc(18px + env(safe-area-inset-bottom));
    box-shadow: 0 -12px 40px rgba(0, 0, 0, .5);
  }
  #cmp-params.open, #cal-filters.open { transform: translateY(0); }

  .filter-sheet-grip {
    display: flex; align-items: center; gap: 10px; position: relative;
    padding: 6px 2px 10px; margin-bottom: 10px; border-bottom: 1px solid var(--border);
  }
  .filter-sheet-grip .fsg-bar {
    position: absolute; top: -6px; left: 50%; transform: translateX(-50%);
    width: 36px; height: 4px; border-radius: 2px; background: var(--border);
  }
  .filter-sheet-grip .fsg-title { font-weight: 700; font-size: 15px; }
  .filter-sheet-grip .fsg-close {
    margin-left: auto; background: transparent; border: none; color: var(--muted);
    font-size: 20px; line-height: 1; min-width: 40px; min-height: 40px; cursor: pointer;
  }
  /* Room so the pill never covers the last bit of content (only on pages with it). */
  body.has-filter-sheet main { padding-bottom: 76px; }
  .cmp-big b { font-size: 20px; }
  /* Baseline strip: tighten the name gutter and shrink text so the shared axis
     still fits a phone; keep the axis ticks aligned to the narrower track. */
  .cmp-bl-row { grid-template-columns: 84px 1fr; gap: 8px; }
  .cmp-bl-name { font-size: 12px; }
  .cmp-bl-axis { margin-left: 92px; }
  .cmp-key { font-size: 11.5px; gap: 5px 12px; }
}

/* Slide the filter sheet only when motion is welcome (phones). */
@media (max-width: 640px) and (prefers-reduced-motion: no-preference) {
  #cmp-params, #cal-filters { transition: transform .28s ease; }
}

/* ============================================================
   Server-rendered content pages (climate hub / city / month /
   records / glossary / about). Text-first, minimal JS.
   ============================================================ */
/* Link colour/decoration for the brand now lives with the lockup rule at the top
   of this file, so it covers the static pages' <h1> form too. */
.site-name { font-size: 20px; margin: 0; }
main.content { max-width: 880px; margin: 0 auto; padding: 8px 20px 56px; line-height: 1.6; }
main.content h1 { font-size: clamp(26px, 4vw, 36px); margin: 8px 0 4px; }
main.content h2 { font-size: 22px; margin: 32px 0 10px; }
main.content p { margin: 10px 0; }
main.content a { color: var(--accent); }
.content .lede { font-size: 18px; color: var(--text); }
.breadcrumb { font-size: 13px; color: var(--muted); margin: 4px 0 12px; }
.breadcrumb a { color: var(--muted); text-decoration: none; }
.breadcrumb a:hover { color: var(--accent); }
.breadcrumb .sep { opacity: .5; }

.content .cta {
  display: inline-block; margin-top: 6px; padding: 11px 18px; border-radius: 10px;
  background: var(--accent); color: #1a1206; font-weight: 700; text-decoration: none;
}
.content .cta:hover { filter: brightness(1.05); }

.today-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; margin: 12px 0; }
.today-card {
  display: flex; flex-direction: column; gap: 3px; padding: 12px 14px;
  background: var(--surface); border: 1px solid var(--border); border-left: 5px solid var(--border);
  border-radius: 10px;
}
.tc-label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .03em; }
.tc-value { font-size: 17px; font-weight: 700; }
.tc-grade { font-size: 12px; color: var(--muted); }

.table-wrap { overflow-x: auto; }
table.normals-table { border-collapse: collapse; width: 100%; margin: 8px 0; font-size: 15px; table-layout: fixed; }
/* Records as color-accented cards: a grid on desktop, single-column (high/low
   stacked vertically) on mobile — no horizontal scroll. */
.records-grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); margin: 14px 0; }
.record-card { border: 1px solid var(--border); background: var(--surface); border-radius: 12px; padding: 12px 14px; }
.rc-metric { margin: 0 0 8px; font-size: 16px; }
.rc-row { display: flex; align-items: baseline; gap: 8px; padding: 6px 0 6px 10px; border-left: 4px solid var(--border); }
.rc-row + .rc-row { margin-top: 4px; }
.rc-high { border-left-color: var(--very-hot); }
.rc-low { border-left-color: var(--very-cold); }
.rc-tag { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; min-width: 62px; flex-shrink: 0; }
.rc-high .rc-tag { color: var(--very-hot); }
.rc-low .rc-tag { color: var(--very-cold); }
.rc-val { font-weight: 700; font-size: 15px; }
.rc-date { color: var(--muted); font-size: 13px; margin-left: auto; white-space: nowrap; padding-left: 6px; }
.records-note { font-size: 13px; margin-top: 12px; }
@media (max-width: 640px) { .records-grid { grid-template-columns: 1fr; } }
table.normals-table th:first-child, table.normals-table td:first-child { width: 26%; }
table.normals-table th, table.normals-table td { padding: 8px 12px; text-align: left; border-bottom: 1px solid var(--border); white-space: nowrap; }
table.normals-table thead th { color: var(--muted); font-weight: 600; font-size: 13px; }
table.normals-table td a { text-decoration: none; }
table.normals-table tbody tr:hover { background: var(--surface-2); }
/* On phones, tighten the climate tables so wide values (3-digit °F with the °C in
   parens) never collide — the records tables are already 3 columns (date folded
   under each value), and this keeps the city's 4-column normals table roomy too. */
/* Month/season records tables: value+date (and season name+span) stack within a
   cell, so top-align keeps the two lines tidy instead of vertically centred. */
table.records-table td { vertical-align: top; }
table.records-table td:first-child { white-space: normal; }

@media (max-width: 560px) {
  table.normals-table { font-size: 13px; }
  table.normals-table th, table.normals-table td { padding: 7px 8px; }
  .rec-on { font-size: 10.5px; }
  /* Stack the "(Dec–Feb)" span under the season name so it can't overflow the
     narrow first column and slide under the tinted value cell. */
  .season-span { display: block; }
}

.climate-records ul { margin: 8px 0; padding-left: 20px; }
.climate-records li { margin: 4px 0; }
.season-span { color: var(--muted); font-weight: 400; font-size: 13px; }

/* Temperature heat-map tints — the interactive tool's diverging cold→hot palette
   brought onto the static tables. The value itself stays in the ink token; the tint
   is a wash behind it, so a record high glows red and a record low reads cold-blue.
   Mix percentages are tuned per tier (saturated darks need less) to keep the number
   legible in both themes; the date column stays muted and untinted. */
td.t-cell { font-weight: 600; }
td.rec-date { color: var(--muted); font-size: 13px; }
/* The date each record occurred, folded under its value so the month/season tables
   stay 3 columns and fit a phone without horizontal scroll. */
.rec-on { display: block; font-size: 11px; font-weight: 400; color: var(--muted); margin-top: 1px; }
/* Month/season records: each metric cell carries two extremes — ▲ warmest and
   ▼ coldest it has ever been — as tinted chips, each with the date it occurred. */
.rec-ext { margin: 4px 0; }
.rec-arrow { display: inline-block; width: 12px; font-size: 10px; }
.rec-arrow.up { color: var(--very-hot); }
.rec-arrow.down { color: var(--very-cold); }
.records-ext .t-inline { font-weight: 700; }
.records-ext .rec-on { margin: 1px 0 2px 12px; font-size: 10.5px; }
.t-rec-cold  { background: color-mix(in srgb, var(--rec-cold) 26%, var(--surface)); }
.t-very-cold { background: color-mix(in srgb, var(--very-cold) 26%, var(--surface)); }
.t-cold      { background: color-mix(in srgb, var(--cold) 28%, var(--surface)); }
.t-cool      { background: color-mix(in srgb, var(--cool) 34%, var(--surface)); }
.t-normal    { background: color-mix(in srgb, var(--normal) 26%, var(--surface)); }
.t-warm      { background: color-mix(in srgb, var(--warm) 40%, var(--surface)); }
.t-hot       { background: color-mix(in srgb, var(--hot) 40%, var(--surface)); }
.t-very-hot  { background: color-mix(in srgb, var(--very-hot) 40%, var(--surface)); }
.t-rec-hot   { background: color-mix(in srgb, var(--rec-hot) 30%, var(--surface)); }
.t-none      { background: transparent; }
/* Inline tinted value (month-page hero numbers, record list) — same washes, padded
   so the colour reads as a chip around the number. */
.t-inline { padding: 1px 7px; border-radius: 7px; }

/* ---- Climate score page ---------------------------------------------------
   Hero (overall score), per-metric cards reuse .normal-card, a by-season chip
   grid, and a button-revealed summary with tinted score tables. All color comes
   from the shared temperature scale via --cat / the .t-* washes. */
.score-hero {
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  padding: 18px 20px; margin: 4px 0 18px;
}
.score-eyebrow { margin: 0 0 8px; }   /* else styled by .section-title */
.score-hero-row { display: flex; align-items: center; gap: 16px; }
.score-num {
  font-size: 52px; font-weight: 800; line-height: 1;
  color: color-mix(in oklab, var(--cat, var(--text)), var(--text) 15%);
}
.score-hero-label { display: flex; flex-direction: column; gap: 4px; }
.score-grade {
  font-size: 18px; font-weight: 700;
  color: color-mix(in oklab, var(--cat, var(--text)), var(--text) 22%);
}
.score-sub { font-size: 13px; color: var(--muted); }
.score-note { font-size: 12px; color: var(--muted); margin: 12px 0 0; line-height: 1.4; }

.score-section { margin: 20px 0; }

/* Score metric cards: name · score · tier · detail, each on its own line so a
   long grade never crams into the corner on a narrow (2-up) phone card. */
.score-card h3 { margin-bottom: 6px; }
.score-card .nc-tier {
  font-size: 12px; font-weight: 700; margin-top: 3px; line-height: 1.25;
  color: color-mix(in oklab, var(--cat, var(--muted)), var(--text) 22%);
}
.score-card .range { margin-top: 1px; }

/* Wet-bulb explainer + heat-stress-day share. */
.score-callout {
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 12px;
  padding: 12px 14px; margin: 14px 0 4px; font-size: 13px; line-height: 1.5; color: var(--text);
}

/* By-season scores: seasons as rows, one uniform column per metric so every
   metric lines up vertically across the seasons. Fixed layout keeps the columns
   equal width; the min-width lets it scroll (not squash) on a phone. */
.season-table { table-layout: fixed; width: 100%; min-width: 620px; max-width: 740px; }
.season-table th:first-child, .season-table td:first-child { width: 62px; text-align: left; }

/* Summary reveal — pill button + caret, mirrors the season-expand pattern. */
.summary-toggle {
  display: inline-flex; align-items: center; gap: 6px; min-height: 44px;
  margin: 8px 0 4px; padding: 8px 16px; border: 1px solid var(--border);
  border-radius: 10px; background: var(--surface); color: var(--text);
  font-size: 15px; font-weight: 600; cursor: pointer;
}
.summary-toggle:hover { border-color: var(--accent); }
.summary-caret { transition: transform .15s ease; }
.summary-toggle[aria-expanded="true"] { border-color: var(--accent); }
.summary-toggle[aria-expanded="true"] .summary-caret { transform: rotate(180deg); }

#score-summary { margin-top: 14px; }
.score-summary-lead { font-size: 14px; color: var(--text); margin: 6px 0; }
.score-sentences { margin: 8px 0 18px; padding-left: 20px; }
.score-sentences li { font-size: 14px; line-height: 1.5; margin-bottom: 8px; color: var(--text); }

#score-body .table-wrap { margin: 8px 0 20px; }   /* .table-wrap supplies overflow-x */
.score-table { border-collapse: collapse; font-size: 13px; }
.score-table th, .score-table td {
  padding: 6px 10px; border-bottom: 1px solid var(--border); text-align: center; white-space: nowrap;
}
.score-table thead th { color: var(--muted); font-weight: 600; }
.score-table tbody th { text-align: left; font-weight: 600; color: var(--text); }
.score-table-overall th, .score-table-overall td {
  border-top: 2px solid var(--border); font-weight: 800;
}
.pq-table td { text-align: left; font-size: 12px; color: var(--muted); }
.pq-d { font-weight: 700; }
.pq-up { color: color-mix(in oklab, var(--hot), var(--text) 30%); }
.pq-down { color: color-mix(in oklab, var(--cool), var(--text) 30%); }

@media (max-width: 640px) {
  .score-num { font-size: 40px; }
  .score-hero { padding: 14px 15px; }
}

/* Monthly temperature-range strip: one gradient bar per month, low→high on a shared
   axis. The signature climate visual — colourful and unmistakably Thermograph. */
.range-strip { display: grid; gap: 6px; margin: 16px 0 8px; max-width: 620px; }
.range-row { display: grid; grid-template-columns: 34px 1fr 92px; align-items: center; gap: 10px; }
.range-month { font-size: 13px; font-weight: 600; color: var(--muted); }
.range-track { position: relative; height: 14px; background: var(--surface-2); border-radius: 999px; }
.range-fill {
  position: absolute; top: 0; bottom: 0; border-radius: 999px;
  background: linear-gradient(90deg, var(--c1), var(--c2));
}
.range-vals { font-size: 13px; color: var(--text); font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
.range-note { font-size: 13px; margin-top: 6px; }
/* Phone: cap the strip so bars + values pack into a tight group on the left
   instead of stretching edge-to-edge, and shrink the values to fit (no reserved
   92px column). Placed after the base rules above so it actually wins. */
@media (max-width: 560px) {
  .range-strip { max-width: 300px; }
  .range-row { grid-template-columns: 26px 1fr auto; gap: 8px; }
  .range-month { font-size: 12px; }
  .range-vals { font-size: 12px; }
}
.climate-foot { font-size: 14px; margin-top: 28px; border-top: 1px solid var(--border); padding-top: 16px; }
.city-blurb { font-size: 15px; margin: 4px 0 14px; }
.blurb-src { color: var(--muted); text-decoration: none; font-size: 13px; white-space: nowrap; }
.blurb-src:hover { color: var(--accent); }
.city-event { border-left: 4px solid var(--accent); background: var(--surface); border-radius: 0 10px 10px 0; padding: 12px 18px; margin: 22px 0; }
.city-event h2 { margin: 2px 0 6px; font-size: 18px; }
.city-event p { margin: 0; }
.city-travel { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px 18px; margin: 28px 0; }
.city-travel h2 { margin-top: 4px; }
/* Month page: the "visiting in <month>?" prompt as a compact callout — text on
   the left, a CTA button on the right (stacks on narrow screens). */
.travel-note {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px 18px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  padding: 14px 18px; margin: 22px 0;
}
.travel-note p { margin: 0; flex: 1 1 240px; }
.travel-note .cta { flex-shrink: 0; margin-top: 0; }
.travel-note { font-size: 15px; }

/* Hub / directory of cities */
/* Collapsible country sections (native <details>). */
/* Hub search box + flat alphabetical results. */
.hub-search { margin: 16px 0 4px; }
.hub-search input {
  width: 100%; padding: 12px 14px; border-radius: 10px; border: 1px solid var(--border);
  background: var(--bg); color: var(--text); font-size: 16px;
}
.hub-search input:focus { outline: none; border-color: var(--accent); }
.hub-results { list-style: none; margin: 8px 0; padding: 0; }
.hub-results[hidden] { display: none; }
.hub-results li { padding: 11px 4px; border-bottom: 1px solid var(--border); display: flex; align-items: baseline; gap: 12px; }
.hub-results li a { text-decoration: none; font-weight: 600; }
.hub-r-country { color: var(--muted); font-size: 13px; margin-left: auto; text-align: right; white-space: nowrap; }
.hub-results .hub-empty { color: var(--muted); }
#hub-directory[hidden] { display: none; }

details.hub-country { border-bottom: 1px solid var(--border); }
details.hub-country > summary {
  cursor: pointer; padding: 12px 4px; font-weight: 700; font-size: 16px;
  list-style: none; display: flex; align-items: center; gap: 8px;
}
details.hub-country > summary::-webkit-details-marker { display: none; }
details.hub-country > summary::before {
  content: "\25B8"; color: var(--muted); transition: transform .15s ease; display: inline-block;
}
details.hub-country[open] > summary::before { transform: rotate(90deg); }
details.hub-country > summary:hover { color: var(--accent); }
.hub-count { margin-left: auto; color: var(--muted); font-weight: 500; font-size: 13px; }
.city-links { display: flex; flex-wrap: wrap; gap: 8px 16px; margin: 0; padding: 2px 4px 16px 22px; list-style: none; }
.city-links a { text-decoration: none; }

/* Glossary / about editorial */
.glossary-term { border-bottom: 1px solid var(--border); padding: 16px 0; }
.glossary-term h2 { margin: 0 0 6px; }

.site-footer {
  max-width: 880px; margin: 0 auto; padding: 24px 20px 48px; border-top: 1px solid var(--border);
  color: var(--muted); font-size: 14px;
}
.site-footer nav { display: flex; flex-wrap: wrap; gap: 8px 20px; margin-bottom: 10px; }
.site-footer a { color: var(--accent); text-decoration: none; }
.site-footer p { margin: 0; }

@media (max-width: 640px) {
  main.content { padding: 8px 14px 40px; }
  .today-cards { grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); }
}

/* ============================================================
   Homepage distribution surfaces: hero, records strip, how it
   works, explore cards, city chips, digest form. Mobile-first;
   the wide-monitor steps at the top of this file widen `main`,
   and these grids flow into whatever room that gives them.
   ============================================================ */

/* --- hero ---------------------------------------------------------------- */
.hero { display: grid; gap: 18px; margin: 8px 0 22px; }
.hero h1 { font-size: clamp(28px, 6vw, 44px); line-height: 1.1; letter-spacing: -0.03em; }
.hero-sub { margin: 10px 0 0; color: var(--muted); font-size: clamp(15px, 2.4vw, 19px); max-width: 46ch; }

/* The grade card is the LCP element. Its three text slots have reserved
   heights so hydrating the live numbers can't shift the page. */
.hero-card { display: grid; gap: 12px; justify-items: start; }
.grade-card {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-left: 4px solid var(--cat, var(--border));
  border-radius: 11px;
  padding: 16px 18px;
  width: 100%;
  max-width: 460px;
}
.grade-where { margin: 0; color: var(--muted); font-size: 13px; min-height: 18px; }
.grade-band {
  margin: 4px 0 0;
  font-size: clamp(26px, 5vw, 34px);
  font-weight: 700;
  letter-spacing: -0.02em;
  min-height: 40px;
  color: color-mix(in oklab, var(--cat, var(--text)) 78%, var(--text));
}
.grade-detail { margin: 6px 0 0; font-size: 15px; min-height: 22px; }
.grade-why { margin: 6px 0 0; font-size: 12px; }

.hero-actions { display: flex; flex-wrap: wrap; gap: 10px; }
.hero-actions button {
  min-height: 44px; padding: 11px 18px; border-radius: 10px;
  font-size: 16px; font-weight: 600; cursor: pointer;
}
.btn-primary { background: var(--accent); color: #1a1206; border: 1px solid var(--accent); }
.btn-ghost { background: var(--surface); color: var(--text); border: 1px solid var(--border); }
.btn-ghost:hover, .btn-primary:hover { border-color: var(--accent); }
.hero-locate-msg { margin: 0; font-size: 13px; color: var(--muted); max-width: 42ch; }
.hero-jump { margin: 0; font-size: 14px; }
.hero-jump a { color: var(--muted); text-decoration: none; }
.hero-jump a:hover { color: var(--accent); }

/* Desktop: copy beside the card rather than stacked above it. */
@media (min-width: 900px) {
  .hero { grid-template-columns: 1.1fr 1fr; align-items: center; gap: 40px; }
}

.stance-line { margin: 14px 0 28px; font-size: 13px; }
.stance-line a { color: var(--muted); }
.stance-line a:hover { color: var(--accent); }

/* --- unusual right now --------------------------------------------------- */
.unusual-strip { margin: 32px 0; }
.unusual-strip h2, .how-it-works h2, .explore h2, .city-hub h2 {
  font-size: 12px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--muted); margin: 0 0 12px;
}
/* Scroll-snap, not a JS carousel: keyboard- and touch-scrollable for free. */
.unusual-row {
  display: flex; gap: 10px; overflow-x: auto; padding: 2px 2px 10px;
  margin: 0; list-style: none; scroll-snap-type: x proximity;
}
.unusual-card { scroll-snap-align: start; flex: 0 0 auto; }
.unusual-card a {
  display: grid; gap: 2px; min-width: 190px; padding: 12px 14px;
  /* A wash of the band colour, so the card reads as its grade at a glance and
     the strip isn't a row of identical grey boxes. */
  background: color-mix(in oklab, var(--cat, var(--surface)) 10%, var(--surface));
  border: 1px solid color-mix(in oklab, var(--cat, var(--border)) 25%, var(--border));
  border-left: 4px solid var(--cat, var(--border));
  border-radius: 11px; text-decoration: none; color: var(--text);
}
.unusual-card a:hover { border-color: var(--accent); }
.unusual-city { font-weight: 600; font-size: 14px; }
/* The reading itself leads: a percentile with no value attached says nothing
   about what the weather actually is. */
/* 50% is the most band colour these can carry and still clear 4.5:1 against the
   tinted card in BOTH schemes — measured across the ramp, worst case being the
   deliberately-dark --rec-cold/--rec-hot "Near Record" tiers. Raising it fails
   dark mode; the band word carries the meaning anyway, colour only reinforces. */
.unusual-value { font-size: 24px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.15;
                 color: color-mix(in oklab, var(--cat, var(--text)) 50%, var(--text)); }
.unusual-band { font-size: 13px; font-weight: 600;
                color: color-mix(in oklab, var(--cat, var(--text)) 50%, var(--text)); }
.unusual-meta { font-size: 12px; color: var(--muted); }
.unusual-more { margin: 2px 0 0; font-size: 14px; }
.unusual-more a { color: var(--accent); text-decoration: none; }

/* --- how it works -------------------------------------------------------- */
.how-it-works { margin: 32px 0; }
.how-steps { margin: 0 0 10px; padding-left: 22px; display: grid; gap: 8px; }
.how-steps li { line-height: 1.45; }
.how-it-works a { color: var(--accent); }

/* --- explore ------------------------------------------------------------- */
.explore { margin: 32px 0; }
.explore-cards { display: grid; gap: 10px; }
@media (min-width: 700px) { .explore-cards { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1400px) { .explore-cards { grid-template-columns: repeat(4, 1fr); } }
.explore-card {
  display: grid; gap: 4px; padding: 14px 16px;
  /* Each card is tinted by its own token so the row carries the product's
     palette instead of reading as four identical grey boxes. */
  background: color-mix(in oklab, var(--cat, var(--surface)) 9%, var(--surface));
  border: 1px solid color-mix(in oklab, var(--cat, var(--border)) 28%, var(--border));
  border-left: 4px solid var(--cat, var(--border));
  border-radius: 11px; text-decoration: none; color: var(--text);
}
.explore-card:hover { border-color: var(--accent); }
.explore-name { font-weight: 600;
                color: color-mix(in oklab, var(--cat, var(--text)) 45%, var(--text)); }
/* The nine grade tiers in order — a miniature of the calendar itself. */
.explore-ramp { display: flex; gap: 2px; margin: 2px 0 1px; }
.explore-ramp i { flex: 1 1 0; height: 6px; border-radius: 2px; }
.explore-desc { color: var(--muted); font-size: 14px; line-height: 1.4; }

/* --- city chips ---------------------------------------------------------- */
.city-hub { margin: 32px 0; }
.city-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 12px; padding: 0; list-style: none; }
.city-chips a {
  display: inline-block; padding: 8px 13px; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--border);
  color: var(--text); text-decoration: none; font-size: 14px;
}
.city-chips a:hover {
  border-color: var(--accent);
  background: color-mix(in oklab, var(--accent) 12%, var(--surface));
}
.city-hub > p a { color: var(--accent); text-decoration: none; }

/* --- digest form (site-wide footer pattern) ------------------------------ */
.digest-form { display: grid; gap: 8px; margin: 0 0 20px; max-width: 480px; }
.digest-form label { font-size: 14px; font-weight: 600; }
.digest-row { display: flex; flex-wrap: wrap; gap: 8px; }
.digest-row input {
  flex: 1 1 200px; min-height: 44px; padding: 10px 12px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; color: var(--text);
  font-size: 16px;   /* hard minimum: smaller makes iOS zoom on focus */
}
.digest-row input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.digest-row button {
  min-height: 44px; padding: 10px 18px; border-radius: 10px;
  background: var(--accent); color: #1a1206; border: 1px solid var(--accent);
  font-size: 16px; font-weight: 600; cursor: pointer;
}
.digest-note { margin: 0; font-size: 12px; }
.digest-status { margin: 0; font-size: 14px; color: var(--accent); }

/* iOS "Add to Home Screen" hint (frontend/ios-install.js) — a slim dismissible
   card pinned above the home indicator, shown only to iOS Safari visitors who
   haven't installed yet. Themed like the rest of the surface chrome. */
.ios-a2hs {
  position: fixed; z-index: 950;
  left: 50%; transform: translateX(-50%);
  bottom: calc(12px + env(safe-area-inset-bottom));
  width: min(440px, calc(100vw - 24px));
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 14px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, .35);
  animation: ios-a2hs-in .25s ease-out;
}
@keyframes ios-a2hs-in { from { opacity: 0; transform: translate(-50%, 12px); } }
.ios-a2hs-share { width: 26px; height: 26px; flex: 0 0 auto; color: var(--accent); }
.ios-a2hs-text { display: flex; flex-direction: column; gap: 2px; font-size: 13px; line-height: 1.3; }
.ios-a2hs-text b { font-weight: 700; }
.ios-a2hs-text span { color: var(--muted); }
.ios-a2hs-x {
  flex: 0 0 auto; margin-left: auto;
  width: 32px; height: 32px; border-radius: 8px;
  background: none; border: none; color: var(--muted);
  font-size: 22px; line-height: 1; cursor: pointer;
}
.ios-a2hs-x:hover { color: var(--text); background: var(--surface-2); }
