/*
 * Utevakt — "Nordic instrument"
 *
 * Norwegian weather UX is built around the horizontal hour strip, so that form is the
 * page's signature: one værstripe component that every theme re-colours by its own
 * threshold. Everything around it stays quiet so the strip carries the boldness.
 *
 * Numbers are the content in a weather instrument, so the type treatment is numeric:
 * tabular figures everywhere values align, big readings set tight, micro-labels tracked
 * wide. No novelty display face — the discipline is in the figures.
 *
 * TOKENS — three layers, and the layering is what makes dark mode possible:
 *
 *   1. Primitives (--grey-*, --teal-*, --amber-*) are raw colours. They never change
 *      between themes and are never used directly in a rule.
 *   2. Semantic tokens (--bg-*, --fg-*, --border-*, --accent*) point at primitives and are
 *      the only colours rules may reference. Dark mode re-points these and nothing else.
 *   3. The Varsom ramp (--fare-*) is neither: it is regulated colour, identical in every
 *      theme, and must never be re-pointed. See the note above it.
 *
 * Type and space scales exist for the sizes that repeat. A handful of optical one-offs
 * (0.35rem, 0.9rem, 1.15rem) stay literal on purpose — forcing them onto the scale would
 * change the layout, and this file's job is to make colour systematic, not to redesign
 * spacing.
 */

:root {
  /* -- primitives ------------------------------------------------------- */
  --white: #ffffff;
  --grey-950: #0f1e2a;      /* fjord dusk */
  --grey-800: #33474f;
  --grey-600: #5c7079;
  --grey-500: #7d949b;
  --grey-400: #b6c6c3;
  --grey-300: #c3d0d3;
  --grey-250: #d3dedc;
  --grey-200: #e4eaea;
  --grey-150: #eef1f0;
  --grey-100: #eef3f2;      /* frost */
  --grey-50: #f2f5f5;

  --teal-700: #0b6e5f;      /* spruce */
  --teal-100: #e0efeb;

  --amber-700: #b4531a;
  --amber-100: #f7e9df;

  /* -- semantic: surfaces and text -------------------------------------- */
  --bg-page: var(--grey-100);
  --bg-surface: var(--white);     /* cards, tabs, chips — the raised plane */
  --bg-raised: var(--white);      /* inputs and light buttons */
  --bg-sunken: var(--grey-150);   /* inert badges: "no", "idle" */
  --bg-hover: var(--grey-50);

  --fg-default: var(--grey-950);
  --fg-soft: var(--grey-800);
  --fg-muted: var(--grey-600);
  --fg-subtle: var(--grey-500);
  --fg-on-accent: var(--white);

  --border-default: var(--grey-250);
  --border-strong: var(--grey-400);

  --accent: var(--teal-700);
  --accent-soft: var(--teal-100);
  --accent-fg: var(--white);
  --focus-ring: var(--accent);

  --warn: var(--amber-700);
  --warn-soft: var(--amber-100);

  /* -- chart ------------------------------------------------------------ */
  /* The plot's own surface and furniture. `--accent` is not listed: it is re-pointed per
     theme further down, which is what keeps each tab distinct while green keeps one
     meaning across all of them. */
  --chart-track: rgb(15 30 42 / 0.05);
  --chart-unknown: var(--grey-300);
  --chart-night: rgb(15 30 42 / 0.06);
  --chart-threshold: var(--grey-600);
  --chart-threshold-fg: var(--fg-muted);
  --chart-now: var(--grey-800);
  --chart-tip-bg: var(--grey-950);
  --chart-tip-fg: var(--white);
  --chart-h: 130px;

  /* -- Varsom avalanche ramp -------------------------------------------- */
  /* Official ramp. Not decorative — do not substitute, and do not "fix" for dark mode:
     the backgrounds are fixed by regulation, so their foregrounds must stay fixed too. */
  --fare-1: #57961c;
  --fare-2: #ffe900;
  --fare-3: #ff9a00;
  --fare-4: #ff0000;
  --fare-5: #000000;
  --fare-0-bg: var(--grey-200);
  --fare-fg-light: #ffffff;
  --fare-fg-2: #1a1a00;
  --fare-fg-3: #331d00;

  /* -- type scale -------------------------------------------------------- */
  --text-2xs: 0.6875rem;
  --text-xs: 0.75rem;
  --text-sm: 0.8125rem;
  --text-md: 0.875rem;
  --text-base: 0.9375rem;
  --text-lg: 1.05rem;
  --text-xl: 1.25rem;
  --text-2xl: 1.5rem;
  --text-3xl: 2rem;

  /* -- space scale ------------------------------------------------------- */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2.5rem;
  --gap: var(--space-4);

  --radius: 4px;
  --wrap: 62rem;
}

