/* ============================================================================
   forecast.css — /forecast/* shell. Brandfield per the site default
   (js/brandfield.js injects the defs + instance on this document, which never
   loads home.css); content on cards per the overlay standard.
   ========================================================================== */

:root {
  /* CANDIDATE C — a light neutral gray with more depth than a near-white.
     Chosen over A (#F2F6F8) for SEPARATION: cards are #fff, and C gives a
     1.177 ground-to-card ratio against A's 1.087, so the cards lift instead
     of floating on a surface almost their own value. Muted text still
     clears AA on it (4.65:1). */
  --fcp-ground: #E9EDF1;
}
.fcp-body {
  min-height: 100dvh;
  position: relative;
  background: var(--fcp-ground);
}

/* ⚠ THE BRANDFIELD IS GONE FROM THESE PAGES (pass 8) — the rules that
   painted it here were removed with it; js/brandfield.js itself is untouched
   and still serves the home hero, the recommended section and the overlays.
   See the note at main() in js/forecast.js for the per-surface reasoning.

   THE GROUND IS ONE TOKEN so it can be swapped in a single place:
     --fcp-ground  the page behind the cards
   Pure white was the other half of the flatness: cards were #fff on #fff,
   so nothing lifted and the hierarchy came only from hairline borders. The
   ground now sits a measured step BELOW the cards, which stay white. */
/* .fcp-bar (the name banner) removed — rev C §1. */

/* ⚠ THE FORECAST CONTAINER IS DELIBERATELY WIDE (pass 4 item 4).
   These pages are chart surfaces, not prose: their density is set by how
   many CSS pixels an hour column gets, so container width is the same
   lever as type size. It was 1120px, which at 1440 spent 320px on empty
   background and squeezed 72 hours into 13.75px columns.

   Now: a flush-ish 24px gutter (not truly flush — the cards are bordered
   surfaces and a border touching the viewport edge reads as clipped, and
   the gutter keeps the scrollbar off the content), capped at 1760px so an
   ultrawide monitor cannot stretch the day rail and the description into
   line lengths nobody can track. Above ~1808px the gutters grow again, by
   choice. This is the SHARED forecast shell, so marine and weather get it
   too — same chart-density problem, same fix. The home page is a
   marketing layout with its own proportions and is NOT touched. */
.fcp {
  position: relative;
  z-index: 1;
  max-width: 1760px;
  margin: 0 auto;
  padding: var(--sp-6, 24px) var(--sp-4, 16px) var(--sp-12, 48px);
  display: flex;
  flex-direction: column;
  gap: var(--sp-5, 20px);
}

.fcp-status {
  font: 500 15px/1.5 var(--font-sans, "Inter", system-ui, sans-serif);
  color: var(--text-2, #5B6B7C);
  margin: 0;
}
.fcp-status[hidden] { display: none; }

.fcp-content {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5, 20px);
}

