/* /forecast — the rebuilt page.
 *
 * Every class is prefixed `fc-`. The approved mockup used bare names (.card, .grid, .chip,
 * .tape) which all already mean something in style.css; dropping those in unprefixed would
 * have silently restyled cards and tables across the app.
 *
 * No colour or radius is invented here — everything comes from the :root tokens in
 * style.css, so the page follows the app's theme rather than drifting from it.
 *
 * The two ideas the layout has to carry, both of them load-bearing:
 *   1. SEASON is the big number and DAY is the sub-line. That is the honest ordering: at a
 *      fixed water the month is worth 8-25 percentage points and the day 1-3, so a layout
 *      that led with the day would misrepresent which lever matters.
 *   2. A species with no measured day signal gets a FLAT week — no ranking, no best-day
 *      marker. The markup for that state is a sentence, not seven identical cells, because
 *      seven identical cells still read as a comparison.
 */

/* ── page rhythm ────────────────────────────────────────────────────────── */
/* `.card` in style.css carries NO margin — the old forecast page spaced its cards with inline
   `style="margin-top:16px"` on each one, which the rebuild dropped, so everything butted
   together. Space the column here instead of reintroducing per-element margins: one `gap`
   cannot double up against a neighbour's margin, and adding a section later needs no styling. */
.forecast-layout { display: flex; flex-direction: column; gap: 16px; }
/* These set their own bottom margin, which STACKS on top of the flex gap — .page-header
   carries margin-bottom:16px from style.css, so the space under the header measured 32px
   against 16px everywhere else and broke the page's vertical rhythm. Zero them and let the
   single `gap` own all the spacing. */
.forecast-layout .fc-answer,
.forecast-layout .page-header { margin-bottom: 0; }
/* A hidden card must not leave a gap behind it (the year grid is withheld by product choice,
   and the week grid hides itself when there is nothing to compare). */
.forecast-layout > [hidden] { display: none; }

/* ── headline ───────────────────────────────────────────────────────────── */
.fc-answer {
    /* Reserved at the MEASURED loaded height, not the skeleton's. The skeleton was a 22px bar
       and the real headline lands at 59px desktop / 71px mobile — two sentences naming two
       species — so the box grew ~37px desktop and ~49px mobile and pushed every card below it
       down. That single growth was most of a 0.175 mobile CLS. Reserve it and the text paints
       INTO the box. min-height, not height: an unusually long species name may add a line, and
       a one-line shift beats clipping the answer. */
    min-height: 59px;
    font-size: 1.3125rem;
    line-height: 1.4;
    font-weight: 600;
    color: var(--text-bright);
    max-width: 60ch;
    margin: 0 0 16px;
    text-wrap: balance;
}
.fc-answer em { font-style: normal; color: var(--accent-green); }
.fc-answer.is-loading { color: var(--text-muted); }

/* ── "jouw vissen" picker ───────────────────────────────────────────────── */

