/* water-system prototype — styled to match the live watts.bar visual language
   (light/paper card, Cormorant Garamond + Montserrat + Oswald, teal water +
   UT-orange accents, smoky-gray ink). Mirrors the site's design tokens so the
   card can slot in as one more lake-hero card. */

:root {
  /* palette (from frontend/styles.css) */
  --ut-orange: #FF8200;
  --ut-orange-text: #b25600;   /* darker burnt-orange for SMALL chart LABELS on white (~5:1 AA). --ut-orange itself is only 2.5:1 at text size — fine for lines/dots (graphical, 3:1) but fails AA as text */
  --water: #008894;
  --water-text: #00747e;        /* darker teal for SMALL text on light: ~5.2:1 (AA). --water itself is 4.23:1, fine for fills/icons/large text but fails AA at body size */
  --water-deep: #0d2e34;
  --moss: #789b48;
  --ink: #4B4B4B;
  --ink-soft: #6a6a6a;
  --ink-faint: #746e5b;        /* #8e8779 -> #797360 -> #746e5b: the faint tier also sits on the tinted flow cards (rgb 244,246,242), where #797360 was only 4.34:1; this clears AA there (~4.6:1) and stays ~5.0:1 on white -- still quieter than --ink-soft (5.4:1), so the hierarchy holds */
  --paper: #dde6ea;            /* light page surface (sky-tint derived) */
  --paper-inset: #ffffff;      /* card surface */
  --water-soft: rgba(0, 136, 148, 0.14);  /* faint teal fill (op-zone band, chip tints) */
  /* one live-status language used everywhere (cam, header chip, dock, lake, birds):
     LIVE (green pulse) -> DELAYED (amber) -> OFFLINE (grey). AA-contrast text. */
  --live-green: #2fa66a; --live-green-text: #1f7d4e; --live-green-soft: rgba(47,166,106,0.13);
  --live-amber: #c77d18; --live-amber-text: #9a6012; --live-amber-soft: rgba(199,125,24,0.14);
  --live-grey:  #9aa0a6; --live-grey-text:  #6a6a6a; --live-grey-soft:  rgba(120,120,120,0.12);
  --rule-faint: rgba(75, 75, 75, 0.22);
  --rule-ghost: rgba(75, 75, 75, 0.10);
  --shadow-light:
      0 1px 2px rgba(75,75,75,0.08),
      0 8px 24px rgba(75,75,75,0.18),
      0 32px 56px rgba(75,75,75,0.10);
  /* fonts (match the site) */
  --display: "Cormorant Garamond", Georgia, serif;
  --body: "Montserrat", system-ui, sans-serif;
  --mono: "Oswald", system-ui, sans-serif;
  /* map surface tones — one green land that bleeds to the card edges */
  --map-bg: #cbd8bc;
  --map-land: #cbd8bc;
  /* match the live watts.bar content column: 1280px max with a gutter that
     steps up at 600/1000px (see frontend/styles.css --gutter / --max-width). */
  --ws-max: 1280px;
  --ws-gutter: max(1rem, env(safe-area-inset-left));
}
@media (min-width: 600px) { :root { --ws-gutter: max(2rem, env(safe-area-inset-left)); } }
@media (min-width: 1000px) { :root { --ws-gutter: 3rem; } }

/* Dark mode — UT palette (orange accent + the teal water identity) on a cool-slate
   dark surface that harmonizes with the dark map land. Designed for AA contrast,
   NOT copied from the live site. Most of the card/strip/chart CSS reads these
   tokens, so overriding them here flips the bulk of the UI; the few hard-coded
   colors get their own dark rules below. */
@media (prefers-color-scheme: dark) {
  :root {
    --ut-orange-text: #ff9a40;  /* on dark slate, brighten the label orange for AA headroom (bright --ut-orange already passes; this keeps labels legible + on-hue) */
    --water: #46bdcc;          /* brighter teal so the water reads on dark */
    --water-text: #5fcad8;     /* small teal text on dark slate (brighter than --water for AA headroom) */
    --water-deep: #2a8c97;
    --moss: #9bbf60;
    --live-green: #46d08a; --live-green-text: #5fd69b; --live-green-soft: rgba(70,208,138,0.18);
    --live-amber: #e0a851; --live-amber-text: #e8b86a; --live-amber-soft: rgba(224,168,81,0.18);
    --live-grey:  #8b9298; --live-grey-text:  #aab0b6; --live-grey-soft:  rgba(170,176,182,0.16);
    --ink: #eef1f3;            /* near-white text (not pure white — less glare) */
    --ink-soft: #c0c6cc;
    --ink-faint: #98a0a8;      /* AA on the raised card surface */
    --paper: #14171a;          /* deep slate page */
    --paper-inset: #232a31;    /* raised card surface */
    --water-soft: rgba(70, 189, 204, 0.18);  /* brighter teal fill so tints read on dark */
    --rule-faint: rgba(255, 255, 255, 0.16);
    --rule-ghost: rgba(255, 255, 255, 0.07);
    --shadow-light:
        0 1px 2px rgba(0,0,0,0.40),
        0 8px 24px rgba(0,0,0,0.46),
        0 32px 56px rgba(0,0,0,0.30);
  }
}

* { box-sizing: border-box; }