/* Night mode re-points semantic tokens only. Primitives keep their meaning, and the Varsom
   ramp is untouched — an avalanche colour that shifts with the user's OS setting would be a
   safety defect, not a style choice.

   `data-theme` is stamped on <html> by a blocking inline script in layout-head.ejs before
   first paint, so there is no flash. Resolving `prefers-color-scheme` there rather than in a
   media query here is what lets an explicit Lys/Mørk choice override the OS — a media query
   cannot be overridden by an attribute without duplicating every token. Without JavaScript
   the attribute stays "light", which is a complete, fully usable experience. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --bg-page: #0c1418;
  --bg-surface: #131e24;
  --bg-raised: #18242b;
  --bg-sunken: #1e2b32;
  --bg-hover: #223037;

  --fg-default: #e6eef0;
  --fg-soft: #c2d0d4;
  --fg-muted: #93a5ab;
  --fg-subtle: #7d949b;

  --border-default: #26343b;
  --border-strong: #3a4c54;

  --accent: #3fbfa5;
  --accent-soft: #14332f;
  --accent-fg: #06231e;

  --warn: #e8853f;
  --warn-soft: #33200f;

  --chart-track: rgb(255 255 255 / 0.05);
  --chart-unknown: #3a4c54;
  /* Night is *lighter* than the plot here: on a dark surface a darker band would read as a
     hole rather than as shading. */
  --chart-night: rgb(255 255 255 / 0.05);
  --chart-threshold: #7d949b;
  --chart-threshold-fg: #93a5ab;
  --chart-now: #c2d0d4;
  --chart-tip-bg: #e6eef0;
  --chart-tip-fg: #0c1418;
  --fare-0-bg: #26343b;
}

:root { color-scheme: light; }

* { box-sizing: border-box; }

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

body {
  margin: 0;
  padding: 0 var(--space-5) 4rem;
  background: var(--bg-page);
  color: var(--fg-default);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  font-size: 16px;
  line-height: 1.5;
}

.wrap { max-width: var(--wrap); margin: 0 auto; }

/* Visually hidden, still announced. Used for icon-only controls and icon+label pairs
   where the label would collide at narrow widths. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------------------------------------------------------------- masthead */

.masthead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--gap);
  flex-wrap: wrap;
  padding: 1.75rem 0 var(--space-5);
  border-bottom: 2px solid var(--fg-default);
  margin-bottom: var(--space-6);
}

.masthead h1 {
  margin: 0;
  font-size: var(--text-2xl);
  font-weight: 700;
  letter-spacing: -0.02em;
}

.masthead h1 span { color: var(--accent); }

/* Wraps because the nav has grown past what a phone width holds on one line: four items
   plus the theme switch overflowed a 413 px viewport and pushed the page sideways. */
.masthead nav {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-4);
}

/* Off-screen until focused, so keyboard users can jump the masthead and tab bar. */
.skip-link {
  position: absolute;
  left: var(--space-2);
  top: -3rem;
  z-index: 10;
  padding: var(--space-2) var(--space-3);
  background: var(--accent);
  color: var(--accent-fg);
  border-radius: var(--radius);
  font-size: var(--text-md);
  font-weight: 600;
  text-decoration: none;
  transition: top 120ms ease;
}

.skip-link:focus { top: var(--space-2); }

/* Three-state segmented control. Sized down so it reads as a setting, not an action. */
.theme-switch {
  display: inline-flex;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-surface);
}

.theme-switch button {
  border: none;
  border-radius: 0;
  background: none;
  color: var(--fg-muted);
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 0.3rem var(--space-2);
  line-height: 1.4;
}

.theme-switch button:hover { background: var(--bg-hover); color: var(--fg-default); filter: none; }
.theme-switch button:focus-visible { outline-offset: -2px; }

.theme-switch button[aria-checked="true"] {
  background: var(--accent);
  color: var(--accent-fg);
}

/* Micro-labels: the small tracked-out uppercase text used for field names and units. */
.eyebrow {
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

/* ---------------------------------------------------------------- tabs */

.tabs {
  display: flex;
  gap: var(--space-1);
  overflow-x: auto;
  border-bottom: 1px solid var(--border-strong);
  margin-bottom: var(--space-6);
  scrollbar-width: thin;
}

.tab {
  flex: 0 0 auto;
  appearance: none;
  border: 1px solid transparent;
  border-bottom: none;
  background: none;
  padding: 0.6rem 0.95rem;
  font: inherit;
  font-size: var(--text-base);
  color: var(--fg-muted);
  cursor: pointer;
  border-radius: var(--radius) var(--radius) 0 0;
  white-space: nowrap;
  margin-bottom: -1px;
}

.tab:hover { color: var(--fg-default); background: var(--bg-hover); }

.tab[aria-selected="true"] {
  background: var(--bg-surface);
  border-color: var(--border-strong);
  border-bottom: 1px solid var(--bg-surface);
  color: var(--fg-default);
  font-weight: 600;
  box-shadow: inset 0 2px 0 var(--accent);
}

.tab:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: -2px;
}

/* Sticky only from tablet up: on a phone the bar eats too much of the viewport. The
   background must be opaque or the scrolled content shows through it.
   Note the noscript rule in layout-head.ejs sets `.tabs { display: none }`, which beats
   sticky outright — so the no-JS path is unaffected by this. */
@media (min-width: 48rem) {
  .tabs {
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--bg-page);
    padding-top: var(--space-2);
  }

  .panel { scroll-margin-top: 4rem; }
}

.panel[hidden] { display: none; }