.fcp-head__name {
  font-family: var(--font-sans, "Inter", system-ui, sans-serif);
  font-weight: var(--fw-display, 800);
  font-size: clamp(26px, 5vw, 34px);
  letter-spacing: var(--tracking-tight, -0.02em);
  color: var(--text-1, #16202C);
  margin: 0;
  overflow-wrap: anywhere; /* ?name= is arbitrary text; it wraps, never overflows */
}
.fcp-head__sub {
  font: 400 13px/1.4 var(--font-sans, "Inter", system-ui, sans-serif);
  color: var(--text-2, #5B6B7C);
  margin: 4px 0 0;
  font-variant-numeric: tabular-nums;
}

/* ── Day strip: 7 tappable cards, overlay-standard opaque ─────────────────── */
.fcp-days {
  display: flex;
  gap: var(--sp-2, 8px);
  overflow-x: auto;
  /* the selection box overhangs its cards by 5px + 2px stroke — the rail
     pads for it so the scroll container never clips the outline */
  position: relative;
  padding: 8px 4px;
  /* ⚠ THE BAR IS THE AFFORDANCE, AND IT USED TO BE HIDDEN. This carried
     `scrollbar-width: none` plus a `::-webkit-scrollbar { display: none }` —
     so the rail was scrollable and said nothing about it. Touch found it by
     dragging; a Windows mouse had no way in at all, because there was no bar
     to drag and a wheel over the rail scrolled the PAGE. Same "technically
     scrollable, practically not" shape as the map timeline's 12px lane.

     It stops being a judgement call at a 16-day horizon. Measured on the live
     page 2026-08-29 at a 1440 viewport, #fcp-days on /forecast/marine/41001:
     17 cards, scrollWidth 2078 in clientWidth 1392 — 686px with no way to
     reach it. Overflow is the normal state now, not an edge case.

     `thin` rather than `auto` because the rail is 96px tall and a platform
     scrollbar is ~15px of it. On Windows Chrome draws a CLASSIC bar, so
     `thin` is persistent and draggable — the case this fixes. macOS overlays
     it and fades it when idle, which is the OS convention and not worth
     fighting; the wheel handler in js/forecast.js (enableRailWheel) is the
     other half of this and is what carries the Mac.

     ⚠ THE THUMB IS `--text-2`, NOT A HAIRLINE TOKEN. `--line-strong` is the
     obvious pick and the wrong one: it is rgba(255,255,255,0.102) in dark,
     which reads as an EDGE, not as a control you have to find. A bar nobody
     sees is the bug being fixed, so the thumb takes an ink token — defined in
     both themes (#5B6B7C light, #93A6B6 dark) and legible in both.

     ⚠ THE `::-webkit-scrollbar` RULES BELOW ARE INERT IN MODERN CHROME. They
     are kept for Safari < 18.2, which shipped no `scrollbar-width`. Measured
     2026-08-29 on Chrome 152, a 200px scroller, reading offsetHeight minus
     clientHeight:

         webkit `height: 8px` ALONE                      gutter 8px
         `scrollbar-width: thin` + that same webkit
           block, i.e. exactly what ships here           gutter 0px

     The standard property wins wherever both are understood. So do not try to
     change this rail's bar by editing only the webkit rules and reloading
     Chrome — nothing will move, and the file will read as though it had. */
  scrollbar-width: thin;
  scrollbar-color: var(--text-2, #5B6B7C) transparent;
}
.fcp-days::-webkit-scrollbar { height: 8px; }
.fcp-days::-webkit-scrollbar-track { background: transparent; }
.fcp-days::-webkit-scrollbar-thumb {
  background: var(--text-2, #5B6B7C);
  border-radius: 4px;
}
.fcp-days::-webkit-scrollbar-thumb:hover { background: var(--accent, #12A594); }
/* ⚠ NO COLOUR HERE — IT MUST INHERIT. This set color on the SVG, which beat
   the per-icon colour set on the wrapping span: the span computed #FFCC44 and
   the svg reset itself to --text-1, so every condition icon rendered
   monochrome while looking correctly wired one level up. The sprites stroke
   with currentColor; whatever sets colour must be the nearest ancestor that
   knows the weather code, and that is the span. */
.fcp-day__hi { font-size: 15px; font-weight: 700; color: var(--text-1, #16202C); }
/* A bucket label's "+" ("6-8ft+"): smaller and lighter than the range text,
   on the baseline so no fixed-height reserve grows. One rule for the day
   chip, the conditions card's range and the chart readout's <tspan>; the
   textContent stays "6-8ft+" (see setBucketText in forecast.js). */
.fcp-plus { font-size: 0.72em; font-weight: 500; }

.fcp-sun {
  font: 500 13px/1.4 var(--font-sans, "Inter", system-ui, sans-serif);
  color: var(--text-2, #5B6B7C);
  margin: 0;
}

/* ── Hourly rows: one opaque card, rows inside ────────────────────────────── */
/* ⚠ ROW LISTS DO NOT TAKE THE WIDE CONTAINER (pass 4 item 4).
   The widening is for CHART surfaces, where width buys pixels per hour. A
   row is read the other way — a time on the left paired with its values on
   the right — and at 1350px that pairing breaks: measured on weather, the
   hour sat at x=40 and its wind at x=1180 with nothing between. So row
   lists cap at a reading width and centre; charts and card grids keep the
   full container. Applies to weather (which has no charts at all) and to
   marine's hourly rows and station readings. */
/* width:100% is load-bearing — these are flex items in a column flex
   parent, where `margin-inline: auto` alone cancels the default stretch
   and collapses them to shrink-to-fit (measured: the readings card fell to
   312px). The explicit basis restores full width, max-width caps it, and
   the auto margins then centre what is left. */
.fcp-hours, #fcp-obs {
  width: 100%;
  max-width: 1100px;
  margin-inline: auto;
}
.fcp-hours {
  list-style: none;
  margin: 0 auto;
  padding: var(--sp-2, 8px) var(--sp-4, 16px);
  background: var(--bg, #fff);
  border: 1px solid var(--line, #E3E9F0);
  border-radius: var(--r-lg, 12px);
}

.fcp-pill {
  font: 700 11px/1 var(--font-sans, "Inter", system-ui, sans-serif);
  letter-spacing: 0.03em;
  padding: 4px 9px;
  border-radius: 999px;
  justify-self: end;
  background: var(--surface-sunken, #E8EDF2); /* neutral when band unknown */
  color: var(--text-2, #5B6B7C);
}
.fcp-pill--sm { font-size: 10px; padding: 3px 7px; justify-self: center; }
/* The five marine bands, through the generated app palette.

   ⚠ THE FG IS DEEPENED HERE, IN THE CONSUMING RULE — exactly where
   rating-palette.css says a correction belongs. Several app pairs measure
   4.04-4.40:1, under the 4.5:1 this 10-11px text needs, and axe failed the
   CHOPPY pill on the live page (31 nodes). color-mix toward the dark ink
   raises every pair past 4.5 while the BAND HUE — the identity — stays the
   app's. The generated palette file remains untouched and keeps matching
   the Dart. */

/* ── Fishing cards: bite score + solunar (shots 7-8) ─────────────────────── */
#fcp-fishing { display: flex; flex-direction: column; gap: var(--sp-5, 20px); }

.fcp-card {
  background: var(--bg, #fff);
  border: 1px solid var(--line, #E3E9F0);
  border-radius: var(--r-lg, 12px);
  padding: var(--sp-5, 20px);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3, 12px);
}
.fcp-card__bar { display: flex; justify-content: space-between; align-items: center; }
.fcp-card__title {
  font: 700 12px/1 var(--font-sans, "Inter", system-ui, sans-serif);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-2, #5B6B7C);
  margin: 0;
}
.fcp-moon { width: 20px; height: 20px; }

#fcp-charts { display: flex; flex-direction: column; gap: var(--sp-5, 20px); }
@media (min-width: 1024px) {
  #fcp-current { width: 100%; max-width: 860px; }
}
/* ⚠ PILLS WEAR THE APP'S TINT TREATMENT: pale --rating-*-bg fill, saturated
   --rating-*-fg text. This REVERTS the solid --rating-*-hue fill that pass 7
   introduced, and the reason is worth keeping.

   Solid fills were asked for because the pills "looked grayed out". That
   appearance had a different cause entirely — `filter: grayscale(0.6)` on
   locked cards, and later the locked card's own `opacity: 0.55` — both since
   removed (d088aa7, 9c27829). So the solid fill was a remedy for a fault it
   did not share a cause with, and it brought its own: at 10px the eye
   integrates fill AND ink, so a pill whose fill is exactly #D77E2A measures a
   whole-pill mean of #B9722E — the muddy brown — and POOR/POOR-FAIR, already
   the tightest adjacent pair in the ramp at dE 21.3, compressed further.
   The tint treatment puts the band on the TEXT, where it is saturated against
   a near-white ground and ink coverage cannot mud it.

   Both values are generated from the Dart; the file is never hand-edited.

   CONTRAST, corrected on the TEXT SIDE ONLY (the CHOPPY precedent). Measured
   fg-on-bg for all 13: six clear on their own (poor 5.04, hazardous 5.04,
   epic 5.53, good/calm 4.87, flat 4.75) and seven land 4.04-4.40. Rather
   than seven per-band mixes, ONE blanket deepening covers every band: 91% fg
   + ink is the smallest single mix that takes the worst (good-epic 4.04) to
   4.61, and it leaves the already-passing bands on their own hue, only a
   shade deeper. The FILL is never touched. */
.fcp-pill[data-band] {
  color: color-mix(in srgb, var(--pill-fg) var(--rating-corr-tint, 91%), #0B1420);
  background: var(--pill-bg);
}
.fcp-pill[data-band='flat']      { --pill-bg: var(--rating-flat-bg);      --pill-fg: var(--rating-flat-fg); }
.fcp-pill[data-band='poor']      { --pill-bg: var(--rating-poor-bg);      --pill-fg: var(--rating-poor-fg); }
.fcp-pill[data-band='poor-fair'] { --pill-bg: var(--rating-poor-fair-bg); --pill-fg: var(--rating-poor-fair-fg); }
.fcp-pill[data-band='fair']      { --pill-bg: var(--rating-fair-bg);      --pill-fg: var(--rating-fair-fg); }
.fcp-pill[data-band='fair-good'] { --pill-bg: var(--rating-fair-good-bg); --pill-fg: var(--rating-fair-good-fg); }
.fcp-pill[data-band='good']      { --pill-bg: var(--rating-good-bg);      --pill-fg: var(--rating-good-fg); }
.fcp-pill[data-band='good-epic'] { --pill-bg: var(--rating-good-epic-bg); --pill-fg: var(--rating-good-epic-fg); }
.fcp-pill[data-band='epic']      { --pill-bg: var(--rating-epic-bg);      --pill-fg: var(--rating-epic-fg); }
.fcp-pill[data-band='calm']      { --pill-bg: var(--rating-calm-bg);      --pill-fg: var(--rating-calm-fg); }
.fcp-pill[data-band='moderate']  { --pill-bg: var(--rating-moderate-bg);  --pill-fg: var(--rating-moderate-fg); }
.fcp-pill[data-band='choppy']    { --pill-bg: var(--rating-choppy-bg);    --pill-fg: var(--rating-choppy-fg); }
.fcp-pill[data-band='rough']     { --pill-bg: var(--rating-rough-bg);     --pill-fg: var(--rating-rough-fg); }
.fcp-pill[data-band='hazardous'] { --pill-bg: var(--rating-hazardous-bg); --pill-fg: var(--rating-hazardous-fg); }
/* stack-only labels — sizes set inline by renderSurfStack (note below) */
/* item 3b: night bands behind the plots (last light -> first light) */

/* ── 2a/2b: model toggle, locked days, the Fetch+ card ──────────────────── */
.fcp-model { display: inline-flex; gap: 6px; margin-top: 8px; }
.fcp-model__btn {
  border: 1px solid var(--line, #D6DEE8); background: var(--bg, #fff);
  color: var(--text-2, #5B6B7C); font: 600 13px/1 var(--font-sans, "Inter", system-ui, sans-serif);
  padding: 7px 14px; border-radius: 999px; cursor: pointer;
}
.fcp-model__btn[aria-pressed='true'] {
  background: var(--accent, #12A594); border-color: var(--accent, #12A594); color: #fff;
}
/* ⚠ NO GRAYSCALE ON A LOCKED PILL. This rule predates solid pill fills and
   was invisible against the old pale tints; against the band's real hue it
   turned #DB4A3C into measured #757474 / #9F8D8C / #BFA19E — grey-brown mud
   that reads as a WRONG RATING rather than as a locked day. The card's own
   0.55 opacity already says "locked", and the lock glyph says it in words.
   Same lesson as the marine recorded-row fix: a filter is indiscriminate,
   and the band hue is meaning, not decoration. */
.fcp-plus { border-color: var(--accent, #12A594); }
.fcp-plus .btn { font: 600 14px/1 var(--font-sans, "Inter", system-ui, sans-serif); padding: 10px 16px; border-radius: 999px; text-decoration: none; cursor: pointer; }
.fcp-plus .btn--primary { background: var(--accent, #12A594); color: #fff; border: 0; }
.fcp-plus .btn--ghost { background: none; border: 1px solid var(--line, #D6DEE8); color: var(--text-2, #5B6B7C); }
/* four stats across where they fit; two columns on a phone, where a
   4-across grid wrapped "Time (GMT-5)" and "SSE 8 mph" mid-label */
.fcp-heart {
  border: 0; background: none; cursor: pointer; font-size: 26px; line-height: 1;
  color: var(--heart); padding: 4px 8px; float: right;
}
.fcp-widget {
  flex: 0 0 auto; scroll-snap-align: start;
  display: flex; align-items: center; gap: 8px;
  background: var(--bg, #fff); border: 1px solid var(--line, #E3E9F0); border-radius: 12px;
  padding: 10px 14px; text-decoration: none; color: var(--text-1, #16202C);
  font: 600 14px/1 var(--font-sans, "Inter", system-ui, sans-serif);
}
.fcp-widget__star { color: var(--star); }
.fcp-widgets__arrow {
  border: 1px solid var(--line, #E3E9F0); background: var(--bg, #fff); border-radius: 999px;
  width: 30px; cursor: pointer; color: var(--text-2, #5B6B7C); font-size: 18px;
}

/* ── Desktop (the approved 2c mock + amendments) ────────────────────────── */
@media (min-width: 1024px) {
  .fcp { padding-inline: 24px; }
  .fcp-head { display: flow-root; }
  .fcp-head .fcp-model { float: right; margin-left: 16px; }
  .fcp-days { gap: 10px; }
  /* ⚠ THE OLD `2fr 1fr` GRID IS GONE (2d). It was a leftover from an earlier
     mock, neutralised by #fcp-stack spanning 1/-1, and any rail nested inside
     it inherited a 2/3-width column — measured 513px of chart. The rail IS
     the grid now. */
  #fcp-charts { display: block; }
  /* ⚠ `align-items: start` HERE LEFT THE TWO CARDS RAGGED. Bite and pressure
     sit side by side in this grid and each took its own content height —
     measured 322 vs 304 at 1760 and 322 vs 272 at 1440 — so the shorter one
     read as unfinished rather than as a deliberate pair. `stretch` is the
     grid's own mechanism for this and needs nothing on the cards; the row
     height is the taller card's, and .fcp-card is already a flex column, so
     the pressure card's slack lands below its chart rather than distorting
     it. (The chart is a fixed-aspect viewBox — growing it would need
     preserveAspectRatio: none, which would stretch the trace itself.) */
  #fcp-fishing { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: stretch; }
}
/* B1: the buoy name reads BIG/BOLD — already the display face; weight up */
.fcp-head__name { font-weight: 800; }

/* ── Desktop refinement pass ─────────────────────────────────────────────── */
@media (min-width: 1024px) {
  /* item 2: the rail fills the content width; a WHOLE number of cards fits
     (7 per view) — the pager handles overflow, no half-card at the edge. */
  /* The rail shares the reclaimed width instead of spending it on
     over-wide cards: every card takes an equal share (flex-basis 0, so
     widths stay identical — the pass-2 uniform-card rule), floored at
     120px so a long rail still scrolls on a narrow desktop, and capped at
     220px so a 7-day rail on a wide screen does not stretch into
     billboards. At 1440 the full 10-day rail now fits without scrolling. */
  .fcp-days { display: flex; gap: 10px; overflow-x: auto; }
}

/* ── 2d: the right rail ──────────────────────────────────────────────────
   ⚠ THE THRESHOLD IS DERIVED FROM CONTENT, NOT A DEVICE CLASS. Two things
   must hold at once:
     the rail's widest line — the rating row "POOR 1.2 ft · 4s · 13 mph SSE",
       measured 194px — plus 32px card padding = 226px minimum; 320px is the
       specced width, which also lets the conditions stats sit 2-up
     the chart must still show A FULL DAY beside it:
       24h x 22px + 36 gutter + 42 card padding = 606px
   320 + 20 gap + 606 + 48 container padding = 994 -> the rail turns on at
   1000px. Below that it stops earning its width and its cards stack under
   the chart, which then takes the whole container. */
#fcp-cond { width: 100%; }

/* ⚠ BELOW THE THRESHOLD, CURRENT CONDITIONS COMES FIRST. Stacking the whole
   rail under the chart buried the one card people open the page for behind a
   ~600px object. `display: contents` dissolves the rail box so its three
   cards become flex items of #fcp-charts and can be ordered individually —
   conditions above the chart, tide and light below it. Order only; no
   duplicate markup, and the rail is untouched above 1000px. */
@media (max-width: 999.98px) {
  #fcp-cond { order: -1; }
  #fcp-stack { order: 0; }
  #fcp-rail-tide { order: 1; }
  #fcp-rail-light { order: 2; }
}

@media (min-width: 1000px) {
  #fcp-charts {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    gap: 20px;
    align-items: start;
  }
}

/* ============================================================================
   DARK — the forecast pages under [data-theme='dark'].

   ⚠ MOST OF THIS PAGE NEEDED NOTHING. The surfaces are already token-driven
   (--bg on --fcp-ground, --line hairlines, --surface-sunken insets) and the
   chart is entirely --ch-* driven, so both swap with the generated palettes.
   What follows is only the places where dark needs a RULE rather than a
   value — where taking light's answer and darkening it gives the wrong
   answer, not a dimmer one.

   THE LAYER MAP, since a card-dense page lives or dies on it. The app
   separates by ELEVATION in dark rather than by hairline, so the ladder runs
   the opposite way to light and is monotonic:

       page   --fcp-ground      #091322   fetchDark.page
       card   --bg              #0F1C2C   +1 step LIGHTER
       raised --surface-raised  #122334   +2   (also --surface-elevated)
       inset  --surface-sunken  #16273A   +3   the LIGHTEST

   ⚠ "SUNKEN" IS A MISNOMER IN DARK AND THE VALUE IS STILL RIGHT. In light an
   inset recedes below the white card; in dark every separated surface moves
   toward the light, insets included, because there is nowhere below a
   near-black page to go. The token name is a light-mode description and
   renaming it would churn light for no rendered gain — so it stays, noted.
   ========================================================================== */

/* The faint TEXT tier. Light is the app's faint verbatim; dark is NOT.
   fetchDark.faint #5E7184 measures 3.41:1 on the card and 3.01 on the inset —
   the one place where a value that passes in light fails in dark. Corrected
   on the TEXT side in the consuming layer (the CHOPPY precedent, same as the
   rating foregrounds): 77% faint + ink is the smallest lift that clears 4.5
   on the WORST ground (inset 4.53, card 5.14), and it stays clearly below
   fetchDark.slate #93A6B6 so the ink hierarchy ink > slate > faint survives.
   .fcp-stack__valarrow keeps the uncorrected --ch-faint on purpose: it is a
   graphic, and 3:1 is its bar. */
/* ⚠ LIGHT NEEDED THE SAME CORRECTION AND NOBODY HAD LOOKED. Session 2 lifted
   this token in dark and left light reading --ch-faint verbatim, because that
   session's remit was dark-only and light-unchanged. Measuring BOTH themes in
   session 3 found the light side is the worse of the two: fetchLight.faint
   #93A1AE is 2.64:1 on the card, 2.32 on the inset and 2.25 on the forecast
   page ground — a shipped AA failure on the locked-day chrome (day, date,
   height) and on every chart tick and hour label. Dark had been fixed and
   light had not, so the accessible rendering was the one nobody could reach.
   Same derivation as dark, mirrored: 62% toward ink is the smallest darkening
   that clears 4.5 on the WORST ground (4.55 there, 5.35 on the card), and it
   stays lighter than --text-secondary #44555E so ink > secondary > faint
   still reads as three tiers.
   ⚠ THIS CHANGES LIGHT ON PURPOSE — the only such change in session 3 besides
   the legal pages, and the reason is a WCAG failure, not a preference. */
:root        { --fcp-faint-text: color-mix(in srgb, var(--ch-faint) 62%, var(--ch-ink));
               --fcp-star-text: #7A6117; }
:root[data-theme='dark'] {
  --fcp-faint-text: color-mix(in srgb, var(--ch-faint) 77%, var(--ch-ink));
  --fcp-star-text:  var(--star);   /* #E3B65A, 9.08:1 on the card */

  /* The rating corrections re-derive to NOTHING here — see the dark block in
     css/rating-palette.css for why a percentage solved against white cannot
     be reused on navy. 100% means "no mix", so the ink operand is inert. */
  --rating-corr-tint:  100%;
  --rating-corr-inset: 100%;
}

/* ── the chart's own surfaces ────────────────────────────────────────────── */

/* THE SELECTION BAND INVERTS CORRECTLY FOR FREE and is deliberately left
   alone: ink at 8% is a darkening in light and a lightening in dark, which is
   exactly the elevation rule — the selected hours sit a step up from the
   chart floor in both themes. Same for the scrubber and crosshair, which are
   ink-derived strokes that stay visible on either ground. */

/* ── accent: INK brightens, FILL does not ───────────────────────────────────
   The app's own rule, already carried by the tokens: --accent-text brightens
   to #6FE3CC in dark while --accent stays #128575 so filled buttons keep
   their identity. The site had no such split for accent used as a STROKE or
   as small text, and #128575 measures 3.79:1 on the dark card — fine for a
   border, not for a glyph, and dim for a selection ring that has to be found
   at a glance. So every INK use switches; the two FILL uses (.btn--primary,
   .fcp-plus) are untouched and keep #fff on #128575 at 4.53:1. */
:root[data-theme='dark'] .fcp-plus                  { border-color: var(--accent-text); }

/* ── the solunar track ───────────────────────────────────────────────────── */

/* ══════════════════════════════════════════════════════════════════════════
   MARINE SHELL — the three places marine's layout diverges from surf's.

   All three hang off `.fcp--marine` (set in marineMain) rather than off
   `:has(#fcp-marine-rail)`, because the rail is REBUILT on every day change
   and a structural selector that depends on it re-resolves mid-interaction.
   ────────────────────────────────────────────────────────────────────────── */

/* 1 — A NARROWER CAP THAN SURF'S 1760.
   Marine deals four SEPARATE cards, each read on its own, where surf stacks
   its rows into one object the eye reads as a unit. At 1760 the shared shell
   gave each marine card 1330px of visible chart = 60 hours in one glance,
   and the two thin-line rows (ENERGY, TIDE) read as a hairline drawn across
   a room. The cap is derived the same way the rail threshold at line 739 is —
   from content, not a device class — and the content unit here is a WHOLE
   DAY, because the chart already draws a gutter at every midnight:

       48h (two whole days) x 22.17px measured density = 1064
       + 42 card padding + 20 gap + 320 rail + 48 container padding
       = 1494  ->  1500px

   At 1440 nothing changes (the page is already under the cap, 46 hours), so
   this is strictly a wide-monitor correction. Surf keeps 1760 — its single
   stacked object earns the span that four separate ones do not. */
.fcp--marine { max-width: 1500px; }

/* 2 — BELOW 1000px THE STATION READINGS COME FIRST.
   Same reasoning and same mechanism as the surf block at line 763: the rail
   dissolves with `display: contents`, its cards become flex items of
   #fcp-charts, and order alone re-sequences them. What differs is WHICH way.
   Surf puts current conditions above its chart and pushes tide/light below.
   Marine's rail is the station's own MEASUREMENTS — the reason someone opens
   a buoy — and DOM order put all four charts (a ~1384px object) ahead of
   them. Order 0 vs 1 lifts the whole rail above the cards; no markup moves.
   The solunar card is in this rail too (item 4) and is explicitly ordered
   last so it does not ride up with the readings. */
@media (max-width: 999.98px) {
  .fcp--marine #fcp-marine-rail > *,
  .fcp--marine #fcp-sol-slot > * { order: 0; }
  .fcp--marine #fcp-solunar { order: 2; }
}

/* The solunar slot is layout-transparent by design — see buildMarineRail.
   It exists so a rail rebuild can re-append ONE stable node; it must not
   also become a box, or the card would lose the rail's column gap above
   1000px and its own order below it. */
#fcp-sol-slot { display: contents; }

/* ── The weather shell ─────────────────────────────────────────────────────
   Same cap and same mobile rule as marine (see "marine shell"): one stacked
   chart object at 1500px, and below 1000px the rail's Conditions card comes
   FIRST, because on weather the current reading is the reason for the visit
   in a way a wind row is not. */
.fcp--weather { max-width: 1500px; }
@media (max-width: 999.98px) {
  .fcp--weather #fcp-weather-rail > * { order: 0; }
}
/* ⚠ ONE PAIR FOR BOTH DISCS. moonIcon() is now the only moon renderer, so
   its parts carry one class pair and both call sites — the 20px bite disc and
   the 34px strip disc — theme through it. The bite card previously hardcoded
   #1B2733 dark / #F5E9C8 lit, which is why its disc all but vanished against
   a dark card in dark mode; tokens fix that as a side effect of unifying. */
.fcp-moon__dark { fill: var(--surface-sunken, #E3E9F0); }
.fcp-moon__lit  { fill: var(--ch-energy, #92670F); }

/* ⚠ THE MOON STRIP'S CLASS IS `fcp-moonstrip`, NOT `fcp-moon`, AND THE NAME
   IS LOAD-BEARING. `.fcp-moon` was ALREADY TAKEN — line 318, `width: 20px;
   height: 20px`, the small moon glyph on the bite card — so naming this card
   the same thing silently capped it at 20px tall. Its 14-cell list then
   overflowed and the Sun card drew straight over the discs.

   That cost a long hunt, so here is what actually identified it, for the next
   one: CLONE THE ELEMENT INTO A BARE DIV AND RE-MEASURE. The clone was still
   42px outside the grid entirely, which killed every ancestor hypothesis —
   grid row sizing, a sibling, flex-shrink from a parent — in one measurement,
   and left only the element's own rules. Removing its class one at a time
   then took it from 42px to 193px on the first try. Ancestor-first is the
   expensive order; isolate before bisecting.

   A generic block name in a shared stylesheet is the hazard here: `fcp-moon`
   reads as "the moon thing" and there was already a moon thing. */

/* ── Weather: the TEMP row inside the stack ────────────────────────────────
   Icon + degrees at each hour's x — the strip's content on the chart's
   geometry. The bar carries the _tempColor ladder (set per hour as a fill
   attribute, since the value is per-datum); the degrees stay in the readable
   ink because that ladder is a fill palette and failed contrast as text. */
/* .fcp-day__plus and .fcp-day__lock are GONE, not hidden — see the note in
   js/forecast.js lockedDayButton. */

/* The paywall panel. The shell (backdrop, bar, x, focus trap) is overlay.js';
   only the inner block is ours. Sized as a DIALOG rather than the full-bleed
   sheet the menu uses — it is a question, not a destination. */
/* Geometry lives in css/overlay.css's dialog mode — one shell, one place. */
.fcpplus { padding: 8px 4px 4px; }
.fcpplus__copy {
  margin: 0 0 var(--sp-5, 18px);
  font-size: var(--fs-md, 15px);
  line-height: 1.45;
  color: var(--text, #16202C);
}
.fcpplus__list {
  margin: 0 0 var(--sp-5, 18px);
  padding: 0 0 0 1.1em;
  display: grid; gap: 6px;
  font-size: var(--fs-sm, 13px);
  line-height: 1.4;
  color: var(--text-secondary, #5B6B7C);
}
.fcpplus__row { display: flex; flex-wrap: wrap; gap: 10px; }
.fcpplus__row .btn { text-decoration: none; }

/* The Fetch+ mark on a gated card header, in place of the measured-data key. */
.fcp-mcard__plus {
  font: 600 10px/1 var(--font-sans, "Inter", system-ui, sans-serif);
  letter-spacing: 0.04em;
  padding: 3px 7px;
  border-radius: 999px;
  color: var(--accent-text, var(--accent, #12A594));
  border: 1px solid currentColor;
}

/* ⚠ THE CONTOURS STROKE A GRADIENT, NOT currentColor — which is why an earlier
   `color:` did nothing. #brand-isobars strokes url(#brandfield-grad), and the
   gradient's STOPS are custom properties, which DO cascade into a <use> shadow
   tree. Retinting them here is what makes the treatment take the theme's ink —
   navy on light, near-white on dark — without forking the path data, which
   tools/ gates on byte-matching index.html's copy. */
/* ⚠ TWO TOKENS, NOT ONE. --pv-line is the CONTOURS, --pv-ink is the WORDS.
   They were a single token, so retinting the artwork would have dragged the
   "Fetch+" lockup with it — Grant asked for the linework, not the text.

   Light: navy contours on a pale card. Dark: TEAL contours on navy — the
   brand colour reads there, where on white it was the pale-on-pale that made
   the whole treatment invisible in the first place. The colour is not the
   same in both themes because the grounds are not. */
:root       { --pv-ink: #16202C; --pv-line: #16202C; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) { --pv-ink: #EAF1F6; --pv-line: #128575; }
}
:root[data-theme='dark'] { --pv-ink: #EAF1F6; --pv-line: #128575; }

/* The banner: the message only, centred over the locked run. It carries a
   plate because it sits over the cards' linework — the one place a fill is
   right, since it is behind TEXT rather than behind the cards. */
:root { --pv-plate: rgba(255, 255, 255, 0.88); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) { --pv-plate: rgba(9, 19, 34, 0.85); }
}
:root[data-theme='dark'] { --pv-plate: rgba(9, 19, 34, 0.85); }

/* The rail becomes the positioning context for the band. */
.fcp-days { position: relative; }