/* never allow horizontal scroll (matches the site's mobile behavior) */
/* overflow-x: clip (not hidden) prevents horizontal scroll WITHOUT creating a scroll
   container -- so the sticky site header actually sticks (hidden silently broke it). */
html, body { max-width: 100%; overflow-x: clip; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
  /* plain block layout, centered container (avoids the flex min-width:auto
     overflow trap that let the map push the page wider than the screen). No
     top/bottom padding -- the sticky header sits flush at the very top and the
     footer runs to the page bottom (Eli: killed the odd gaps above nav + below foot). */
  padding: 0;
}
/* same content column as the live site: full width up to 1280px, gutters that
   step at 600/1000px — so the card reflows exactly as it would on watts.bar. */
main { width: 100%; max-width: var(--ws-max); margin: 0 auto; padding: 0 var(--ws-gutter); min-width: 0; }

/* ── LIVE STATUS PILL (one shared language: LIVE / DELAYED / OFFLINE) ─────────── */
.live-pill { display: inline-flex; align-items: center; gap: 0.45em; font-family: var(--mono); text-transform: uppercase; letter-spacing: 0.09em; font-size: 10px; font-weight: 700; line-height: 1; padding: 3px 8px 3px 7px; border-radius: 999px; white-space: nowrap; vertical-align: middle; }
.live-pill-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; position: relative; }
.live-pill[data-live="live"]    { color: var(--live-green-text); background: var(--live-green-soft); }
.live-pill[data-live="delayed"] { color: var(--live-amber-text); background: var(--live-amber-soft); }
.live-pill[data-live="offline"] { color: var(--live-grey-text);  background: var(--live-grey-soft); }
.live-pill[data-live="live"]    .live-pill-dot { background: var(--live-green); }
.live-pill[data-live="delayed"] .live-pill-dot { background: var(--live-amber); }
.live-pill[data-live="offline"] .live-pill-dot { background: var(--live-grey); }
/* the heartbeat: an expanding ring on the LIVE dot only */
.live-pill[data-live="live"] .live-pill-dot::after { content: ""; position: absolute; inset: 0; border-radius: 50%; background: var(--live-green); animation: live-beat 2.6s cubic-bezier(0,0,0.2,1) infinite; }
@keyframes live-beat { 0% { transform: scale(1); opacity: 0.5; } 70%, 100% { transform: scale(3); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .live-pill[data-live="live"] .live-pill-dot::after { animation: none; display: none; } }

/* ── unified horizontal-scroll affordance ─────────────────────────────────────
   One consistent hint across every horizontal carousel on the page (hourly forecast,
   3-day outlook, bird strips) so scrolling reads the same everywhere (Eli). The edge
   nearest more content fades; the fade clears at each end. window.wbScrollHint(el)
   sets data-scroll from scroll position; a partial next item peeking + this fade make
   the scroll obvious. No fade when the content already fits (no data-scroll). */
.wb-hscroll { --wb-fade: 32px; }
/* fade ONLY the RIGHT edge (when there's more to the right) -- never the left, which would
   dim the first item's label, e.g. a bird name (Eli). At the end, no fade at all. */
.wb-hscroll[data-scroll="start"], .wb-hscroll[data-scroll="mid"] { -webkit-mask-image: linear-gradient(to right, #000 calc(100% - var(--wb-fade)), transparent); mask-image: linear-gradient(to right, #000 calc(100% - var(--wb-fade)), transparent); }

/* nothing inside the card may exceed the viewport */
.ws-card, .ws-card * { max-width: 100%; min-width: 0; }
img, svg, canvas { max-width: 100%; }

/* ── the card (matches .lake-hero chrome) ───────────────── */
.ws-card {
  position: relative;
  background: var(--paper-inset);
  border-radius: 4px 4px 14px 14px;
  box-shadow: var(--shadow-light);
  overflow: hidden;
  margin-bottom: 18px;   /* match the other cards (was 0 -> wildlife butted against it) */
}
.ws-card::before {  /* top accent bar, like .lake-hero--water */
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px;
  background: linear-gradient(90deg, transparent, var(--water), transparent);
}

/* card header — title styled to match the live site's section headers
   (.rain-section-title on watts.bar): a layered-water icon + an uppercase,
   letter-spaced label, instead of the old serif hero + duplicate stat line. */
.ws-cardhead { padding: 16px 18px 12px; }
.ws-cardtitle {
  display: inline-flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
  margin: 0; font-family: var(--body); font-weight: 700;
  font-size: clamp(20px, 4.4vw, 26px); letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink); line-height: 1.1;
}
.ws-cardtitle-icon { width: 23px; height: 23px; color: var(--water); flex: 0 0 auto; }

/* ── map ────────────────────────────────────────────────── */
/* full-bleed within the card (no side margin) so the SVG is exactly the card
   width and never overflows/clips on the right */
/* container-query context so labels (and their type) scale with the card width:
   bigger card -> bigger labels, dots, ticks. 1cqi = 1% of the stage width. */
.ws-stage { position: relative; overflow: hidden; container-type: inline-size; }

/* ── Radar overlay (RainViewer tiles reprojected onto the map) ──────────────────
   The layer sits just above the land/water base and below every living element, so
   the flow, dam nodes, and label cards stay crisp on top. Slightly transparent so
   the terrain reads through the rain. */
.ws-radar { opacity: 0.9; pointer-events: none; }   /* opaque enough to read WHERE the rain is (Eli) */
@media (prefers-color-scheme: dark) { .ws-radar { opacity: 0.82; }
  .ws-radar image { mix-blend-mode: screen; } }   /* lift radar off the dark slate */
.ws-radar image { image-rendering: auto; image-rendering: smooth; }
/* toggle + timestamp, parked in the map's top-right corner */
.ws-radar-panel { position: absolute; top: 9px; right: 9px; z-index: 4; display: inline-flex; align-items: center; gap: 8px; pointer-events: none; }
.ws-radar-toggle {
  pointer-events: auto; display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--mono); text-transform: uppercase; letter-spacing: 0.1em; font-size: 11px; font-weight: 600;
  color: var(--ink-soft); background: #ffffff; border: 1px solid var(--rule-faint);
  border-radius: 999px; padding: 5px 11px 5px 9px; cursor: pointer;
  box-shadow: 0 1px 2px rgba(75,75,75,0.10), 0 4px 12px rgba(75,75,75,0.14);
  transition: color .15s ease, border-color .15s ease, box-shadow .15s ease;
}
.ws-radar-toggle svg { width: 14px; height: 14px; }
.ws-radar-toggle:hover { box-shadow: 0 2px 5px rgba(75,75,75,0.18), 0 8px 20px rgba(75,75,75,0.20); }
.ws-radar-toggle:focus-visible { outline: 2px solid var(--water); outline-offset: 2px; }
.ws-radar-panel[data-on] .ws-radar-toggle { color: var(--water-text); border-color: var(--water); }
.ws-radar-stamp { pointer-events: none; font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.04em; color: #fff; background: rgba(13,46,52,0.72); padding: 2px 7px; border-radius: 5px; }  /* 0.62 -> 0.72: the stamp floats over the map, which can carry bright radar cells behind it; the deeper scrim keeps the white time/attribution legible over any terrain (outdoor-glare headroom) */
.ws-radar-stamp:empty { display: none; }
@media (prefers-color-scheme: dark) {
  .ws-radar-toggle { background: #1e242b; color: var(--ink-soft); border-color: var(--rule-faint); }
  .ws-radar-panel[data-on] .ws-radar-toggle { color: var(--water-text); border-color: var(--water); }
}
/* Mobile: the chip rode on top of the top-right Melton Hill card, so move it to
   the open top-left corner (the watershed card was dropped down to clear it). */
@media (max-width: 600px) { .ws-radar-panel { left: 9px; right: auto; } }
@media (max-width: 440px) { .ws-radar-toggle span { display: none; } .ws-radar-toggle { padding: 6px; } .ws-radar-stamp { font-size: 9px; } }
#ws {
  display: block; width: 100%; height: auto; aspect-ratio: 1000 / 700;
  background: var(--map-bg);
}
/* the dot canvas is retired (flow is now SVG pulse-lines); kept in the DOM, hidden */
#ws-canvas { display: none; }
/* top overlay svg: measurement points + leader arrows, above the dot canvas */
.ws-top { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.ws-top .ws-node { pointer-events: auto; }

.ws-node:focus { outline: none; }
.ws-node:focus .ws-node-dot { stroke: var(--water); stroke-width: 2.2px; }

/* curved leader tying each label card to its measurement dot, with an arrowhead */
.ws-leader { stroke: #5d5d5d; stroke-opacity: 0.9; stroke-width: 2.5; vector-effect: non-scaling-stroke; fill: none; stroke-linecap: round; }
/* black casing drawn under each leader (1px proud on each side) so the colored
   shaft stays legible over the map; the arrowhead tips get their own stroke. */
.ws-leader-casing { stroke: #111; stroke-opacity: 0.85; stroke-width: 4.5; vector-effect: non-scaling-stroke; fill: none; stroke-linecap: round; }
/* Mobile: the map is zoomed out, so a non-scaling 2.5px line reads as a hairline.
   Thicken it (and the heads are bumped in JS) so the leaders stay legible. */
@media (max-width: 600px) { .ws-leader { stroke-width: 4; stroke-opacity: 1; } .ws-leader-casing { stroke-width: 6; } }

/* ── card ↔ map link (hover/focus a card to trace it to its place) ──────────── */
.ws-leader { transition: stroke-width .18s ease, stroke-opacity .18s ease; }
.ws-leader-casing { transition: stroke-width .18s ease; }
.ws-leader.is-linked { stroke-width: 4.2; stroke-opacity: 1; }
.ws-leader-casing.is-linked { stroke-width: 7; }
@media (max-width: 600px) { .ws-leader.is-linked { stroke-width: 5.6; } .ws-leader-casing.is-linked { stroke-width: 8; } }
.ws-node-dot, .ws-node-ring { transition: transform .2s cubic-bezier(.22,1,.36,1); transform-box: fill-box; transform-origin: center; }
.ws-node.is-linked .ws-node-dot, .ws-node.is-linked .ws-node-ring { transform: scale(1.32); }
.ws-node.is-linked .ws-node-halo { opacity: 0.5 !important; }
.ws-boatline line { transition: stroke-opacity .2s ease; }
.ws-boatline.is-linked line:first-of-type { stroke-opacity: 0.4; }

/* ── local-watershed link: hovering the card lights + speeds the runoff creeks,
   since the diffuse streams ARE its footprint (it has no single map node). ───── */
.ws-streams.is-linked .ws-stream-flow { opacity: 0.9 !important; stroke-width: 2.9; animation-duration: 1.8s; }
.ws-streams.is-linked .ws-stream-base { stroke: #6f8a5e; stroke-opacity: 0.95; }
.ws-stream-flow, .ws-stream-base { transition: opacity .2s ease, stroke-opacity .2s ease; }

/* ── boat-gate liveness glint (a single bright dash sweeps the counting line) ── */
.ws-boat-shimmer { stroke-opacity: 0.5; stroke-dasharray: 7 110; animation: ws-boatglint 4.2s linear infinite; }
@keyframes ws-boatglint { from { stroke-dashoffset: 60; } to { stroke-dashoffset: -67; } }

/* ── label cards (the data layer; styled like the site's stat chips) ── */
.ws-labels { position: absolute; inset: 0; pointer-events: none; }
.ws-lbl {
  position: absolute; display: flex; flex-direction: column; gap: clamp(1px, 0.3cqi, 4px);
  /* size to the widest line so the nowrap name never clips (the .ws-card *
     min-width:0 would otherwise let it shrink under its content on mobile). */
  width: max-content;
  padding: clamp(4px, 0.9cqi, 9px) clamp(6px, 1.4cqi, 14px) clamp(5px, 1cqi, 10px);
  border-radius: clamp(6px, 0.8cqi, 10px);
  background: rgba(255, 255, 255, 0.94);   /* slightly see-through so the map subtly reads through (Eli likes it) */
  border: 1px solid var(--rule-faint);
  border-left: clamp(3px, 0.5cqi, 5px) solid var(--water);
  box-shadow: 0 1px 2px rgba(75,75,75,0.10), 0 4px 12px rgba(75,75,75,0.14);
  white-space: nowrap;
  /* No backdrop-filter: the card is 0.94 opaque so the blur was invisible, and it
     forced a per-frame GPU composite that costs real frames on low-end Android —
     the exact "weak signal at the lake" device the brief targets. (perf)
     pointer-events:auto re-enables hover here (the .ws-labels layer is none). */
  pointer-events: auto;
  transition: box-shadow .18s cubic-bezier(.22,1,.36,1), border-color .18s ease;
}
.ws-lbl:hover { box-shadow: 0 2px 5px rgba(75,75,75,0.18), 0 10px 24px rgba(75,75,75,0.22); }
.ws-lbl:focus-visible { outline: 2px solid var(--water); outline-offset: 2px; }
/* alignment relative to the leader anchor */
.ws-lbl[data-align="r"] { transform: translate(0, -50%); }
.ws-lbl[data-align="l"] { transform: translate(-100%, -50%); }
.ws-lbl[data-align="t"] { transform: translate(-50%, -100%); }
.ws-lbl[data-align="b"] { transform: translate(-50%, 0); }
.ws-lbl[data-align="c"] { transform: translate(-50%, -50%); }
.ws-lbl--out { border-left-color: var(--ut-orange); }
.ws-lbl--zero { border-left-color: var(--ink-faint); background: rgba(243, 245, 241, 0.94); box-shadow: 0 1px 2px rgba(75,75,75,0.08), 0 3px 9px rgba(75,75,75,0.10); }  /* same see-through as the other cards (was opaque, which made the dry watershed card the odd one out) */
/* type scales with the card width via cqi (1cqi = 1% of stage width), clamped so
   it never gets too small on a phone or too big on a wide desktop. */
.ws-lbl-name {
  font-family: var(--mono); font-weight: 600; font-size: clamp(11px, 1.7cqi, 17px);
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-soft);
}
/* per-card lead icon (dam / gauge / cloud-rain), tinted to the source color in JS.
   Glyphs are drawn to fill the box, so sizing to ~cap-height + a small negative
   baseline shift seats them from the text baseline up to the cap top (topline). */
.ws-lbl-icon { width: 1.12em; height: 1.12em; display: inline-block; vertical-align: -0.22em; margin-right: 0.38em; }
/* value row: direction arrow + big number + a small stacked "CFS" unit */
.ws-lbl-val {
  display: flex; align-items: center; gap: clamp(2px, 0.5cqi, 5px);
  font-family: var(--mono); font-weight: 600; line-height: 1;
  color: var(--ink); font-variant-numeric: tabular-nums;
}
.ws-lbl-num { font-size: clamp(18px, 3cqi, 30px); }
.ws-lbl-arrow { font-size: clamp(16px, 2.5cqi, 26px); font-weight: 700; line-height: 0.85; }
/* "CFS" — small unit beside the number, neutral, not italic */
.ws-lbl-cfs {
  font-style: normal; font-size: clamp(10px, 1.4cqi, 14px); font-weight: 600;
  letter-spacing: 0.04em; color: var(--ink-faint); align-self: flex-end;
  margin-bottom: clamp(1px, 0.4cqi, 4px);
}
/* map-label numbers are small text on a white card (14px on phones); use the AA text
   variants, not the vivid fill colors. The vivid --ut-orange / --water stay on the
   border stripe + arrows-on-map, which is where the source-identity color belongs. */
.ws-lbl--out .ws-lbl-val { color: var(--ut-orange-text); }
.ws-lbl--in:not(.ws-lbl--zero) .ws-lbl-val { color: var(--water-text); }
.ws-lbl--zero .ws-lbl-val { color: var(--ink-faint); }
.ws-lbl-cfs { color: var(--ink-faint); }   /* unit stays neutral, not the category color */
/* hierarchy: outflow + Fort Loudoun read first */
.ws-lbl--out .ws-lbl-num { font-size: clamp(20px, 3.4cqi, 36px); }
.ws-lbl[data-key="fort_loudoun"] .ws-lbl-num { font-size: clamp(19px, 3.1cqi, 33px); }
/* generator lightning bolts inside the label (filled = online) */
.ws-lbl-gens { display: flex; align-items: center; gap: clamp(1px, 0.3cqi, 4px); margin-top: clamp(2px, 0.45cqi, 5px); }
.ws-lbl-gens .ws-bolt { display: inline-flex; width: clamp(13px, 2cqi, 18px); height: clamp(16px, 2.5cqi, 23px); }
.ws-lbl-gens .ws-bolt svg { width: 100%; height: 100%; }
.ws-lbl-gens .ws-bolt svg path { fill: none; stroke: var(--ink-faint); stroke-width: 1.7; stroke-linejoin: round; }
.ws-lbl-gens .ws-bolt.on svg path { fill: var(--moss); stroke: var(--moss); }
.ws-lbl--out .ws-lbl-gens .ws-bolt.on svg path { fill: var(--ut-orange); stroke: var(--ut-orange); }
.ws-lbl-gens u {
  text-decoration: none; font-family: var(--mono); font-size: clamp(7.5px, 1.25cqi, 13px);
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-faint); margin-left: clamp(2px, 0.4cqi, 4px);
}

/* center "Watts Bar Lake" readout panel (the in-map title + live state) */
.ws-lbl--lake { gap: clamp(2px, 0.4cqi, 5px); padding: clamp(6px, 1.2cqi, 13px) clamp(9px, 1.7cqi, 17px); }
.ws-lbl-title {
  font-family: var(--display); font-weight: 600; line-height: 1.0; color: var(--ink);
  font-size: clamp(16px, 3.1cqi, 30px);
}
.ws-lbl-state { font-family: var(--body); font-size: clamp(9px, 1.5cqi, 15px); color: var(--ink-soft); white-space: nowrap; }
.ws-lbl-inflow {
  font-family: var(--mono); font-weight: 600; font-size: clamp(8px, 1.3cqi, 13px);
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--water); white-space: nowrap;
}
/* estimated local watershed — title + reading stay on one line; only the rain
   note wraps (width-capped). */
.ws-lbl--wide { white-space: nowrap; }
/* derived-value method note: a deliberate "derivation" block (a method eyebrow over
   the calculation as a formula), set off from the reading by a hairline — reads as
   intentional, not a placeholder italic line. */
.ws-lbl-note {
  white-space: normal; max-width: clamp(130px, 24cqi, 210px);
  display: flex; flex-direction: column; gap: clamp(1px, 0.3cqi, 3px);
  margin-top: clamp(4px, 0.8cqi, 8px); padding-top: clamp(4px, 0.8cqi, 8px);
  border-top: 1px solid var(--rule-faint); line-height: 1.2;
}
/* Mobile: the cards sit on the open land around the lake, so keep them compact —
   drop the watershed's method note (a desktop-only calc explainer). The boat
   headline ("busier than usual...") STAYS — it's the most useful read. (adapt) */
@media (max-width: 600px) { .ws-lbl .ws-lbl-note { display: none; } }
.ws-est-eyebrow {
  font-family: var(--mono); font-weight: 600; font-size: clamp(7.5px, 1.1cqi, 10px);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-faint);
}
.ws-est-formula {
  font-family: var(--mono); font-weight: 500; font-size: clamp(10px, 1.45cqi, 13px);
  color: var(--ink-soft); font-variant-numeric: tabular-nums; white-space: normal;
}
.ws-est-op { color: var(--water); font-weight: 700; margin: 0 0.18em; }

/* boat-activity counter card (purple; the "how busy" reading, not a water flow) */
.ws-lbl--boat .ws-lbl-val { color: #6B3FA0; flex-wrap: wrap; }
.ws-lbl-boaticon { display: inline-flex; align-self: center; width: clamp(13px, 2cqi, 19px); height: clamp(13px, 2cqi, 19px); color: #6B3FA0; margin-right: clamp(2px, 0.5cqi, 5px); }
.ws-lbl-boaticon svg { width: 100%; height: 100%; display: block; }
.ws-lbl-bandrow { display: flex; align-items: baseline; gap: clamp(4px, 1cqi, 8px); margin-top: clamp(2px, 0.5cqi, 5px); white-space: nowrap; }
.ws-lbl-band { font-family: var(--mono); font-weight: 600; font-size: clamp(8px, 1.3cqi, 12px); letter-spacing: 0.08em; text-transform: uppercase; padding: 1px 7px; border-radius: 9px; }
.ws-band-packed, .ws-band-busy { background: rgba(138,92,194,0.18); color: #5a3691; }
.ws-band-moderate { background: rgba(138,92,194,0.14); color: #5a3691; }
.ws-band-light { background: rgba(0,136,148,0.11); color: var(--water); }
.ws-band-quiet, .ws-band-calm { background: var(--rule-ghost); color: var(--ink-faint); }
.ws-lbl-lasthr { font-family: var(--body); font-size: clamp(9px, 1.4cqi, 13px); color: var(--ink-faint); }
.ws-bl-recent { flex: 0 0 auto; width: 10px; height: 3px; border-radius: 2px; background: #6B3FA0; }
.ws-bl-base-sw { flex: 0 0 auto; width: 10px; height: 0; border-top: 2px dashed var(--ink-faint); }
.ws-bl-dot { color: var(--ink-faint); }
/* simplified boat card: icon left of the title (same height as the title font),
   then count, last-hour, and the headline sentence */
.ws-lbl-name--boat { display: inline-flex; align-items: center; gap: 0.38em; }
.ws-lbl-name--boat svg { width: 1em; height: 1em; color: #6B3FA0; flex: 0 0 auto; }
.ws-boat-lasthr { display: block; font-family: var(--body); font-size: clamp(9.5px, 1.4cqi, 13px); color: var(--ink-soft); margin-top: clamp(1px, 0.3cqi, 3px); white-space: nowrap; }
.ws-boat-headline { display: block; max-width: clamp(150px, 26cqi, 230px); font-family: var(--body); font-size: clamp(10px, 1.45cqi, 13px); color: var(--ink-faint); line-height: 1.3; margin-top: clamp(3px, 0.7cqi, 6px); white-space: normal; }
/* inflow-source cards: share of total inflow (%), set just right of the CFS.
   Muted so the discharge stays the headline; the % is the supporting context. */
/* share of total inflow — sits on the number's baseline (like the CFS unit),
   set off from the value by a thin divider so it reads as a deliberate second
   figure, not a floating afterthought. */
.ws-lbl-pct {
  align-self: flex-end; margin-bottom: clamp(1px, 0.4cqi, 4px);
  margin-left: clamp(5px, 1.2cqi, 9px); padding-left: clamp(5px, 1.2cqi, 9px);
  border-left: 1px solid var(--rule-faint);
  font-family: var(--mono); font-weight: 600; font-size: clamp(11px, 1.7cqi, 15px);
  line-height: 1; color: var(--ink-soft); font-variant-numeric: tabular-nums;
}

/* local-watershed creeks: a teal flow line pulsing toward the lake. Intensity
   (opacity) + speed (duration) are set on the .ws-streams group from live runoff. */
.ws-stream-flow {
  fill: none; stroke-linecap: round;
  stroke-dasharray: 2.5 8;
  opacity: var(--creek-intensity, 0.18);
  animation: ws-creek var(--creek-dur, 3.6s) linear infinite;
  will-change: stroke-dashoffset;
}
@keyframes ws-creek { to { stroke-dashoffset: -21; } }

/* channel flow-line pulse: a WAVE riding the ribbon toward the lake (inflow) /
   off-frame (outflow). Built from three phase-locked dash layers that share a
   LEADING edge but trail back with growing length — a bright rounded front that
   fades to a faint tail (a wave, not a uniform pill). Each layer's dash+gap sums
   to PULSE_PERIOD (30) and the keyframe shifts one full period, so they stay
   locked; the per-layer --o aligns every layer's front to the same point.
   Width/opacity/speed set per channel in flow.js. */
.ws-chan-flow {
  fill: none; stroke-linecap: round; opacity: 0;
  stroke-dasharray: 5 25; --o: -14;            /* bright head */
  animation: ws-chanflow 2s linear infinite;   /* duration overridden per channel */
  will-change: stroke-dashoffset;
}
.ws-chan-flow--t1 { stroke-dasharray: 11 19; --o: -8; }  /* mid wake */
.ws-chan-flow--t2 { stroke-dasharray: 19 11; --o: 0;  }  /* faint tail */
@keyframes ws-chanflow {
  from { stroke-dashoffset: var(--o, 0); }
  to   { stroke-dashoffset: calc(var(--o, 0) - 30); }
}

/* pause all flow animation when the card is offscreen / tab hidden (perf) */
.ws-stage.is-paused .ws-chan-flow,
.ws-stage.is-paused .ws-stream-flow,
.ws-stage.is-paused .ws-boat-shimmer,
.ws-stage.is-paused .ws-node.is-live .ws-node-halo { animation-play-state: paused; }

/* dam node glow + watershed wetness */
.ws-node-halo { transition: opacity .8s ease; }
.ws-node.is-live .ws-node-halo { animation: ws-pulse var(--pulse-dur, 3s) ease-in-out infinite; }
@keyframes ws-pulse {
  0%, 100% { opacity: var(--halo-min, 0.15); transform: scale(1); transform-box: fill-box; transform-origin: center; }
  50%      { opacity: var(--halo-max, 0.5);  transform: scale(1.18); transform-box: fill-box; transform-origin: center; }
}
.ws-ungaged-fill, .ws-ungaged-glow { transition: opacity 1.2s ease; }
/* gentle live "now" ping on the reservoir-elevation today dot -- ties the liveness
   theme to the cam pill + header chip pulses. */
.ws-now-pulse { transform-box: fill-box; transform-origin: center; animation: ws-now-ping 2.6s cubic-bezier(0, 0, 0.2, 1) infinite; }
@keyframes ws-now-ping { 0% { transform: scale(1); opacity: 0.6; } 70%, 100% { transform: scale(2.7); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .ws-now-pulse { animation: none; display: none; } }

/* ── number table (source of truth) ─────────────────────── */
.ws-numbers-wrap { padding: 6px 18px 12px; }
table#ws-numbers {
  width: 100%; border-collapse: collapse;
  font-family: var(--body); font-size: 13px; font-variant-numeric: tabular-nums;
}
#ws-numbers .ws-cap {
  text-align: left; font-family: var(--display); font-weight: 600; font-size: 18px;
  color: var(--ink); padding: 8px 0 6px; border-bottom: 1px solid var(--rule-faint);
}
#ws-numbers .ws-asof { font-family: var(--mono); font-size: 11px; color: var(--ink-faint); letter-spacing: 0.04em; text-transform: uppercase; }
#ws-numbers td, #ws-numbers th { padding: 5px 0; vertical-align: baseline; }
#ws-numbers th {
  text-align: left; font-family: var(--mono); font-weight: 600;
  font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--water); padding-top: 14px;
}
#ws-numbers .ws-head-row th { border-bottom: 1px solid var(--rule-ghost); }
#ws-numbers .ws-num { text-align: right; white-space: nowrap; color: var(--ink); font-family: var(--mono); }
#ws-numbers .ws-num-strong { font-size: 16px; font-weight: 600; color: var(--ink); }
#ws-numbers .ws-now { text-align: right; width: 78px; color: var(--ink-faint); font-size: 11px; padding-left: 8px; font-family: var(--mono); }
#ws-numbers .ws-total td { padding-top: 7px; }
#ws-numbers .ws-src td, #ws-numbers .ws-local td { color: var(--ink-soft); }
#ws-numbers .ws-src-label { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.ws-sub { color: var(--ink-faint); font-size: 11px; }
.ws-tag {
  font-family: var(--mono); font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase;
  padding: 1px 5px; border-radius: 4px; background: rgba(0,136,148,0.12); color: var(--water);
}
.ws-tag-river { background: rgba(120,155,72,0.16); color: var(--moss); }
.ws-band {
  font-family: var(--mono); font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase;
  padding: 1px 6px; border-radius: 999px;
}
.ws-band-high { background: rgba(255,130,0,0.16); color: var(--ut-orange); }
.ws-band-elevated { background: rgba(120,155,72,0.18); color: var(--moss); }
.ws-band-normal { background: rgba(0,136,148,0.14); color: var(--water); }
.ws-band-minimal { background: rgba(75,75,75,0.10); color: var(--ink-faint); }
#ws-numbers .ws-note-row .ws-note { color: var(--ink-faint); font-size: 11px; font-style: italic; padding-top: 2px; }
#ws-foot {
  color: var(--ink-faint); font-size: 10.5px; line-height: 1.5;
  padding: 10px 18px 16px; border-top: 1px solid var(--rule-faint);
}

/* ── fullscreen ─────────────────────────────────────────── */
.ws-card.is-fullscreen {
  position: fixed; inset: 0; z-index: 50; max-width: none; border-radius: 0;
  display: flex; flex-direction: column; background: var(--paper-inset);
}
.ws-card.is-fullscreen .ws-stage { flex: 1; min-height: 0; margin: 0; border-radius: 0; }
.ws-card.is-fullscreen #ws { height: 100%; width: 100%; aspect-ratio: auto; }
.ws-card.is-fullscreen .ws-numbers-wrap { max-width: 560px; margin: 0 auto; width: 100%; }
body.ws-locked { overflow: hidden; }

/* ── node detail popover ────────────────────────────────── */
#ws-pop {
  position: absolute; z-index: 20; min-width: 140px; max-width: 220px;
  background: #ffffffee; border: 1px solid var(--rule-faint);
  border-radius: 10px; padding: 8px 10px; box-shadow: var(--shadow-light);
  pointer-events: none; font-family: var(--mono);
}
#ws-pop .ws-pop-title { font-family: var(--body); font-weight: 700; font-size: 12px; color: var(--water); margin-bottom: 5px; }
#ws-pop table { width: 100%; border-collapse: collapse; font-size: 11.5px; }
#ws-pop td { padding: 2px 0; color: var(--ink-soft); }
#ws-pop td:last-child { text-align: right; color: var(--ink); font-variant-numeric: tabular-nums; }

/* Mobile: narrow the wide watershed card (let its title + rain note wrap) so it
   fits in the upper-left land where Emory used to sit, off the lake. */
@media (max-width: 600px) {
  /* watershed card: the method note is hidden on mobile, so drop the 150px cap
     that was clipping the (now icon-prefixed) name; size to content like the rest. */
  .ws-lbl--wide { white-space: nowrap; }
  .ws-lbl--wide .ws-lbl-name { white-space: nowrap; }
  /* boats card: cap width tight so the headline wraps and the card sits in the
     lower-center land pocket; right-aligned, so a narrower cap pulls the LEFT edge
     off the teal western-arm channel while keeping the right edge clear of Fort. */
  .ws-lbl--boat { max-width: 150px; white-space: normal; }
  .ws-lbl--boat .ws-boat-headline { white-space: normal; }
}
/* Narrow phones (<=440px): the on-map cards bottom out at their min type sizes,
   which take a bigger share of the small map and start to overlap at the positions
   tuned for ~505px. Shrink the card type + padding a notch here so the same layout
   holds; >=505px and desktop are unaffected (they're above this breakpoint). */
@media (max-width: 440px) {
  /* Readability floors for the on-map cards — our readers are older, often in
     reading glasses and bright sun, so the secondary fine print can't drop into
     the 7-8px range. Big number stays the anchor; units/name/boat lines bumped
     ~+2px (the cards absorb it without colliding, verified at 360px). (Eli) */
  .ws-lbl { padding: 3px 5.5px 3.5px; gap: 0; }
  .ws-lbl-name { font-size: 10px; }
  .ws-lbl-num,
  .ws-lbl--out .ws-lbl-num,
  .ws-lbl[data-key="fort_loudoun"] .ws-lbl-num { font-size: 14px; }
  .ws-lbl-arrow { font-size: 12px; }
  .ws-lbl-cfs, .ws-lbl-pct { font-size: 9px; }
  .ws-lbl-gens .ws-bolt { width: 9px; height: 12px; }
  .ws-lbl-gens u { font-size: 9px; }
  .ws-boat-lasthr, .ws-boat-headline { font-size: 9px; line-height: 1.3; margin-top: 2px; }
  .ws-boat-headline { max-width: 116px; }
}

@media (prefers-reduced-motion: reduce) {
  .ws-node.is-live .ws-node-halo { animation: none !important; }
  .ws-stream-flow, .ws-chan-flow { animation: none !important; opacity: 0; }
  /* drop the gate glint; the card↔map link is a state change, not motion, so it stays. */
  .ws-boat-shimmer { animation: none !important; opacity: 0; }
  #ws-canvas { display: none; }
}

/* ── Dark-mode component overrides ───────────────────────────────────────────
   These flip the hard-coded (non-token) colors. Placed at the END of the file so
   they win over the base rules by source order (same specificity). Tokens are set
   in the dark :root block near the top. */
@media (prefers-color-scheme: dark) {
  /* on-map label cards were a near-white wash → dark translucent slate surface */
  .ws-lbl { background: rgba(30, 36, 43, 0.92); }   /* slightly see-through slate (already consistent in dark) */
  .ws-lbl--zero { background: rgba(42, 47, 54, 0.92); }
  /* boat identity purple (#6B3FA0) is too dark on slate; lighten the title/icon +
     value to a brighter purple that meets contrast. The detector line + strip bars
     themselves (#8A5CC2) read fine on dark. */
  .ws-lbl-name--boat svg { color: #bda0ec; }
  .ws-lbl--boat .ws-lbl-val { color: #c4a8ef; }
}


/* --- html[data-night="1"] mirror of the prefers-color-scheme:dark rules above, so the
   site's time-based night theme also darkens this homepage (added at homepage cutover) --- */
html[data-night="1"] {
    --ut-orange-text: #ff9a40;  /* on dark slate, brighten the label orange for AA headroom (bright --ut-orange already passes; this keeps labels legible + on-hue) */
    --water: #46bdcc;          /* brighter teal so the water reads on dark */
    --water-text: #5fcad8;     /* small teal text on dark slate (brighter than --water for AA headroom) */
    --water-deep: #2a8c97;
    --moss: #9bbf60;
    --live-green: #46d08a; --live-green-text: #5fd69b; --live-green-soft: rgba(70,208,138,0.18);
    --live-amber: #e0a851; --live-amber-text: #e8b86a; --live-amber-soft: rgba(224,168,81,0.18);
    --live-grey:  #8b9298; --live-grey-text:  #aab0b6; --live-grey-soft:  rgba(170,176,182,0.16);
    --ink: #eef1f3;            /* near-white text (not pure white — less glare) */
    --ink-soft: #c0c6cc;
    --ink-faint: #98a0a8;      /* AA on the raised card surface */
    --paper: #14171a;          /* deep slate page */
    --paper-inset: #232a31;    /* raised card surface */
    --water-soft: rgba(70, 189, 204, 0.18);  /* brighter teal fill so tints read on dark */
    --rule-faint: rgba(255, 255, 255, 0.16);
    --rule-ghost: rgba(255, 255, 255, 0.07);
    --shadow-light:
        0 1px 2px rgba(0,0,0,0.40),
        0 8px 24px rgba(0,0,0,0.46),
        0 32px 56px rgba(0,0,0,0.30);
  }
html[data-night="1"] .ws-radar { opacity: 0.82; }
html[data-night="1"] .ws-radar image { mix-blend-mode: screen; }
html[data-night="1"] .ws-radar-toggle { background: #1e242b; color: var(--ink-soft); border-color: var(--rule-faint); }
html[data-night="1"] .ws-radar-panel[data-on] .ws-radar-toggle { color: var(--water-text); border-color: var(--water); }
html[data-night="1"] /* on-map label cards were a near-white wash → dark translucent slate surface */
  .ws-lbl { background: rgba(30, 36, 43, 0.92); }
html[data-night="1"] /* slightly see-through slate (already consistent in dark) */
  .ws-lbl--zero { background: rgba(42, 47, 54, 0.92); }
html[data-night="1"] /* boat identity purple (#6B3FA0) is too dark on slate; lighten the title/icon +
     value to a brighter purple that meets contrast. The detector line + strip bars
     themselves (#8A5CC2) read fine on dark. */
  .ws-lbl-name--boat svg { color: #bda0ec; }
html[data-night="1"] .ws-lbl--boat .ws-lbl-val { color: #c4a8ef; }