.panel-head { margin-bottom: var(--space-5); }
.panel-head h2 { margin: 0 0 0.15rem; font-size: var(--text-xl); letter-spacing: -0.01em; }
.panel-head p { margin: 0; color: var(--fg-muted); font-size: var(--text-base); }

/* ---------------------------------------------------------------- theme accents */

/*
 * Each theme owns an accent. It is set on the tab and on the panel via `data-theme-key`, and
 * because `--accent` is inherited, every descendant that already referenced it — the verdict
 * badge, the good bars in the værstripe, the window chips, the best-day row — re-colours for
 * free. No per-theme rules beyond these.
 *
 * Colour lives here rather than in the theme registry precisely because of the night-mode
 * pairs below: the display layer has no business knowing two hex values per theme.
 *
 * Hues are picked for the subject (soil, water, embers, snow) but pulled to a similar
 * lightness so the page keeps one temperature and no tab shouts louder than the others.
 */
[data-theme-key="garden"] { --accent: #2f7d32; --accent-soft: #e2efe1; }
[data-theme-key="running"] { --accent: #0b6e5f; --accent-soft: #e0efeb; }
[data-theme-key="motorcycle"] { --accent: #3f5f8a; --accent-soft: #e4eaf3; }
[data-theme-key="fishing"] { --accent: #1f6f8b; --accent-soft: #e0eef3; }
[data-theme-key="grilling"] { --accent: #a8501d; --accent-soft: #f6e7dc; }
[data-theme-key="drone"] { --accent: #5b5f8c; --accent-soft: #e8e8f2; }
[data-theme-key="photo"] { --accent: #8a5a2b; --accent-soft: #f2e8dc; }
[data-theme-key="avalanche"] { --accent: #4a5f6b; --accent-soft: #e7ecef; }
[data-theme-key="flood"] { --accent: #2a6f97; --accent-soft: #e1edf4; }
[data-theme-key="kayaking"] { --accent: #17706b; --accent-soft: #e0efee; }

/* Dark accents are lifted and desaturated so they clear 4.5:1 on the dark surfaces, and
   --accent-fg flips to a dark ink because the accent is now the light colour. */
:root[data-theme="dark"] [data-theme-key="garden"] { --accent: #6fc36f; --accent-soft: #16301a; --accent-fg: #08210c; }
:root[data-theme="dark"] [data-theme-key="running"] { --accent: #3fbfa5; --accent-soft: #14332f; --accent-fg: #06231e; }
:root[data-theme="dark"] [data-theme-key="motorcycle"] { --accent: #8fb0dd; --accent-soft: #172232; --accent-fg: #0a1522; }
:root[data-theme="dark"] [data-theme-key="fishing"] { --accent: #5fbcd8; --accent-soft: #102a33; --accent-fg: #04202a; }
:root[data-theme="dark"] [data-theme-key="grilling"] { --accent: #e08b53; --accent-soft: #33200f; --accent-fg: #2a1405; }
:root[data-theme="dark"] [data-theme-key="drone"] { --accent: #a6a9dd; --accent-soft: #1d1e2e; --accent-fg: #101120; }
:root[data-theme="dark"] [data-theme-key="photo"] { --accent: #d8a067; --accent-soft: #2c2013; --accent-fg: #221306; }
:root[data-theme="dark"] [data-theme-key="avalanche"] { --accent: #9db4c1; --accent-soft: #1b262c; --accent-fg: #0c161b; }
:root[data-theme="dark"] [data-theme-key="flood"] { --accent: #6fb6dd; --accent-soft: #122733; --accent-fg: #04161f; }
:root[data-theme="dark"] [data-theme-key="kayaking"] { --accent: #4fbcb2; --accent-soft: #10302e; --accent-fg: #042220; }

.theme-icon {
  width: 1.1em;
  height: 1.1em;
  flex: 0 0 auto;
  vertical-align: -0.18em;
  color: var(--accent);
}

.tab { display: inline-flex; align-items: center; gap: 0.4rem; }
/* An unselected tab is quiet: the icon carries the theme colour, the label does not. */
.tab .theme-icon { opacity: 0.75; }
.tab[aria-selected="true"] .theme-icon { opacity: 1; }

.panel-head h2 { display: flex; align-items: center; gap: var(--space-2); }
.panel-head .theme-icon { width: 1.25rem; height: 1.25rem; }

/* The panel accent tints the card's top edge — enough to place the card in its theme
   without colouring the data. */
.panel .card { border-top: 2px solid var(--accent); }

/* ---------------------------------------------------------------- cards */

.card {
  background: var(--bg-surface);
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
  padding: 1.15rem var(--space-5);
  margin-bottom: var(--space-4);
  /* Barely-there lift. A weather instrument should look printed, not stacked. */
  box-shadow: 0 1px 2px rgb(15 30 42 / 0.04);
}

:root[data-theme="dark"] .card { box-shadow: none; }

.card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: 0.9rem;
}

.card-head h3 { margin: 0; font-size: var(--text-lg); letter-spacing: -0.01em; }
/* A place with no position gets no forecast. The mark is the only thing that explains the
   empty card, now that coordinates are never shown. */
.place-warn { color: var(--warn); font-size: var(--text-sm); cursor: help; }

/* The one big number per card — the reading you came to see. */
.readout {
  display: flex;
  align-items: baseline;
  gap: 0.55rem;
  flex-wrap: wrap;
  margin-bottom: 0.4rem;
}

.readout-value {
  font-size: var(--text-3xl);
  font-weight: 650;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}

.readout-unit { font-size: var(--text-base); color: var(--fg-muted); }

.verdict {
  display: inline-block;
  padding: 0.15rem var(--space-2);
  border-radius: var(--radius);
  font-size: var(--text-sm);
  font-weight: 600;
}

.verdict-yes { background: var(--accent-soft); color: var(--accent); }
.verdict-no { background: var(--bg-sunken); color: var(--fg-soft); }
.verdict-idle { background: var(--bg-sunken); color: var(--fg-muted); font-weight: 500; }

/* ---------------------------------------------------------------- now strip */

.now {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-6);
  padding: 0.7rem 0;
  border-top: 1px solid var(--border-default);
  border-bottom: 1px solid var(--border-default);
  margin: 0.9rem 0;
}

.now div { display: flex; flex-direction: column; gap: 0.1rem; }
.now b {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  font-size: var(--text-base);
}

/* The symbol and the temperature are the glanceable pair, so they lead the row and are the
   only part of it set larger than the rest. */
.now-symbol { flex-direction: row !important; align-items: center; gap: var(--space-2); }
.now-symbol span { display: flex; flex-direction: column; gap: 0.1rem; }
.now-symbol img { flex: 0 0 auto; }
.now-temp { font-size: var(--text-xl) !important; letter-spacing: -0.02em; line-height: 1.1; }
.now-symbol-label { font-size: var(--text-sm); color: var(--fg-muted); }

.now .is-warn { color: var(--warn); }

/* Row-height icons inside the day tables. */
.wsym { width: 1.5rem; height: 1.5rem; vertical-align: -0.4rem; }

/* ---------------------------------------------------------------- theme summary */

/* The tab's answer, above the cards. Weighted like a headline because it is one — the cards
   below are the evidence for it. */
.theme-summary {
  border-left: 3px solid var(--border-strong);
  padding: 0.1rem 0 0.1rem var(--space-3);
  margin-bottom: var(--space-4);
}

.theme-summary[data-verdict="yes"] { border-left-color: var(--accent); }

.theme-summary-headline {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--fg-default);
}

.theme-summary[data-verdict="no"] .theme-summary-headline,
.theme-summary[data-verdict="none"] .theme-summary-headline {
  font-weight: 400;
  color: var(--fg-muted);
}

.theme-summary-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem;
  margin: 0.25rem 0 0;
  font-size: var(--text-md);
}

.theme-summary-meta .chip {
  font-size: var(--text-xs);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 22%, transparent);
}

/* ---------------------------------------------------------------- chart */

.chart { margin: var(--space-2) 0 0; }

.chart-caption {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

.chart-unit { font-variant-numeric: tabular-nums; text-transform: none; letter-spacing: 0; }

/* The plot answers "how much"; these chips answer "when", without counting bars. */
.chart-windows {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem;
  margin: 0.3rem 0;
}

.chart-windows .chip {
  font-size: var(--text-xs);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
  color: var(--fg-default);
  background: var(--accent-soft);
  background: color-mix(in srgb, var(--accent) 22%, transparent);
}

/* A two-column grid: the y labels sit outside the plot, so the SVG itself can stretch
   freely without ever scaling a glyph. */
.chart-grid {
  display: grid;
  /* minmax(0, 1fr), not 1fr: a `1fr` track is `minmax(auto, 1fr)`, so it refuses to shrink
     below the plot's min-content width — the hourly cells and their tooltip labels. On a
     phone that pushed the track to ~630px inside a ~385px column and made the whole page
     scroll sideways. */
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 0.4rem;
  align-items: stretch;
}

.chart-y {
  position: relative;
  height: var(--chart-h);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  font-size: var(--text-2xs);
  font-variant-numeric: tabular-nums;
  color: var(--fg-muted);
  text-align: right;
  min-width: 1.75rem;
}

/* Positioned at the threshold's own height rather than spaced evenly, so the number lines
   up with the line it names. */
.chart-y-threshold {
  position: absolute;
  right: 0;
  transform: translateY(-50%);
  color: var(--chart-threshold-fg);
  font-weight: 600;
}

.chart-plot { position: relative; height: var(--chart-h); min-width: 0; }

.chart-plot svg {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--chart-track);
  border-radius: var(--radius);
}

.chart-night { fill: var(--chart-night); }
.chart-band { fill: var(--accent); opacity: 0.1; }

.chart-threshold {
  stroke: var(--chart-threshold);
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
}

.chart-now { stroke: var(--chart-now); stroke-width: 1.5; }

.chart-bar, .chart-tick { fill: var(--fg-soft); }
.chart-bar[data-state="good"], .chart-tick[data-state="good"] { fill: var(--accent); }
.chart-bar[data-state="bad"], .chart-tick[data-state="bad"] { fill: var(--warn); }
.chart-bar[data-state="neutral"], .chart-tick[data-state="neutral"] { fill: var(--fg-subtle); }
/* Not-measured must not look like measured-as-nothing, so a gap is hatched, not blank. */
.chart-bar[data-state="unknown"] { fill: url(#chart-unknown); }

.chart-area { fill: var(--accent); opacity: 0.14; }
.chart-line { stroke: var(--accent); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }

/* The hover layer: transparent buttons over the plot, one per reading. */
.chart-cells {
  position: absolute;
  inset: 0;
  display: flex;
  margin: 0;
  padding: 0;
  list-style: none;
}

.chart-cells li { flex: 1 1 0; display: flex; }

.chart-cell {
  flex: 1;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  position: relative;
  cursor: default;
}

.chart-cell:hover { background: color-mix(in srgb, var(--fg-default) 7%, transparent); filter: none; }

.chart-tip {
  position: absolute;
  bottom: calc(100% + 0.3rem);
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  opacity: 0;
  pointer-events: none;
  white-space: nowrap;
  font-size: var(--text-xs);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  padding: 0.2rem 0.45rem;
  border-radius: var(--radius);
  background: var(--chart-tip-bg);
  color: var(--chart-tip-fg);
  box-shadow: 0 2px 8px rgb(15 30 42 / 0.18);
}

.chart-cell:hover .chart-tip { opacity: 1; }
/* At the edges a centred tooltip would be clipped by the card, so it anchors instead. */
.chart-cell.is-first .chart-tip { left: 0; transform: none; }
.chart-cell.is-last .chart-tip { left: auto; right: 0; transform: none; }

/* A touch device has no hover, so this tooltip can never be shown there — but it is still
   laid out, and a wide one near the right-hand cells pushed the whole page sideways on a
   phone. Removing it costs nothing that device could ever display. */
@media (hover: none) {
  .chart-tip { display: none; }
}

.chart-x {
  grid-column: 2;
  display: flex;
  font-variant-numeric: tabular-nums;
  font-size: var(--text-2xs);
  color: var(--fg-muted);
  margin-top: 0.15rem;
}

.chart-x span { flex: 1 1 0; min-width: 3px; text-align: center; }
/* Only every third hour is labelled; the rest would collide at mobile widths. */
.chart[data-axis="hour"] .chart-x span:not(:nth-child(3n + 1)) { visibility: hidden; }

.chart-legend { margin: 0.35rem 0 0; font-size: var(--text-xs); color: var(--fg-muted); }

.chart-legend-threshold {
  border-left: 2px dashed var(--chart-threshold);
  padding-left: 0.35rem;
  margin-left: 0.15rem;
}

.chart-table { font-size: var(--text-xs); margin-top: 0.2rem; }
.chart-table summary { cursor: pointer; color: var(--fg-muted); }
.chart-table summary:hover { color: var(--fg-default); }
.chart-extremes { font-variant-numeric: tabular-nums; }
.chart-table table { border-collapse: collapse; margin-top: 0.35rem; width: 100%; }
.chart-table caption { text-align: left; color: var(--fg-muted); padding-bottom: 0.2rem; }
.chart-table th, .chart-table td {
  text-align: left;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  padding: 0.15rem 0.5rem 0.15rem 0;
  border-bottom: 1px solid var(--border-default);
}

/* ---------------------------------------------------------------- day table */

table.days {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
  font-size: var(--text-md);
  margin: 0.35rem 0 0.15rem;
}

table.days th {
  text-align: left;
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-muted);
  padding: 0.3rem var(--space-2) 0.3rem 0;
  border-bottom: 1px solid var(--border-default);
}

table.days td {
  padding: 0.35rem var(--space-2) 0.35rem 0;
  border-bottom: 1px solid var(--border-default);
}

table.days tr.is-best td { background: var(--accent-soft); font-weight: 600; }
table.days tr.is-partial td { color: var(--fg-muted); }

/* ---------------------------------------------------------------- avalanche */

/* The Varsom ramp must be the only colour signal in this block, so the theme accent is
   neutralised here rather than trusted not to reach it. */
.fare, .fare-badge { --accent: var(--fg-muted); }

.fare {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.35rem;
}

.fare-badge {
  width: 2.5rem;
  height: 2.5rem;
  display: grid;
  place-items: center;
  border-radius: var(--radius);
  font-size: 1.35rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  border: 1px solid rgb(0 0 0 / 0.18);
}

.fare-1 { background: var(--fare-1); color: var(--fare-fg-light); }
.fare-2 { background: var(--fare-2); color: var(--fare-fg-2); }
.fare-3 { background: var(--fare-3); color: var(--fare-fg-3); }
.fare-4 { background: var(--fare-4); color: var(--fare-fg-light); }
.fare-5 { background: var(--fare-5); color: var(--fare-fg-light); }
.fare-0 { background: var(--fare-0-bg); color: var(--fg-muted); font-size: var(--text-xs); font-weight: 600; }

/* The danger label is a word, not a figure, so it does not get the full readout size. */
.fare-label { font-size: var(--text-lg); }

/* ---------------------------------------------------------- flood/landslide */

/* NVE's flood and landslide scale runs 1-4 and is published in the same regulated green,
   yellow, orange and red as the avalanche ramp. The tokens are reused as-is rather than
   copied to new hexes: two sets of "official" colours that could drift apart is exactly the
   failure mode the ramp exists to prevent. The ramp itself is not modified here. */
.hazard-row { display: flex; flex-wrap: wrap; gap: var(--space-4); margin-bottom: 0.35rem; }
.hazard { --accent: var(--fg-muted); display: inline-flex; align-items: center; gap: 0.6rem; }
.hazard .fare-badge { width: 2.1rem; height: 2.1rem; font-size: 1.1rem; }

.safety {
  border-left: 3px solid var(--warn);
  background: var(--warn-soft);
  padding: 0.6rem 0.8rem;
  font-size: var(--text-sm);
  border-radius: 0 var(--radius) var(--radius) 0;
  margin-top: 0.9rem;
}

/* ---------------------------------------------------------------- forms */

.settings {
  border-top: 1px solid var(--border-default);
  margin-top: var(--space-4);
  padding-top: 0.9rem;
}

.settings summary {
  cursor: pointer;
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

.settings summary:hover { color: var(--fg-default); }
.settings[open] summary { margin-bottom: var(--space-3); }

form.tuning { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: flex-end; }

form.tuning label {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  font-size: var(--text-sm);
  color: var(--fg-soft);
}

input[type="number"], input[type="text"], input[type="email"], input[type="password"], select {
  font: inherit;
  font-size: var(--text-base);
  font-variant-numeric: tabular-nums;
  padding: 0.4rem var(--space-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--bg-raised);
  color: var(--fg-default);
  min-width: 5rem;
}

input:focus-visible, select:focus-visible, button:focus-visible, a:focus-visible,
summary:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 1px;
}

button {
  font: inherit;
  font-size: var(--text-md);
  font-weight: 600;
  padding: 0.45rem 0.9rem;
  border-radius: var(--radius);
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--accent-fg);
  cursor: pointer;
}

button:hover { filter: brightness(1.08); }

button.ghost { background: none; color: var(--fg-soft); border-color: var(--border-strong); }
button.ghost:hover { background: var(--bg-hover); filter: none; }

/* ---------------------------------------------------------------- place combobox */

/* The suggestion list is positioned against this, and it is the widest field in the row:
   the place name is the only thing the user actually has to supply. */
.place-combobox { position: relative; flex: 1 1 18rem; }
.place-combobox input[type="text"] { width: 100%; }

.place-listbox {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 5;
  margin: 0.25rem 0 0;
  padding: 0.25rem;
  list-style: none;
  max-height: 17rem;
  overflow-y: auto;
  background: var(--bg-raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgb(15 30 42 / 0.12);
}

.place-option {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: 0.4rem var(--space-2);
  border-radius: var(--radius);
  cursor: pointer;
}

.place-option:hover,
.place-option[aria-selected="true"] { background: var(--accent-soft); }

.opt-name { font-size: var(--text-base); color: var(--fg-default); }
.opt-meta { font-size: var(--text-sm); color: var(--fg-muted); margin-left: auto; }

.place-chosen:empty { display: none; }

/* The locate button is the fastest path to a usable location, so it is sized and coloured
   to be the obvious thing to press rather than a footnote under the form. */
.locate { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; margin-top: 0.9rem; }

.locate-btn {
  font-size: var(--text-base);
  padding: 0.55rem 1.05rem;
  border-color: var(--border-strong);
  background: var(--bg-raised);
  color: var(--fg-default);
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.06);
}

.locate-btn:hover { background: var(--bg-hover); filter: none; }
.locate-btn[disabled] { opacity: 0.6; cursor: progress; }
.locate [data-locate-status]:empty { display: none; }

.checkbox-row { flex-direction: row !important; align-items: center; gap: 0.4rem !important; }

/* ---------------------------------------------------------------- notifications */

.toasts { display: flex; flex-direction: column; gap: var(--space-2); }
.toasts:empty { display: none; }

.toast {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  background: var(--accent-soft);
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--radius) var(--radius) 0;
  padding: var(--space-2) 0.7rem;
  font-size: var(--text-md);
  margin-bottom: var(--space-2);
}

.toast form { margin-left: auto; }
.toast-error { background: var(--warn-soft); border-left-color: var(--warn); font-weight: 600; }

/* ---------------------------------------------------------------- empty states */

.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-2);
  padding: var(--space-8) var(--space-4);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius);
  background: var(--bg-surface);
  margin-bottom: var(--space-4);
}

.empty h2 { margin: 0; font-size: var(--text-xl); letter-spacing: -0.01em; }
.empty p { margin: 0; max-width: 34rem; }
.empty-icon { width: 2.5rem; height: 2.5rem; color: var(--accent); opacity: 0.8; }

/* ---------------------------------------------------------------- misc */

.note { color: var(--fg-muted); font-size: var(--text-md); }
.error { color: var(--warn); font-weight: 600; }

.card-error {
  background: var(--warn-soft);
  border-left: 3px solid var(--warn);
  padding: var(--space-2) 0.7rem;
  font-size: var(--text-md);
  border-radius: 0 var(--radius) var(--radius) 0;
  margin-bottom: var(--space-3);
}

.places { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-2); }

.place {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  border: 1px solid var(--border-default);
  background: var(--bg-surface);
  border-radius: var(--radius);
  padding: 0.3rem 0.3rem 0.3rem 0.65rem;
  font-size: var(--text-md);
}

.place.is-favourite { border-color: var(--accent); }
.place[draggable="true"] { cursor: grab; }
.place.is-dragging { opacity: 0.45; cursor: grabbing; }

/* The arrow forms are the ordering control without JavaScript; sortable.js stamps the root
   and takes over, so the two controls are never both on screen. */
.place-move { display: inline-flex; gap: 0.1rem; }
.has-drag .place-move { display: none; }

.nudge {
  background: none;
  border: 0;
  color: var(--fg-muted);
  font-size: var(--text-sm);
  padding: 0 0.15rem;
  line-height: 1;
}

.nudge:hover { color: var(--fg-default); filter: none; }

/* The name doubles as the rename control. The marker is dropped so the chip still reads as
   a label rather than as a disclosure widget until you interact with it. */
.place-edit { position: relative; }
.place-edit summary { cursor: text; list-style: none; }
.place-edit summary::-webkit-details-marker { display: none; }
.place-edit summary:hover { text-decoration: underline dotted; }

.place-edit-form {
  position: absolute;
  top: calc(100% + 0.35rem);
  left: -0.4rem;
  /* Above the sticky tab bar (z-index 5). At 4 the bar covered the rename popover once the
     chip row had scrolled under it: the field was visible but the Lagre button was not
     clickable, because the bar was on top of it. */
  z-index: 6;
  display: flex;
  gap: 0.3rem;
  padding: 0.4rem;
  background: var(--bg-raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgb(15 30 42 / 0.12);
}

.place-edit-form input[type="text"] { min-width: 9rem; }

.place .star {
  color: var(--fg-subtle);
  font-size: var(--text-lg);
  padding: 0 0.1rem;
}

.place .star[aria-pressed="true"] { color: var(--accent); }
.place .star:hover { color: var(--accent); filter: none; }

.place form { display: inline; }
.place button {
  background: none;
  border: none;
  color: var(--fg-muted);
  padding: 0.1rem 0.3rem;
  font-size: 1rem;
  line-height: 1;
}
.place button:hover { color: var(--warn); filter: none; }

footer {
  margin-top: var(--space-8);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-default);
  color: var(--fg-muted);
  font-size: var(--text-sm);
}

footer .build {
  margin-top: var(--space-2);
  font-size: var(--text-xs, 0.75rem);
  opacity: 0.8;
}

/* ---------------------------------------------------------------- changelog */

.prose { max-width: 46rem; }
.prose h1 { font-size: 1.5rem; margin-bottom: var(--space-4); }
.prose h2 { font-size: 1.15rem; margin-top: var(--space-6); }
.prose h3 { font-size: 1rem; margin-top: var(--space-4); }
.prose ul { padding-left: 1.2rem; }
.prose li { margin: 0.2rem 0; }
.prose code {
  background: var(--bg-subtle, rgba(127, 127, 127, 0.12));
  padding: 0.05rem 0.3rem;
  border-radius: 4px;
  font-size: 0.9em;
}

/* ---------------------------------------------------------------- auth pages */

.auth { max-width: 22rem; margin: 4rem auto; }
.auth form { display: flex; flex-direction: column; gap: var(--space-3); }
.auth label { display: flex; flex-direction: column; gap: 0.2rem; font-size: var(--text-md); }

/* ---------------------------------------------------------------- responsive */

@media (max-width: 32rem) {
  body { padding: 0 0.85rem 3rem; }
  .readout-value { font-size: 1.6rem; }
  .now { gap: 0 var(--space-4); }
  :root { --chart-h: 104px; }
  form.tuning { gap: var(--space-2); }
  table.days { font-size: var(--text-sm); }

  /* On a phone the cards are tall enough that the tabs scroll away entirely, leaving no way
     to switch theme without scrolling back to the top. */
  .tabs {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--bg-page);
  }

  /* So a #sted- link does not land the card underneath the sticky bar. */
  .card-wrap { scroll-margin-top: 3.5rem; }
}

/* The chart needs horizontal room per hour, so the layout stays single-column and only the
   measure widens. Two columns of cards would squeeze a 24-hour plot below legibility. */
@media (min-width: 64rem) {
  :root { --wrap: 72rem; }
}

/* ---------------------------------------------------------------- motion */

/* Everything here is decoration on top of a page that is already correct without it, which
   is why the reduced-motion block below can switch all of it off outright. */

.chart-bar, .chart-tick {
  animation: chart-rise 0.35s ease-out both;
  transform-origin: center bottom;
}

/* Capped so the last of 24 bars is not still arriving a second after the page settled. */
.chart-bar:nth-child(n + 12), .chart-tick:nth-child(n + 12) { animation-delay: 0.1s; }
.chart-bar:nth-child(n + 20), .chart-tick:nth-child(n + 20) { animation-delay: 0.18s; }

@keyframes chart-rise {
  from { transform: scaleY(0.2); opacity: 0; }
  to { transform: scaleY(1); opacity: 1; }
}

.toast { animation: toast-in 0.25s ease-out both; }

@keyframes toast-in {
  from { transform: translateY(-0.35rem); opacity: 0; }
  to { transform: none; opacity: 1; }
}

.verdict { transition: background-color 0.25s ease, color 0.25s ease; }

/* Marks the card a shared link pointed at, for two seconds — long enough to find it, short
   enough not to become part of the design. */
.card-wrap.is-linked .card {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* The chip for the place that was just added. Same two-second budget and the same reason as
   .is-linked above: point at the new thing, then get out of the way. The animation is
   already covered by the reduced-motion rule at the top of this block. */
.place.is-new {
  border-color: var(--accent);
  animation: place-in 2s ease-out both;
}

@keyframes place-in {
  0% { background: var(--accent-soft); }
  60% { background: var(--accent-soft); }
  100% { background: none; }
}

/* A form waiting on the server. Prevents the second click that used to create the place
   twice, and says why the button stopped responding. */
button[aria-disabled="true"],
button:disabled {
  opacity: 0.6;
  cursor: progress;
}

/* The tab bar scrolls horizontally on a phone. Without this there is nothing on screen to
   say that it does, and the eighth tab simply looks absent. */
.tabs {
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 1.5rem), transparent 100%);
}

@media (min-width: 48rem) {
  /* Everything fits, so the hint would be a permanent fade over a tab that is fully visible. */
  .tabs { mask-image: none; }
}

.tab { scroll-snap-align: start; }

/* ----------------------------------------------------------- alert history */

.alert-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-4) 0;
}

.alert-list { list-style: none; padding: 0; margin: var(--space-4) 0; }

.alert-entry {
  background: var(--bg-surface);
  border: 1px solid var(--border-default);
  /* The theme accent on the leading edge, the same cue the cards use, so a glance down the
     list groups by theme without needing to read the label. */
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  padding: var(--space-4);
  margin-bottom: var(--space-3);
}

.alert-entry-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.alert-message { margin: 0 0 var(--space-2); }

.alert-status {
  margin-left: auto;
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 0.15rem 0.5rem;
  border-radius: var(--radius);
  background: var(--bg-sunken);
  color: var(--fg-muted);
}

/* Sent is the unremarkable case and stays quiet. The two that need a human are not. */
.alert-status-skipped { background: var(--warn-soft); color: var(--warn); }
.alert-status-failed { background: var(--warn-soft); color: var(--warn); }

.watch-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-2) 0 0;
}

/* ------------------------------------------------------------------ pricing */

.plans {
  display: grid;
  /* Collapses to one column on a phone rather than overflowing sideways. */
  grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
  gap: var(--gap);
  margin: var(--space-5) 0;
}

.plan {
  background: var(--bg-surface);
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
  padding: var(--space-5);
}

.plan-current { border-color: var(--accent); }
.plan-price { font-size: var(--text-xl); font-weight: 600; margin: 0.2rem 0 var(--space-3); }
.plan ul { padding-left: 1.2rem; margin: 0 0 var(--space-4); }
.plan li { margin-bottom: var(--space-1); }

.cta-button {
  background: var(--accent);
  color: var(--accent-fg);
  border: 1px solid var(--accent);
  font-weight: 600;
}

/* ------------------------------------------------------------------ landing */

/* Only semantic tokens here, so night mode needs no rules of its own: everything below
   re-points automatically when the tokens do. */

.landing-hero { margin: var(--space-6) 0; }

.landing-hero h2 {
  font-size: var(--text-2xl);
  line-height: 1.25;
  max-width: 30ch;
}

.landing-lead {
  font-size: var(--text-lg);
  color: var(--fg-soft);
  max-width: 62ch;
}

.cta {
  display: inline-block;
  background: var(--accent);
  color: var(--accent-fg);
  padding: 0.7rem 1.2rem;
  border-radius: var(--radius);
  font-weight: 600;
  text-decoration: none;
}

.cta:hover { filter: brightness(1.08); }

.landing-themes,
.landing-honesty {
  background: var(--bg-surface);
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
  padding: var(--space-5);
  margin: var(--space-6) 0;
}

.landing-list {
  list-style: none;
  padding: 0;
  margin: var(--space-4) 0 0;
  display: grid;
  /* auto-fit with a minimum keeps this from ever forcing a horizontal scrollbar: on a narrow
     phone it collapses to one column rather than overflowing. */
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  gap: var(--gap);
}

.landing-list li {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
}

.landing-list .theme-icon {
  width: 1.4rem;
  height: 1.4rem;
  margin-top: 0.15rem;
}

.landing-honesty ul { padding-left: 1.2rem; }
.landing-honesty li { margin-bottom: var(--space-2); max-width: 70ch; }

/* The tab bar is sticky from tablet up, so an element scrolled to by an anchor (or by the
   browser restoring a position) would otherwise come to rest underneath it — visible in the
   viewport but unclickable, because the bar is on top. */
@media (min-width: 48rem) {
  .card-wrap,
  .place,
  .panel {
    scroll-margin-top: 4.5rem;
  }
}