/* ── the tape ───────────────────────────────────────────────────────────── */
.fc-tape {
    display: flex; gap: 10px; overflow-x: auto; padding: 14px 16px 16px;
    scroll-behavior: smooth; scrollbar-width: thin;
    scrollbar-color: var(--border-light) transparent;
}
.fc-tcard {
    flex-shrink: 0; width: 186px; border-radius: var(--radius);
    border: 1px solid var(--border); background: var(--bg-elevated);
    overflow: hidden; position: relative; cursor: pointer; text-align: left;
    transition: all var(--transition);
}
.fc-tcard:hover { border-color: var(--border-light); transform: translateY(-2px); }
.fc-tcard.is-sel { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.fc-tcard img { width: 100%; height: 104px; object-fit: cover; background: rgba(255,255,255,.03); }
/* The card body is built from <span>s (a <button> may not contain <div>), so each line has to
   be made a block explicitly — left inline they run together into one wrapped paragraph. */
.fc-tbody { display: block; padding: 9px 11px 11px; }
.fc-tname, .fc-tscale, .fc-tpct, .fc-tsub { display: block; }
.fc-tname {
    font-size: .875rem; font-weight: 600; color: var(--text-bright); margin-bottom: 6px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The scale gets NAMED. Without it "+94%" reads as if it were about tomorrow, when it is
   the month. Two different scales sharing one card is exactly how a number gets misread. */
.fc-tscale {
    font-size: .625rem; font-weight: 600; text-transform: uppercase; letter-spacing: .06em;
    color: var(--text-faint); margin-bottom: 1px;
}
.fc-tpct { font-size: 1.375rem; font-weight: 700; letter-spacing: -.02em; line-height: 1.1; }
.fc-tsub { font-size: .6875rem; color: var(--text-muted); margin-top: 3px; }
.fc-tape-rule {
    display: flex; align-items: center; gap: 10px; padding: 0 16px 13px;
    font-size: .6875rem; color: var(--text-faint); letter-spacing: .04em; text-transform: uppercase;
}
.fc-tape-rule i {
    flex: 1; height: 1px;
    background: linear-gradient(90deg, rgba(63,185,80,.5), rgba(110,118,129,.25), rgba(219,109,40,.5));
}

/* ── detail ─────────────────────────────────────────────────────────────── */
.fc-dt { display: grid; grid-template-columns: 300px 1fr; gap: 0; }
.fc-dt-photo { position: relative; min-height: 100%; }
.fc-dt-photo img { width: 100%; height: 100%; object-fit: cover; position: absolute; inset: 0; }
.fc-dt-scrim {
    position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: flex-end;
    padding: 16px 18px;
    background: linear-gradient(to top, rgba(8,12,18,.9) 0%, rgba(8,12,18,.55) 40%, transparent 75%);
}
.fc-dt-name { font-size: 1.3125rem; font-weight: 600; color: #fff; text-shadow: 0 1px 4px rgba(0,0,0,.7); }
.fc-dt-sub { font-size: .75rem; color: var(--text); text-shadow: 0 1px 3px rgba(0,0,0,.7); margin-top: 2px; }
.fc-dt-main { padding: 16px 18px 18px; border-left: 1px solid var(--border); min-width: 0; }
.fc-dt-block { margin-bottom: 18px; }
.fc-dt-block:last-child { margin-bottom: 0; }
.fc-dt-h { display: flex; align-items: baseline; gap: 10px; margin-bottom: 9px; flex-wrap: wrap; }
.fc-dt-h b {
    font-size: .6875rem; font-weight: 600; text-transform: uppercase; letter-spacing: .06em;
    color: var(--text-muted);
}
.fc-dt-h span { font-size: .8125rem; color: var(--text); }
.fc-dt-h span em { font-style: normal; color: var(--text-bright); font-weight: 600; }
.fc-dt-empty { font-size: .8125rem; color: var(--text-muted); padding: 2px 0 4px; }

/* Column counts live in CSS, not inline, so the media queries can rewrap them. A grid cell
   cannot shrink below its own text, so twelve across simply does not fit on a phone — the
   year strip wraps to two half-year rows rather than overflowing the card. */
.fc-strip { display: grid; gap: 4px; min-width: 0; }
.fc-strip.is-week { grid-template-columns: repeat(7, 1fr); }
.fc-strip.is-year { grid-template-columns: repeat(12, 1fr); }
.fc-st {
    border-radius: var(--radius); padding: 8px 2px 9px; text-align: center;
    border: 1px solid transparent; min-width: 0;
}
.fc-st .fc-k {
    font-size: .6875rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
    color: var(--text-muted);
}
.fc-st .fc-v {
    font-size: 1rem; font-weight: 700; font-variant-numeric: tabular-nums;
    margin-top: 3px; color: var(--text-muted);
}
.fc-st.is-pos .fc-v { color: var(--text-bright); }
.fc-st.is-cur { border-color: rgba(88,166,255,.5); }
.fc-st.is-cur .fc-k { color: var(--accent); }
.fc-st.is-shut .fc-v { font-size: .6875rem; font-weight: 500; font-style: italic; color: var(--text-faint); }

/* ── the two reference tables ───────────────────────────────────────────── */
/* STICKY HEADER ROW — so you never lose which score belongs to which day.
 *
 * The header pins to the TABLE's own scroll box, not to the viewport, and that is forced
 * rather than chosen. `position: sticky` is captured by the nearest scrolling ancestor, and
 * this table has two above it that cannot be changed for one card's sake:
 *     div.card              overflow: hidden        (clips content to the rounded corners)
 *     main#main-content     overflow: hidden auto   (the site-wide horizontal-scroll guard)
 * Both are scroll containers that never actually scroll, so a viewport-relative header sticks
 * to them and simply rides the page — measured running off to -920px. `overflow-y: clip` on
 * the wrapper does not rescue it either; Chromium computes it to `hidden`, still a container.
 *
 * So the wrapper becomes the scrollport on purpose: give it a height, and the header pins to
 * the top of it. Same behaviour at every resolution, no global CSS surgery, and it also solves
 * the narrow widths where the table genuinely must scroll sideways (320px of wrapper against a
 * 640px table at 390px).
 */
.fc-gridwrap {
    overflow: auto;
    /* Enough for the header plus roughly eight rows on a laptop, and it shrinks with the
       viewport. overscroll-behavior stops the page from scrolling on when the table bottoms
       out, which is what makes nested scrolling feel accidental. */
    max-height: 72vh;
    overscroll-behavior: contain;
}
.fc-grid thead th {
    position: sticky; top: 0; z-index: 3;
    /* Opaque, deliberately: body rows scroll UNDER this. A translucent header repeats the bug
       that made the selected row's species column see-through on mobile. */
    background: var(--bg-card);
}
/* The corner cell is sticky on BOTH axes, so it must outrank its own row and the sticky
   species column beneath it. */
.fc-grid thead th.fc-sp { z-index: 4; }
}
.fc-grid { width: 100%; border-collapse: separate; border-spacing: 0; }
.fc-grid th, .fc-grid td { padding: 0; text-align: center; }
.fc-grid thead th { padding: 8px 4px 10px; border-bottom: 1px solid var(--border); vertical-align: bottom; }
.fc-grid th.fc-sp, .fc-grid td.fc-sp {
    position: sticky; left: 0; z-index: 2; background: var(--bg-card);
    border-right: 1px solid var(--border); width: 230px;
}
.fc-grid tbody tr:hover td.fc-sp { background: var(--bg-elevated); }
.fc-grid thead th.fc-sp {
    text-align: left; padding-left: 16px; font-size: .6875rem; font-weight: 600;
    color: var(--text-muted); text-transform: uppercase; letter-spacing: .06em;
}
.fc-dn { font-size: .75rem; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; color: var(--text-bright); }
.fc-dd { font-size: .6875rem; color: var(--text-faint); margin-top: 1px; }
.fc-grid th.is-cur .fc-dn { color: var(--accent); }
.fc-nowpill {
    /* Inline with the day name — see renderWeekGrid. margin-top would push the name's baseline
       and undo the alignment this move exists to create. */
    display: inline-block; margin-left: 5px; vertical-align: middle;
    font-size: .5625rem; font-weight: 700;
    letter-spacing: .08em; text-transform: uppercase; color: var(--accent);
    background: var(--accent-dim); border-radius: 9px; padding: 2px 7px;
}
.fc-grid td.is-cur { background-image: linear-gradient(rgba(88,166,255,.055), rgba(88,166,255,.055)); }
.fc-grid tbody td { border-bottom: 1px solid var(--border); }
.fc-grid tbody tr:last-child td { border-bottom: 0; }
.fc-grid tbody tr { cursor: pointer; }
.fc-grid tbody tr:hover td { background-color: var(--bg-elevated); }
/* THE SELECTED ROW MUST NOT DISSOLVE ITS OWN STICKY COLUMN.
 *
 * `tr.is-sel td` (specificity 0,2,3) out-specifies the sticky species column's opaque
 * `background: var(--bg-card)` above (0,2,1). So selecting a row dropped its sticky cell to 5%
 * alpha, and on any viewport where the grid scrolls — every width under ~700px, because
 * `.fc-grid` is min-width 640px inside an overflow-x wrapper — the percentage cells travelling
 * underneath printed straight through the species name and photo. Measured at 360/390/430px:
 * 199px of overlap, the full width of the cell.
 *
 * Two changes, both minimal:
 *   - exclude .fc-sp from the tint rule, so it can never lose its opaque base. Free, because
 *     every day cell already carries an inline background-color from renderWeekGrid, so this
 *     tint was never visible on them anyway.
 *   - give the sticky cell the tint as a background-IMAGE instead, which layers over the base
 *     colour rather than replacing it. Same technique the current-day column already uses.
 * Not a z-index fix: z-index 2 already wins the paint order; the cell was simply see-through.
 */
.fc-grid tbody tr.is-sel td:not(.fc-sp) { background-color: rgba(88,166,255,.05); }
.fc-grid tbody tr.is-sel td.fc-sp {
    box-shadow: inset 3px 0 0 var(--accent);
    background-image: linear-gradient(rgba(88,166,255,.05), rgba(88,166,255,.05));
}
.fc-spcell { display: flex; align-items: center; gap: 12px; padding: 6px 14px 6px 16px; text-align: left; }
.fc-spcell img { width: 88px; height: 50px; border-radius: 5px; object-fit: cover; background: rgba(255,255,255,.03); flex: none; }
.fc-spcell span { font-size: .875rem; color: var(--text-bright); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fc-cell { font-size: .875rem; font-weight: 600; font-variant-numeric: tabular-nums; padding: 9px 4px; color: var(--text-muted); }
.fc-cell.is-pos { color: var(--text-bright); }
.fc-cell.is-shut {
    font-size: .6875rem; font-weight: 500; font-style: italic;
    color: var(--text-faint); background-color: var(--bg) !important;
}
.fc-ycell { height: 32px; padding: 0 2px; }
.fc-ybar {
    height: 24px; border-radius: 3px; display: flex; align-items: center; justify-content: center;
    font-size: .8125rem; font-weight: 600; font-variant-numeric: tabular-nums; color: transparent;
}
.fc-ycell.is-cur .fc-ybar { color: var(--text-bright); }
.fc-legend { display: flex; align-items: center; padding: 11px 16px 12px; border-bottom: 1px solid var(--border); flex-wrap: wrap; }
.fc-legend .fc-cap { font-size: .75rem; color: var(--text-muted); margin-right: 14px; }
.fc-lg { display: flex; align-items: center; gap: 7px; margin-right: 16px; }
.fc-lg i { width: 26px; height: 14px; border-radius: 3px; display: block; flex: none; }
.fc-lg span { font-size: .6875rem; color: var(--text-muted); }

/* ── "hier maakt de dag niet uit" ───────────────────────────────────────── */
.fc-grp { padding: 14px 16px; border-bottom: 1px solid var(--border); }
.fc-grp:last-of-type { border-bottom: 0; }
.fc-grp h3 { font-size: .8125rem; font-weight: 600; color: var(--text-bright); margin: 0 0 4px; }
.fc-grp p { font-size: .75rem; color: var(--text-muted); margin: 0 0 11px; max-width: 72ch; }
.fc-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.fc-chip {
    display: flex; align-items: center; gap: 9px; background: var(--bg-elevated);
    border: 1px solid var(--border); border-radius: 22px; padding: 3px 14px 3px 3px;
    cursor: pointer; text-align: left;
}
.fc-chip:hover { border-color: var(--border-light); }
.fc-chip img { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; opacity: .9; }
.fc-chip span { font-size: .8125rem; color: var(--text); }
.fc-note { font-size: .75rem; color: var(--text-muted); }

/* ── location line ──────────────────────────────────────────────────────── */
.fc-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.fc-loc { font-size: .8125rem; color: var(--text-muted); }

/* ── responsive ─────────────────────────────────────────────────────────── */
@media (max-width: 1000px) {
    .fc-dt { grid-template-columns: 250px 1fr; }
    .fc-st .fc-v { font-size: .9375rem; }
}
@media (max-width: 900px) {
    .fc-dt { grid-template-columns: 1fr; }
    .fc-dt-photo { height: 180px; }
    .fc-dt-main { border-left: 0; border-top: 1px solid var(--border); }
    .fc-grid { min-width: 640px; }
    /* The photo shrinks here (282px -> 180px) but the overlay type did not: the species name
       kept its full desktop 21px on a 360px screen and the subtitle wrapped to two lines, so
       the two together covered 38% of the image against 24% on desktop — the fish was mostly
       caption. Scaled to hold the desktop proportion. The subtitle may still take two lines on
       the narrowest phones; at this size that reads as a caption rather than as a headline
       competing with the species name. */
    .fc-dt-scrim { padding: 14px 15px; }
    .fc-dt-name { font-size: 1.0625rem; }
    .fc-dt-sub { font-size: .6875rem; line-height: 1.35; }
}
/* Below this width twelve months cannot fit: a cell cannot shrink below its own text, so
   this is a REWRAP to two half-year rows, not a resize. */
@media (max-width: 780px) {
    .fc-strip.is-year { grid-template-columns: repeat(6, 1fr); }
    /* SEVEN ACROSS DOES NOT FIT A PHONE. At 390px each of seven cells gets 37px of usable
       width, and "+102%" needs 47px at 13px — every cell was clipped, at 360, 390 and 430
       alike. Shrinking the type to fit would need ~9px, which is not a number anyone reads.
       Wrap to 4 + 3 instead: the cell grows to ~70px and the value keeps its size. Same move
       the year strip already makes just above, for the same reason. */
    .fc-strip.is-week { grid-template-columns: repeat(4, 1fr); }
    .fc-st { padding: 7px 2px 8px; }
}
@media (max-width: 640px) {
    .fc-grid th.fc-sp, .fc-grid td.fc-sp { width: 150px; }
    .fc-spcell { gap: 9px; padding: 6px 10px 6px 12px; }
    .fc-spcell img { width: 56px; height: 32px; }
    .fc-spcell span { font-size: .8125rem; }
    .fc-tcard { width: 152px; }
    .fc-tcard img { height: 86px; }
    .fc-dt-main { padding: 14px 14px 16px; }
    .fc-strip { gap: 3px; }
    .fc-st .fc-v { font-size: .875rem; }
    .fc-st .fc-k { font-size: .625rem; }
    .fc-answer { font-size: 1.0625rem; min-height: 71px; }
}
@media (max-width: 420px) {
    .fc-st { padding: 6px 1px 7px; }
    .fc-st .fc-v { font-size: .8125rem; margin-top: 2px; }
}

.fc-dt-note{font-size:.6875rem;color:var(--text-faint);margin-top:7px;max-width:64ch}

/* ── location prompt ────────────────────────────────────────────────────── */
/* Prominent when it is open — it is the one thing blocking a useful forecast. */
/* `.card` in style.css is `overflow: hidden` (it clips content to the rounded corners), and the
   typeahead list below is an ABSOLUTELY POSITIONED overlay hanging off the bottom of the input.
   Inside a clipping card the suggestions were sliced off at the card's bottom edge — only the
   ~12px of card padding under the input survived, so typing appeared to "cut something off".
   The card must therefore opt out of the clip. It is safe here specifically: this card's only
   children are text and one flex row, none of which reach the rounded corners, so nothing else
   depended on the clip. Do NOT lift this to `.card` globally — cards elsewhere DO rely on it. */
.fc-locprompt { overflow: visible; }
.fc-locprompt-title { font-size: 1rem; font-weight: 600; color: var(--text-bright); margin: 0 0 4px; }
.fc-locprompt-sub { font-size: .8125rem; color: var(--text-muted); margin: 0 0 12px; max-width: 60ch; }
.fc-locprompt-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.fc-locprompt-gps {
    display: inline-flex; align-items: center; gap: 7px;
    border: 1px solid var(--border-light); border-radius: var(--radius);
    padding: 7px 14px; font-size: .8125rem; font-weight: 500;
    color: var(--accent); background: none; cursor: pointer; transition: all var(--transition);
}
.fc-locprompt-gps:hover:not(:disabled) { background: var(--accent-dim); border-color: var(--accent); }
.fc-locprompt-gps:disabled { opacity: .5; cursor: default; }

/* ── the map picker ─────────────────────────────────────────────────────── */
/* HEIGHT IS RESERVED, not grown into. Leaflet is fetched only when this card opens, so the
   container exists for a moment with nothing in it — without a fixed height the card would
   expand when the map arrives and shove the page. The skeleton fills the same box, so what
   the visitor sees is a map-shaped placeholder becoming a map, with nothing moving. */
.fc-map {
    position: relative;
    /* Tall enough that fitBounds can actually zoom to a bucket. The box is full-card width, so
       height is what constrains the fit: a 0.5-degree bucket is taller than it is wide, and at
       340px the map settled two zoom levels out and the region read as a small box lost in the
       country. It is also the shape of the Netherlands, which is taller than it is wide. */
    height: 440px;
    margin: 12px 0 10px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;                 /* clip the tiles to the rounded corners */
    background: var(--bg-elevated);
    cursor: crosshair;                /* this map is a picker, not a viewer */
}
.fc-map-skel { position: absolute; inset: 0; border-radius: 0; }
.fc-map.is-dead { display: none; }    /* Leaflet failed — search + GPS still work */

/* Leaflet paints its own chrome; align it with the app's tokens rather than its defaults. */
.fc-map .leaflet-container { height: 100%; width: 100%; background: var(--bg-elevated); font: inherit; }
.fc-map .leaflet-control-attribution {
    background: rgba(0,0,0,.55); color: var(--text-faint); font-size: .625rem;
}
.fc-map .leaflet-control-attribution a { color: var(--text-muted); }
.fc-map .leaflet-bar a {
    background: var(--bg-card); color: var(--text-bright); border-bottom-color: var(--border);
}
.fc-map .leaflet-bar a:hover { background: var(--bg-elevated); }

.fc-map-foot {
    display: flex; align-items: center; justify-content: space-between;
    gap: 12px; flex-wrap: wrap; margin-bottom: 12px;
}
.fc-map-status { font-size: .8125rem; color: var(--text-muted); }
.fc-map-status.is-error { color: var(--accent-orange, #e8a33d); }
.fc-map-done {
    border: 1px solid var(--accent); border-radius: var(--radius);
    padding: 7px 16px; font-size: .8125rem; font-weight: 600;
    color: var(--accent); background: var(--accent-dim); cursor: pointer;
    transition: all var(--transition);
}
.fc-map-done:hover { background: var(--accent); color: var(--bg-card); }
.fc-map-done[hidden] { display: none; }

/* The search row is a SHORTCUT under the map now, not the main event. */
.fc-locprompt .fc-locprompt-row { padding-top: 10px; border-top: 1px solid var(--border); }

@media (max-width: 640px) {
    /* Shorter, but still tall enough that a 0.5-degree region and the coastline around it are
       both legible — the rectangle is the whole point, so it must not fill the viewport. */
    .fc-map { height: 320px; }
}

/* ── season silhouette ──────────────────────────────────────────────────── */
/* The species' year as a shape. Colours come from the app tokens; the "now" cursor is blue
   because blue means TIME everywhere on this page and must never be read as a value. */
.fc-sil { display: block; width: 100%; height: 56px; overflow: visible; }
.fc-sil--spark { height: 22px; opacity: .85; }
.fc-sil-stop-top { stop-color: var(--accent-green); stop-opacity: .38; }
.fc-sil-stop-bot { stop-color: var(--accent-green); stop-opacity: .03; }
.fc-sil-line { fill: none; stroke: var(--accent-green); stroke-width: 1.6; vector-effect: non-scaling-stroke; }
.fc-sil-zero { stroke: var(--border-light); stroke-width: 1; stroke-dasharray: 2 3; vector-effect: non-scaling-stroke; }
.fc-sil-nowline { stroke: var(--accent); stroke-width: 1; stroke-dasharray: 2 2; opacity: .55; vector-effect: non-scaling-stroke; }
.fc-sil-nowdot { fill: var(--accent); stroke: var(--bg-card); stroke-width: 1.5; vector-effect: non-scaling-stroke; }

/* ── het visjaar ────────────────────────────────────────────────────────── */
.fc-visjaar-intro { max-width: 78ch; }
.fc-visjaar-list { list-style: none; margin: 0; padding: 0; }
/* NO content-visibility here. It was `content-visibility: auto` for rendering cost, and the
   browser then skips layout for off-screen rows — which also removes them from
   document.body.innerText. All 27 rows were in the HTML but 0 of 27 sentences survived to
   rendered text, in all three languages. Googlebot parses the DOM so classic search was
   unaffected; what it cost was every headless-render-then-extract pipeline, which is a large
   share of how AI answer engines read a page. This section is the page's only evergreen
   content, so that was the worst possible place to save a few milliseconds of layout. With
   27 rows the saving was small; if it is ever wanted back, put it on .fc-visjaar-sil only —
   the SVG carries no text. */
.fc-visjaar-row {
    display: grid; grid-template-columns: 150px 240px 1fr; gap: 16px; align-items: center;
    padding: 10px 16px; border-top: 1px solid var(--border);
}
/* All 27 rows render. The show-all/show-less toggle is gone: every row already shipped in the
   HTML (the button only flipped a class), the section sits below the fold on both viewports so
   hiding rows protected nothing above it, and Google's own guidance is that if content matters
   it should be visible. Hiding 19 of 27 buried the species with the most search intent —
   the sort is drama x sqrt(support), which put baars at #10 and snoekbaars at #22. */
.fc-visjaar-name { font-size: .875rem; font-weight: 600; color: var(--text-bright); text-decoration: none; }
.fc-visjaar-name:hover { color: var(--accent); }
.fc-visjaar-sil { min-width: 0; }
/* Reserved height: the silhouettes sit above the FAQ fold on mobile, so an unreserved SVG
   would shift the page as each one paints (project_cls_skeleton_convention). */
.fc-visjaar-sil .fc-sil { height: 44px; }
.fc-visjaar-say { font-size: .8125rem; color: var(--text-muted); margin: 0; }
@media (max-width: 760px) {
    .fc-visjaar-row { grid-template-columns: 1fr; gap: 6px; padding: 12px 16px; }
    .fc-visjaar-sil .fc-sil { height: 52px; }
}

/* ── two columns once there is room for two ─────────────────────────────────
 * One column per row wasted most of the width: measured at 1440px the sentence column was
 * 888px for a sentence that needs 526px, so ~360px sat empty on all 27 rows and the section
 * ran 2,434px tall — a long scroll past mostly nothing.
 *
 * The cell is restructured rather than just halved. Keeping the name in its own fixed 150px
 * column inside a half-width cell would leave the sentence under 200px and wrap it to four
 * lines; instead the name sits directly above its sentence in one text block beside the
 * silhouette, which is the natural reading order anyway (shape, then what the shape means).
 *
 * Grid fills across then down. That is correct here: the order is "most pronounced rhythm
 * first", a ranking people scan rather than read line by line.
 */
@media (min-width: 1100px) {
    .fc-visjaar-list { display: grid; grid-template-columns: 1fr 1fr; }
    /* Named areas with the silhouette SPANNING both text rows. The first attempt stacked the
       name and the sentence in one shared area with align-self end/start, which only looks
       right while the sentence is exactly two lines — a three-line sentence would have printed
       straight through the species name. */
    .fc-visjaar-row {
        grid-template-columns: 200px 1fr;
        grid-template-rows: auto auto;
        grid-template-areas: "sil name" "sil say";
        column-gap: 14px; row-gap: 2px;
        align-items: center; align-content: center;
        padding: 12px 18px;
        /* contain-intrinsic-size removed with content-visibility: it only sized the
           placeholder that a skipped row used, and skipping is what hid these sentences
           from rendered-text extraction. */
    }
    /* A hairline between the two columns, so the eye does not read a left cell's silhouette
       as belonging to the right cell's sentence. */
    /* :not(:last-child) — there are 27 rows, an odd number, so the final cell sits alone in
       the left column and a divider there would hang beside nothing. */
    .fc-visjaar-row:nth-child(odd):not(:last-child) { border-right: 1px solid var(--border); }
    .fc-visjaar-sil { grid-area: sil; }
    .fc-visjaar-name { grid-area: name; align-self: end; }
    .fc-visjaar-say { grid-area: say; align-self: start; }
}

/* ── tape segmentation ──────────────────────────────────────────────────── */
/* Pinned species are the user's dashboard: bigger and on a lifted surface. Deliberately NOT an
   accent left-border — that idiom is banned in this project's design notes. */
.fc-tspark { display: block; margin-top: 8px; opacity: .9; }
.fc-tspark .fc-sil { height: 20px; }
.fc-lockline { display: inline-flex; align-items: center; gap: 5px; color: var(--text-muted); }
.fc-lockline svg { opacity: .7; flex: none; }
@media (max-width: 640px) {
}

/* ── the shared weather row ─────────────────────────────────────────────── */
/* Rendered once at the top of the grid instead of repeated as ~20 parallel species curves.
   Visually set apart so it reads as the CONDITION the rows sit under, not as another species. */
.fc-wxrow td { background: var(--bg) !important; }
.fc-wxrow .fc-cell { color: var(--text); font-weight: 600; }
.fc-spcell--wx { gap: 10px; color: var(--text-muted); }
.fc-wxicon { display: flex; color: var(--accent); flex: none; width: 88px; justify-content: center; }
.fc-wxrow + tr td { border-top: 1px solid var(--border-light); }
@media (max-width: 640px) { .fc-wxicon { width: 56px; } }

/* ── locked week cells + the gate ───────────────────────────────────────── */
/* Texture, not numbers. Nothing is sent for a locked day, so devtools finds wallpaper — the
   withheld fact never reaches the browser and the paywall is not a CSS rule. Everything below
   is decoration over that emptiness, and each piece has to be checked against one question:
   could a reader decode a day value from it? Only two things vary between these six cells —
   the weekday label and the sheen's start time. Neither is a forecast.

   It went through two worse versions. Flat grey boxes with a small lock read as six cells we
   had failed to fill, not as six cells being withheld; the fix is that the value slot now
   holds a blurred REDACTION BAR, identical in all six, which says "a number is here" without
   being one. Coloured blooms read as data — green IS "above average" on this strip and blue IS
   "now" — so the whole locked treatment is monochrome and today's cell is the only colour. */
.fc-st--locked {
    position: relative; overflow: hidden;
    /* A pane, not a swatch: the vertical gradient plus the inset top highlight below is what
       makes it read as glass over something rather than as grey fill. */
    background: linear-gradient(180deg, var(--bg-hover), var(--bg-elevated));
    box-shadow: inset 0 1px 0 rgba(255,255,255,.05);
    /* Button reset — these are <button>s so they can be clicked and tabbed to. */
    appearance: none; -webkit-appearance: none; width: 100%;
    /* line-height too, not just the family: the UA stylesheet pins a button to `normal`, so
       without this the label and the value slot sit on a shorter line box than the same markup
       in today's <div> cell and the frosted row lands ~5px shallow wherever the strip wraps. */
    font-family: inherit; line-height: inherit; color: inherit; cursor: pointer;
    border: 1px solid var(--border-light);
    transition: border-color .18s ease, transform .18s ease, box-shadow .18s ease;
}
.fc-lockpat {
    /* Bled past the edges so the blur has material to sample: at inset 0 a 5px blur fades the
       wash out against its own boundary and leaves a pale ring inside the border. */
    position: absolute; inset: -25%; pointer-events: none;
    background:
        radial-gradient(circle at 30% 24%, rgba(139,148,158,.30), transparent 62%),
        radial-gradient(circle at 76% 78%, rgba(139,148,158,.20), transparent 64%),
        repeating-linear-gradient(135deg, rgba(139,148,158,.20) 0 3px, transparent 3px 8px);
    /* 5px, not 7: past about 6 the hatch dissolves into a flat smear and the cell goes back to
       reading as an empty box rather than as something obscured. */
    filter: blur(5px);
}
/* THE SHEEN. A light source travelling behind the glass, which is what turns a static tile
   into something that looks like it has content. It inherits .fc-lockpat's blur, so it lands
   as a soft bloom rather than a hard band. */
.fc-lockpat::after {
    content: ""; position: absolute; inset: 0;
    background: linear-gradient(115deg, transparent 38%,
                rgba(255,255,255,.20) 50%, transparent 62%);
    transform: translateX(-130%);
    animation: fc-sheen 4.6s ease-in-out infinite;
}
@keyframes fc-sheen {
    0%, 58% { transform: translateX(-130%); }
    100%    { transform: translateX(130%); }
}
/* Staggered so the row shimmers as a WAVE rather than six tiles flashing in unison — the wave
   reads as one surface, the unison reads as a loading spinner. Child 1 is today's real cell. */
.fc-strip.is-week .fc-st--locked:nth-child(3) .fc-lockpat::after { animation-delay: .10s; }
.fc-strip.is-week .fc-st--locked:nth-child(4) .fc-lockpat::after { animation-delay: .20s; }
.fc-strip.is-week .fc-st--locked:nth-child(5) .fc-lockpat::after { animation-delay: .30s; }
.fc-strip.is-week .fc-st--locked:nth-child(6) .fc-lockpat::after { animation-delay: .40s; }
.fc-strip.is-week .fc-st--locked:nth-child(7) .fc-lockpat::after { animation-delay: .50s; }
/* The label and the value slot ride ABOVE the wash — .fc-lockpat is absolutely positioned and
   comes first in the DOM, so without a stacking context of their own they would sit under it. */
.fc-st--locked .fc-k,
.fc-st--locked .fc-v { position: relative; display: block; }
/* Absolutely positioned, so the badge costs the cell no height and cannot pull the value slot
   off the line the real cells sit on. */
.fc-lockbadge {
    position: absolute; top: 5px; right: 5px; display: flex; line-height: 0;
    color: var(--text-faint); transition: color .18s ease, transform .18s ease;
}
.fc-lockbadge svg { width: 11px; height: 11px; }
/* NO explicit height on the slot. It has to occupy exactly the line box a percentage would, or
   the frosted cells come out shorter than today's — invisible on desktop, where one grid row
   stretches them all to match, but plainly wrong at <=520px where the strip wraps to 4 columns
   and the all-locked second row sits 5px shallower than the first. A block box with an INLINE
   child inherits .fc-v's font-size and the page's line-height and lands on the value line by
   construction, at all four breakpoints, without this file having to know either number. */
.fc-lockic { display: block; margin-top: 3px; }
.fc-lockbar {
    display: inline-block; vertical-align: middle;
    width: 58%; height: 9px; border-radius: 5px;
    background: linear-gradient(90deg, rgba(230,237,243,.12),
                rgba(230,237,243,.42) 45%, rgba(230,237,243,.14));
    filter: blur(2.5px);
    transition: filter .18s ease, opacity .18s ease;
}
.fc-st--locked:hover,
.fc-st--locked:focus-visible {
    border-color: var(--text-faint); transform: translateY(-2px);
    box-shadow: 0 4px 14px rgba(0,0,0,.28), inset 0 1px 0 rgba(255,255,255,.08);
}
/* Hover stays COLOURLESS. The accent blue read as the "now" cursor moving under the pointer,
   and beside today's green cell the strip came out blue-and-green for no reason a reader could
   decode. Brightness carries the affordance instead. */
.fc-st--locked:hover .fc-lockpat,
.fc-st--locked:focus-visible .fc-lockpat { opacity: .8; }
.fc-st--locked:hover .fc-lockbadge,
.fc-st--locked:focus-visible .fc-lockbadge { color: var(--text-bright); transform: scale(1.12); }
/* The bar sharpens a little on hover — the gesture of "this could be revealed" — but never all
   the way, because there is nothing underneath it to reveal. */
.fc-st--locked:hover .fc-lockbar,
.fc-st--locked:focus-visible .fc-lockbar { filter: blur(1.6px); opacity: .9; }
.fc-st--locked:focus-visible { outline: 2px solid var(--text-muted); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
    .fc-st--locked { transition: none; }
    .fc-st--locked:hover, .fc-st--locked:focus-visible { transform: none; }
    .fc-lockpat::after { animation: none; transform: translateX(0); opacity: .5; }
    .fc-lockbadge { transition: none; }
}
.fc-gate {
    font-size: .8125rem; color: var(--text); margin: 11px 0 0; max-width: 62ch; line-height: 1.5;
}
.fc-gate-cta {
    display: inline-block; margin-left: 4px; font-weight: 600; color: var(--accent);
    text-decoration: none; border-bottom: 1px solid currentColor;
}
.fc-silmonths {
    display: grid; grid-template-columns: repeat(12, 1fr); gap: 2px; margin-top: 4px;
    font-size: .5625rem; text-transform: uppercase; letter-spacing: .03em; color: var(--text-faint);
    text-align: center;
}
.fc-silmonths .is-cur { color: var(--accent); font-weight: 700; }
@media (max-width: 640px) { .fc-silmonths { font-size: .5rem; } }

/* ── the return loop ────────────────────────────────────────────────────── */
/* Turns the free horizon from a wall into an appointment. Quiet by design — it sits under the
   week strip and must never out-shout the forecast itself. */
#fc-return {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    padding: 11px 14px; border: 1px solid var(--border-light); border-radius: 10px;
    background: var(--bg-elevated); font-size: .8125rem; color: var(--text-muted);
}
#fc-return[hidden] { display: none; }
.fc-ret-ic { display: flex; color: var(--accent); }
.fc-ret-tx { color: var(--text); }
.fc-ret-cta { margin-left: auto; color: var(--accent); font-weight: 600; text-decoration: none; }
.fc-ret-cta:hover { text-decoration: underline; }
@media (max-width: 520px) { .fc-ret-cta { margin-left: 0; } }
.fc-gate--flat { color: var(--text-muted); }

/* ── reserved heights (CLS) ─────────────────────────────────────────────── */
/* Both boxes are empty in the HTML and filled by JS, which grew them ~250px each and put
   /forecast at CLS 0.256 (Google's "good" bar is 0.10). Measured with Playwright rather than
   guessed: tape 272px desktop / 254px mobile, detail panel 295px / 460px. Reserve the loaded
   height so the fill paints INTO the box instead of pushing the page down.
   min-height, not height — a species with a long name may wrap to one row taller, and clipping
   is worse than a 1-row shift. */
.fc-tape { min-height: 272px; }
#fc-detailcard { min-height: 295px; }
@media (max-width: 640px) {
    .fc-tape { min-height: 265px; }
    /* 520, not 460: the panel's height depends on the species. The ~20 species that ride the
       general day curve carry an extra explanatory note (measured 520px); the 5 with their own
       measured day pattern do not (463px). Reserving the LARGER of the two means neither case
       resizes the box — the shorter one simply leaves whitespace, which is invisible, whereas a
       57px jump on every second selection is not. Re-measured at 538 once the best-day
       label was added — it adds a line to the week strip. */
    #fc-detailcard { min-height: 538px; }
}
/* The axis labels the ordering (absolute rate); this line labels the numbers (own average).
   Two different quantities on one strip, so both get named. */
.fc-tape-foot {
    padding: 0 16px 12px; font-size: .6875rem; color: var(--text-faint);
}

/* The axis reads BEFORE the cards now, so it needs to look like a heading for the row rather
   than a caption under it. */
.fc-tape-rule.is-above { margin: 0 16px 8px; }

/* ── location typeahead ─────────────────────────────────────────────────── */
/* Replaces the province <select>. The list is absolutely positioned so opening it cannot push
   the page down — this block sits above the fold on mobile and a reflow here would land
   straight in CLS. */
.fc-place { position: relative; flex: 1 1 220px; min-width: 0; }
.fc-place-input {
    width: 100%; padding: 9px 12px; font-size: .875rem;
    color: var(--text-bright); background: var(--bg-elevated);
    border: 1px solid var(--border); border-radius: var(--radius);
}
.fc-place-input:focus { outline: none; border-color: var(--accent); }
.fc-place-list {
    position: absolute; z-index: 40; top: calc(100% + 4px); left: 0; right: 0;
    margin: 0; padding: 4px; list-style: none; max-height: 260px; overflow-y: auto;
    background: var(--bg-card); border: 1px solid var(--border-light);
    border-radius: var(--radius); box-shadow: 0 8px 24px rgba(0,0,0,.45);
}
.fc-place-list[hidden] { display: none; }
.fc-place-list li {
    display: flex; align-items: baseline; gap: 8px; justify-content: space-between;
    padding: 7px 10px; border-radius: 6px; cursor: pointer; font-size: .8125rem;
    color: var(--text);
}
.fc-place-list li:hover, .fc-place-list li.is-on { background: var(--accent-dim); color: var(--accent); }
.fc-place-n { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fc-place-t { font-size: .625rem; color: var(--text-faint); text-transform: uppercase; letter-spacing: .05em; }

/* ── maintenance state ──────────────────────────────────────────────────── */
/* .forecast-maintenance itself (wave icon, title, pulsing dots) already lives in style.css and
   is reused unchanged — it was written for the pre-rebuild page and only lost its call site.
   This adds the one line the new layout needs: what IS still accurate while the daily update
   runs, so the panel does not read as "the whole page is broken". */
.forecast-maintenance-still {
    margin: 18px 0 0; max-width: 46ch;
    font-size: .8125rem; line-height: 1.5; color: var(--text-faint);
}

/* ── loading skeletons ──────────────────────────────────────────────────── */
/* Server-rendered, because nothing on this page runs until `await window.__i18nReady`
   resolves at ~700ms — a JS-injected skeleton would appear at the same moment as the content
   it stands in for. Measured empty windows before this: tape 709ms local / 1,969ms Fast 3G,
   detail panel 722ms / 1,991ms.
   The shimmer itself is the shared `.skeleton` from style.css, which already honours
   prefers-reduced-motion. These rules only give the blocks the right dimensions so the
   skeleton and the real card occupy identical space and CLS stays at 0. */
.fc-tcard--sk { cursor: default; }
.fc-tcard--sk:hover { transform: none; border-color: var(--border); }
.fc-tsk-img { display: block; width: 100%; height: 104px; border-radius: 0; }
/* The real card's lines carry their own margins; the bars inherit those boxes, so only the
   gaps between them need restating. */
.fc-tcard--sk .fc-tname { margin-bottom: 6px; }
.fc-tcard--sk .fc-tpct { margin: 2px 0 3px; }
.fc-tcard--sk .fc-tspark { display: block; margin-top: 6px; }

.fc-dtsk-photo { position: absolute; inset: 0; border-radius: 0; }
.fc-dtsk-sil { display: block; width: 100%; height: 56px; }
.fc-dt-photo .fc-dt-scrim .fc-dt-name .skeleton,
.fc-dt-photo .fc-dt-scrim .fc-dt-sub .skeleton { background-color: rgba(255,255,255,.14); }

.fc-wsk-img { display: block; width: 88px; height: 50px; border-radius: 5px; flex: none; }
@media (max-width: 640px) { .fc-wsk-img { width: 56px; height: 32px; } }

/* ── best day of the week ───────────────────────────────────────────────── */
/* Only drawn when bestDay() reports a real spread — a flat week gets no winner, because
   marking one there would present rounding noise as a recommendation. The outline is the
   green accent, NOT the blue one: blue is reserved for "now" everywhere on this page, and a
   blue ring here would read as today rather than as best. */
.fc-st--best {
    border-color: var(--accent-green) !important;
    box-shadow: 0 0 0 1px var(--accent-green);
    position: relative;
}
.fc-bestlbl {
    margin-top: 3px;
    font-size: .5rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
    color: var(--accent-green); line-height: 1.2;
}
/* The label adds a line, so every cell in the strip reserves room for it — otherwise the
   winning cell would be taller than its neighbours and the row would sit unevenly. */
.fc-strip.is-week .fc-st { padding-bottom: 4px; }
.fc-strip.is-week .fc-st::after {
    content: ""; display: block; height: 12px;
}
.fc-strip.is-week .fc-st--best::after { display: none; }

/* ── weather in the week-grid header ────────────────────────────────────── */
/* Says WHY one day differs from the next — the grid scores every species per day but never
   showed the conditions behind them.
   Sized from measurement: at 15px in a 188px-wide cell (68px on mobile) the glyph was smaller
   than the 12px day label beside it, and painted in --text-faint it was the lowest-contrast
   thing on the page. Both are why it could not be read. 26px desktop / 24px mobile, one step
   up in contrast. Still quieter than the percentages — it is context for the numbers, not a
   competing row of data. */
.fc-wx {
    display: inline-flex; align-items: center; gap: 5px;
    margin-top: 5px; color: var(--text-muted);
}
.fc-wx svg { width: 26px; height: 26px; flex: none; }
.fc-wxt { font-size: .75rem; font-variant-numeric: tabular-nums; }
/* Today's column reads brightest — it is the one you are most likely to act on. */
.fc-grid th.is-cur .fc-wx { color: var(--text-bright); }
@media (max-width: 640px) {
    /* The cell is 68px here; a 24px icon plus "18°" still fits, so the temperature stays —
       at the old 15px it had to be dropped to make room. */
    .fc-wx { gap: 3px; margin-top: 4px; }
    .fc-wx svg { width: 24px; height: 24px; }
    .fc-wxt { font-size: .6875rem; }
}

/* Wind, in km/h with a plain-language bucket. Deliberately the quietest thing in the header:
   wind is ~3.5% of the model and this page's FAQ says so at length, so it must not read as a
   catch signal while sitting above a grid of catch percentages. It is here for the angler
   standing on an exposed bank.
   NO catch-palette colour on any bucket — green/orange mean catch chance everywhere else, and
   a green "calm" would say "good day to fish", the one thing wind does not support. The
   strongest bucket gets a brighter grey and nothing more. */
.fc-wind {
    /* flex, not inline-flex: inline it sat beside the temperature in some columns and wrapped
       to its own line in others, purely on where the text happened to break — the same header
       laid out two different ways across seven cells. Its own row every time. */
    display: flex; align-items: baseline; justify-content: center; gap: 4px;
    margin-top: 3px; color: var(--text-muted);
    /* The header cell is uppercased (for DO / VR / ZA), which was shouting the unit as
       "8 KM/U". Units are lowercase. */
    text-transform: none;
}
/* Same size family as the weather glyph beside it (26px). At 14px in --text-faint this read
   as a rendering fault rather than as secondary information — two icons on one line at half
   the scale of each other look broken. De-emphasis is carried by the SMALLER TEXT and the
   absence of any catch-palette colour, not by making the glyph unreadable. */
.fc-wind svg { width: 22px; height: 22px; flex: none; align-self: center; }
.fc-windv { font-size: .6875rem; font-variant-numeric: tabular-nums; white-space: nowrap; }
.fc-windw { font-size: .625rem; text-transform: lowercase; white-space: nowrap; opacity: .85; }
.fc-wind--gale { color: var(--text); }
/* Today's column brightens the weather glyph, so the wind has to come up with it — left muted
   it sat as the one dim thing in an otherwise highlighted cell. Still a step below the
   weather, which keeps the ordering without opening a visible gap. */
.fc-grid th.is-cur .fc-wind { color: var(--text); }
@media (max-width: 640px) {
    .fc-wind { gap: 3px; margin-top: 2px; flex-wrap: wrap; justify-content: center; }
    .fc-wind svg { width: 20px; height: 20px; }
    .fc-windv { font-size: .625rem; }
    .fc-windw { font-size: .5625rem; }
}

/* The "In form right now" and "Het visjaar" card headers are <h2> so those sections have a
   passage boundary in the document outline — the visjaar is a third of this page's
   server-rendered words and 27 outbound species links, and with a <div> it appeared in no
   heading outline at all. The heading must not bring h2's default block layout, margins or
   font-size into a flex card-header, so it inherits everything from the parent. */
.card-header > .card-header-title {
    margin: 0;
    padding: 0;
    font: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    color: inherit;
}
