/* ──────────────────────────────────────────────────────────
   Feliz — base stylesheet
   Asian-minimalism: warm off-white, generous space, hairline
   dividers, single restrained accent. No shadows.
   ────────────────────────────────────────────────────────── */

/* LAADTIJD-JE-LAADT-ALLEEN-DE-TAB-DIE-JE-OPENT — hier stond
   `@import url('offline-pill.css')`. Een @import bovenaan een stylesheet is een
   EXTRA blokkerende rondgang: de browser ontdekt offline-pill.css pas nadat
   app.css (883 KB) binnen is, dus de twee konden nooit naast elkaar lopen.
   offline-pill.css wordt nu direct gelinkt in index.html en cardio.html, VÓÓR
   app.css, zodat de cascade-volgorde exact gelijk blijft aan wat @import gaf.
   Zet hem hier niet terug: link hem in het document. */

/* v0.9.306 A11Y-2 — WCAG SC 2.3.3 / 2.2.2: honour the OS-level reduced-motion
   preference. Strips animation + transition + smooth-scroll across the whole
   SPA for users who set it (vestibular sensitivity, motion-sickness, AT). */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* v0.9.306 A11Y-2 — WCAG SC 2.4.7: every focusable element gets a visible
   keyboard-focus ring. :focus-visible (not :focus) so mouse-clicks don't
   paint the ring; keyboard navigation does. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* THEME-1-FINISH-REGISTRY (2026-06-13) — finish engine per
   docs/briefs/THEME_FINISH_ENGINE_SPEC_2026-06-13.md. The two v0.9.362
   presets become a six-finish registry: a finish is a curated token
   bundle ONLY — layout/type/spacing/icons never change per finish.
   Six finishes locked by Jayy (mock: _MOCK_theme_finishes_v1.html):
   nacht (factory default), dag (factory), gauge / linen / volt / pulse
   (premium). Old ids map forward: lime-night→volt, blue-day→dag.

   :root carries the structural tokens AND the nacht (default) appearance
   bundle, so first paint before the boot script lands on brand navy.
   Each finish gets its own :root[data-theme-preset="<id>"] block below.
   JS additionally stamps data-theme-mode="dark|light" on <html> so
   component-level mode overrides (further down + strength.html) apply to
   every dark/light finish without per-finish selector duplication. */
:root {
  /* Brand Nacht appearance tokens — "Sassu Ice" (COLOR_SYSTEM_RESEARCH
     2026-08-05 §3). The accent moved from a pastel (#A8D8EA, C≈0.07) to an
     electric cyan on the same hue family (H≈195, C≈0.16): the pastel read as
     a haze against the near-black canvas, which was the "no vibrance"
     complaint. --on-accent stays near-black because the accent keeps a high
     lightness (L≈75%) and white text fails on it — same luminance rule the
     strength-bridge enforced inline at v0.9.185. */
  --bg:        #03040A;
  --surface:   #0A0F1A;
  --ink:       #F0F2F5;
  --ink-2:     #ACB3C1;
  --muted:     #7B8291;
  --line:      #1E2533;
  /* DE-SECTIES-OP-EEN-SCHERM-ZIJN-ZICHTBAAR-GESCHEIDEN (2026-09-22) — the
     edge that says "a new section starts here", as opposed to --line, which
     is the quiet rule INSIDE a section (row stripes, in-card dividers).
     WCAG 1.4.11 asks 3:1 of a meaningful non-text boundary; --line measured
     1.15:1 against the card in nacht and 1.48:1 in dag, so every check-in
     card edge sat 2-8x under the bar in all six finishes. Raising --line
     itself would have turned every internal stripe into a fence, and
     shifting --surface-card would have re-broken the --muted sums the sister
     phase CONTRAST-DE-ZACHTE-TEKST-IS-OOK-IN-HET-DONKER-TE-LEZEN just
     landed. So: one extra token, one problem. Each finish picks a value that
     clears 3:1 against --surface-card, --surface AND --bg — the three planes
     the edge touches — so the border alone carries the separation and the
     1.10-1.16 plane difference has nothing to prove. The border stays 1px;
     only the colour changes. Consumers use var(--card-edge, var(--line)) so
     a preset that does not set it (cresco-nacht / cresco-dag) keeps an edge
     rather than losing one. */
  --card-edge: #52658B;
  --line-2:    #151B26;
  --accent:    #2AF0EA;
  --accent-2:  #A0F7F4;
  --on-accent: #03040A;
  --temptation-line: #f59e0b;
  /* THEME-2 brand-surface tokens — finish-INVARIANT. Brand front-door
     surfaces (landing) render Deep Navy + Ice in every finish per
     APP_BRAND_MEDIA_UX_SPEC §0; these never get per-finish overrides. */
  --brand-navy: #080C14;
  --brand-ink:  #DCE8F0;
  --brand-ice:  #A8D8EA;
  --brand-dim:  #7A8AA0;
  /* BRAND-HORSE-HEAD plate + glyph — finish-INVARIANT navy disc behind the
     horse silhouette, and the canonical ice-blue silhouette itself. Both are
     brand-moment literals (the horse mark is a brand-moment artifact on its own
     fixed palette, identical to the auth-lockup brand treatment — no finish
     recolouring), same precedent as --brand-navy (lives in the :root token
     block, which css-hex-lint exempts). */
  --horse-plate: #0E1320;
  --horse-glyph: #A8D8EA;
  --danger:    #FF1744;
  /* v0.9.911 NUTRITION-PANTRY-METER-CLEANUP — semantic amber warning, used
     for the negative-stock clip border on pantry meters; lives in the :root
     token block (css-hex-lint exempt) so var(--warning) resolves everywhere. */
  --warning:   #D97706;
  /* v0.9.314 STRENGTH-BADGE-RED-RECOLOR — iOS-system-red notification dot.
     Preset-agnostic: the tabbar surface is dark in both presets, so the
     badge red is constant (no per-preset override). */
  --badge-bg:  #FF3B30;
  --on-badge:  #FFFFFF;
  --ok:        #00E676;
  /* v0.9.255 — semantic DOMAIN colours, independent of the preset --accent.
     COLOR-3: re-valued onto the chroma-capped Sassu Ice domain hues
     (training green H145, nutrition amber H55, mood lavender H300). Training
     left the blue family because the new signature --accent is cyan H195 —
     a blue domain hue sat close enough to read as "the accent, dimmed".
     Mood previously borrowed var(--accent) outright, which is exactly the
     collision the one-domain-hue-per-screen rule exists to stop; it now owns
     a hue. These hexes are the sRGB fallback — the @supports block below
     restates all three in oklch() for browsers that have it. */
  --training:  #67BB6B;
  --on-training: #0E1726;
  --nutrition: #E78A45;
  --mood:      #AE96DA;
  /* NUTRITION-HYDRATION-CARD-AND-INTAKE-WATER-GOAL — water = semantic blue,
     deliberately distinct from the amber --nutrition macro accent. Every finish
     restates this (SASSU-DESIGN-GOLF-A fix #2) — a theme-invariant blue was one
     of the three colours that stayed put while the rest of the app re-skinned. */
  --hydration: #4EA8DE;
  /* DAGRITME-EEN-CIJFER-VAN-0-TOT-10-VUL-JE-IN-DE-RIJ-IN — de regenboog onder
     een Cijfer-rij. Jayy (L55): "met wat kleur zoals een regenboog, zodat het
     duidelijk anders is dan de rest" — de kleur is hier dus GEEN status maar een
     plaatsaanduiding: hij zegt "dit is een schuif", niet "dit is fout". Daarom
     zes vaste stops in plaats van --danger/--warning/--ok: die drie dragen
     betekenis, en een 3 die rood kleurt zou een gevoelscijfer in een oordeel
     veranderen. Afwerking-INVARIANT (zoals --brand-navy en --hydration): een
     regenboog die per thema verschuift is geen schaal meer om op te mikken. */
  --scale-a:   #FF5C7A;
  --scale-b:   #FF9F43;
  --scale-c:   #FFD43B;
  --scale-d:   #63E6A0;
  --scale-e:   #3BC9DB;
  --scale-f:   #9775FA;
  /* De duim en zijn ring. De ring is met opzet vast donker en volgt --ink NIET:
     op een lichte afwerking zou --ink de duim wél omranden maar de rand tegen de
     gele stop op ~1,3:1 zetten, en dan is de duim op precies het midden van de
     schaal onzichtbaar. Eén donkere ring haalt overal 3:1 tegen alle zes stops. */
  --scale-thumb:      #FFFFFF;
  --scale-thumb-ring: #03040A;
  /* DAGRITME-DE-GEVOELSBALK-LACHT-MEE — de witte sluier over de zes stops zodra
     een stop ACHTER TEKST komt te liggen (het schijfje met het cijfer erin). De
     stops zelf zijn gekozen om naast elkaar te liggen, niet om inkt te dragen:
     een rij met een antwoord erin is een `--row--done` en staat op opacity 0.62,
     en een 62%-versie van de oranje stop zet donkere inkt op 4,4:1 — onder de
     grens. Verbleken in plaats van verzadigen, want de TINT moet blijven zeggen
     welke kant van de baan je koos; dat is de hele reden dat het schijfje de
     baankleur draagt. Ook invariant: een sluier die per afwerking verschuift
     geeft per afwerking een andere verhouding om te bewaken. */
  --scale-tint: rgba(255, 255, 255, 0.6);
  /* DAGBOEK-JE-DAGDOELEN-ZIE-JE-ZONDER-TE-LEZEN — de gehaalde dagdoelen worden
     gevulde schijfjes in de kleur van het doel, en die kleuren zijn met opzet de
     legenda van de weekgrafiek erboven: kcal = --nutrition, eiwit = --accent,
     krachttraining = --training, water = --hydration, slaap = --mood. Stappen
     was het enige onderdeel zonder eigen kleur en krijgt er hier één. Het icoon
     IN de schijf is --surface, dus donker op een donkere afwerking en wit op een
     lichte; daarom zetten de lichte afwerkingen dit goud verderop dieper —
     een bleke gouden schijf met een wit voetje erin is geen voetje meer. */
  --goal-steps: #D9A82B;
  /* De gouden stempel op een dag waarop ALLES gehaald is, plus zijn inkt. Dit
     paar staat maar op ÉÉN plek en wordt door geen enkele afwerking herschilderd:
     het goud is in elke afwerking hetzelfde goud (een stempel is een stempel) en
     de inkt erop is daarom in elke afwerking dezelfde donkere. Zou de inkt
     --on-accent zijn, dan werd hij op de lichte afwerkingen bijna-wit op goud. */
  --goal-all:    #F5C542;
  --on-goal-all: #3B2A00;
  /* Low-but-valid progress (<60% of a goal). Deliberately NOT --danger: an
     unfinished ring is not a failure, and the alarm red next to the #FF3B30
     badge was the loudest false-signal on the start screen. Muted, in-family
     per finish so it recedes rather than shouts. */
  --progress-low: #5F7896;
  /* v0.9.326 A11Y-12 S38 — nutrition macro tokens lifted for dark bg.
     COLOR-3: the macro triad is now one harmonised set — equal lightness,
     chroma capped ~0.13, separated by hue alone — instead of three neons at
     three different lightnesses. --mac-p completes the set; the protein bar
     used to reach for the generic --ok, which is 0.21 chroma and the single
     loudest colour on the Nutrition screen. */
  --mac-p:     #57BC80;
  --mac-c:     #E1A035;
  --mac-f:     #E37F65;
  --fiber:     #4DB6AC;
  --protein-2: #3DDC84;
  --favorite:  #FF8F00;
  --on-danger: #FFFFFF;
  /* v0.9.328 — dark lift instead of dark darken. */
  --surface-muted: rgba(255, 255, 255, 0.04);
  /* v0.9.368 STRENGTH-EDIT-MODE-MULTI-EXERCISE-GROUPING — subtle accent tint
     used for the "selected" row highlight in Edit mode. The 2026-06-01
     contrast addendum: a full --accent fill made labels disappear in the
     prior pair-only flow, so the selected-row state lives at ~20% accent
     alpha on dark and ~14% on light. Label keeps --ink/--text legible. */
  --accent-subtle: rgba(42, 240, 234, 0.20);
  /* Group palette — group_id (1..N) → color_token + on-color_token. Eight
     slots cycle on group_id. None equals --accent so the group channel
     reads separately from the active-state accent. Tokens hold on both
     presets; on-group-N inverts because the swatch is used at high
     saturation on chip+row-bar. Visual lock: _MOCK_strength_edit_mode_
     grouping_v1.html (2026-06-02). */
  --group-1: #00E5FF; --on-group-1: #001517;
  --group-2: #FFB300; --on-group-2: #1A0C00;
  --group-3: #B388FF; --on-group-3: #0E0420;
  --group-4: #FF7A6B; --on-group-4: #2A0904;
  --group-5: #1DE9B6; --on-group-5: #002115;
  --group-6: #76FF03; --on-group-6: #0A1C00;
  --group-7: #FF4081; --on-group-7: #33000F;
  --group-8: #536DFE; --on-group-8: #001033;
  /* v0.9.347 SETTINGS-IOS-STYLE-RESTYLE — iOS-Dark grouped-list tokens.
     systemGroupedBackground (#000) sits BELOW the card in luminance — the
     inverse of the light pattern (light has the screen grey + card white). */
  --surface-bg:        #05070D;
  --surface-card:      #111827;
  --divider:           #232B3A;
  --toggle-track-off:  #29323F;
  --toggle-thumb-off:  #FFFFFF;
  --section-label:     #9CA3AF;
  color-scheme: dark;

  --font-sans: -apple-system, BlinkMacSystemFont, "Inter", "Helvetica Neue", Helvetica, Arial, sans-serif;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;

  /* SHAPE SCALE — the ONLY four corner radii in the app. Before
     SASSU-DESIGN-GOLF-A there were 34 distinct radius declarations including
     19 raw px literals (2,3,5,7,9,11,13,18,20,99,999,9999px) and both 999px
     AND 9999px for the same pill; a per-component free-for-all is the single
     loudest "generated, not designed" tell. The three scalar values are the
     three that were already dominant (4px / 8px / 12px), so the large majority
     of components render pixel-identical to before — this is a consolidation,
     not a redesign. All three sit on the 4px grid.
       --radius-sm    input, swatch, tight inner element
       --radius-md    button, chip-with-corners, list row, small tile
       --radius-lg    card, panel, sheet, modal, dialog
       --radius-pill  anything whose radius should equal half its height
     Percentage radii (50% circle, 22% brand-plate squircle) stay percentages
     on purpose: they scale with the element, which no fixed token can do. */
  --radius-sm:   4px;
  --radius-md:   8px;
  --radius-lg:   12px;
  --radius-pill: 999px;

  /* MOTION — one signature for the whole app (SASSU-DESIGN-GOLF-D).
     Before this there were 33 duration variants and 8 easing curves, so no
     two surfaces moved alike and the app read as assembled rather than
     designed. Motion is finish-INVARIANT: these live only here, never in a
     preset override, because how fast a sheet opens is not a colour choice.
       --dur-fast   status/feedback — hover, focus, chip, small state flip
       --dur-base   transitions — sheet, overlay, tab, bar, height/opacity
       --dur-slow   one-shot choreography — entrance, celebration, flash
     The two below are PERIODS, not transition speeds: a spinner's revolution
     and a pulse's breath are functional rates, so they are carved out of the
     two-speed rule rather than crushed into it (a 200ms spinner is a strobe).
       --dur-loop   one revolution / one tick-smoothing cycle
       --dur-pulse  one breath of a looping pulse
     Easing collapses to a single decelerate curve. `linear` survives as the
     only second curve because constant rate is a requirement, not a taste:
     rotation and progress fills stutter under any eased curve. */
  --dur-fast:  120ms;
  --dur-base:  200ms;
  --dur-slow:  500ms;
  --dur-loop:  800ms;
  --dur-pulse: 1600ms;
  --ease-standard: cubic-bezier(0.2, 0, 0, 1);

  /* M3 type scale (roles) — one fixed size + line-height per role so the
     nutrition surface normalises to a system instead of raw px 10-32. */
  --text-label:    11px;  --lh-label:    1.3;
  --text-body-sm:  12px;  --lh-body-sm:  1.4;
  --text-body:     14px;  --lh-body:     1.45;
  --text-title:    16px;  --lh-title:    1.3;
  --text-headline: 24px;  --lh-headline: 1.25;

  /* Overlay scrim — pure black alpha, NOT a tinted grey. A tinted scrim
     desaturates the white cards showing through it, which is why the Dag hub
     read washed-out (_AUDIT_hub_dag.png). Black alpha only darkens. */
  --scrim: rgba(0, 0, 0, 0.5);

  /* HAIRLINE + GLOW (COLOR-2-GLOW-ELEVATION, research §2/§3) — the two
     primitives the elevation ladder below is built from. A hairline is a
     1px light-on-dark (or ink-on-light) edge that says "this is a separate
     plane"; a glow is a large-blur, low-opacity accent bloom that says
     "this plane is live". Glows are derived from --accent so every preset
     gets its own signature bloom for free. They are deliberately expensive
     to spend: OLED burn, battery and gradient banding all scale with glowing
     area, so glow belongs on interactive / active / PR moments only, never
     as ambient decoration. */
  --hairline:        rgba(255, 255, 255, 0.06);
  --hairline-strong: rgba(255, 255, 255, 0.12);
  --glow-soft: 0 0 16px color-mix(in srgb, var(--accent) 20%, transparent);
  --glow-hard: 0 0 24px color-mix(in srgb, var(--accent) 35%, transparent);

  /* ELEVATION LADDER — three rungs plus one upward-cast rung, per finish.
     Before SASSU-DESIGN-GOLF-A there were 53 ad-hoc box-shadow variants of
     which exactly ONE ever reached the screen, so there was no readable
     depth order at all. Assignment is semantic, not decorative:
       --elev-1  resting card / row / chip
       --elev-2  raised transient: popover, dropdown, FAB, drag-lift
       --elev-3  modal / dialog / fullscreen overlay panel
       --elev-up bottom-docked surfaces that rise from the edge (sheet, tabbar)
     The neutral drop-shadow is retired as the elevation language: it read as
     generic-default chrome and barely registered on a near-black canvas.
     Depth is now hairline-at-rest, hairline+glow once a plane is raised, so
     the ladder is legible on both finishes without any grey haze. */
  --elev-1:  0 0 0 1px var(--hairline);
  --elev-2:  0 0 0 1px var(--hairline), var(--glow-soft);
  --elev-3:  0 0 0 1px var(--hairline-strong), var(--glow-soft);
  --elev-up: 0 -1px 0 0 var(--hairline), var(--glow-soft);

  /* FOCUS / SELECTION RINGS — zero-blur spread rings. Deliberately separate
     from the elevation ladder: a ring is an outline substitute (it never
     shifts layout the way border-width does), not a depth cue. The ring
     itself stays crisp; the glow rides behind it so focus reads at a glance
     without thickening the outline. */
  --ring-focus:  0 0 0 3px var(--accent-subtle), var(--glow-soft);
  --ring-accent: 0 0 0 2px var(--accent), var(--glow-soft);
  /* Inset twin — for cells that must not paint outside their own box, so it
     stays glow-free by definition. */
  --ring-accent-inset: inset 0 0 0 2px var(--accent);
  /* "You are here" double ring: surface gap, then accent halo. The single
     strongest positional signal in the app, so it carries the hard glow. */
  --ring-current: 0 0 0 2px var(--surface), 0 0 0 4px var(--accent), var(--glow-hard);

  /* NAV-CHAT-TAB-HUB-KEBAB-AND-20PCT-SMALLER — bottom-nav shrunk ~20% (was 96px)
     so the menu is less present, especially on small phones. safe-area inset is
     added separately (padding-bottom: --safe-bottom) so the home-indicator gap
     is untouched. */
  --tabbar-h: 86px;
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  /* v0.9.309 — A11Y-4 S6: iPhone 14+ notch + Dynamic Island.
     Auth wordmark/Sign-in form was overlapping the camera cutout. */
  --safe-top:    env(safe-area-inset-top,    0px);
  --safe-left:   env(safe-area-inset-left,   0px);
  --safe-right:  env(safe-area-inset-right,  0px);

  /* v0.9.375 CARDIO-MAP-LEAFLET-THEMABLE — per-tenant map theming tokens
     (spec §4.2). Live polyline tracks --accent (preset-aware); current-
     position marker stays a constant dark slate so it reads on the lime
     accent without re-mapping per preset. Planned-route overlay (dashed)
     uses a soft slate-blue so it doesn't compete with the live polyline.
     --cardio-tile-filter is empty by default; a tenant override sets
     e.g. `sepia(0.3) hue-rotate(220deg)` to brand-shift the OSM raster
     tiles without touching JS. The PwaMapProvider reads these via
     getComputedStyle on the map root every call, so a runtime theme
     swap recolours the next stroke automatically. */
  --cardio-polyline:    var(--accent);
  --cardio-marker:      #1A1A2E;
  --cardio-planned:     #5B8DEF;
  --on-planned:         #FFFFFF;
  --cardio-tile-filter: none;

  /* DAY-CYCLE-REFINEMENTS (v0.9.466) — SLEEP phase rows render against a
     deliberately darker background so the "night band" reads as a
     separator between EVENING and MORNING. Dark preset leans into the
     night metaphor; light preset gets a soft cool-gray tint. */
  --day-cycle-sleep-bg: #070A12;

  /* DAY-CYCLE-FULL-TOOL-AND-WIDGET-VISUAL-POLISH (v0.9.484) — per-phase
     thin left-bar accents that segment the EVENING / SLEEP / MORNING
     cards. Dusk indigo / deep slate / warm amber — the temperature
     gradient nudges the visual story of the day. Slippage pill palette
     stays monotone within each kind: late = soft indigo bg + indigo fg,
     early = soft green bg + green fg. Nacht preset (default :root).
     css-hex-lint exempt — all hex sits inside the :root token block. */
  --day-cycle-evening-accent: #6E7BFF;
  --day-cycle-sleep-accent:   #3F4859;
  --day-cycle-morning-accent: #F4A946;
  --slip-late-bg:  rgba(110, 123, 255, 0.18);
  --slip-late-fg:  #BFC6FF;
  --slip-early-bg: rgba(0, 230, 118, 0.18);
  --slip-early-fg: #5EE19A;

  /* MOOD-ANALYTICS-RAINBOW-AND-POLISH (v0.9.474) — semantic mood encoding
     palette, mirrors public/assets/js/calendar/_helpers.js MOOD_COLOR_STOPS.
     Drives the scatter X-axis rainbow background and the dot fills. The
     six stops map to mood scores 1, 3, 5, 6, 8, 10 along the X axis
     (red → orange → yellow → green → blue). Background opacity is
     preset-aware: nacht reads punchier on the dark surface, dag stays
     muted-pastel against the light surface. */
  --mood-rainbow-1:  #DC3C3C;
  --mood-rainbow-3:  #EB8C28;
  --mood-rainbow-5:  #E6C81E;
  --mood-rainbow-6:  #B4D21E;
  --mood-rainbow-8:  #3CB950;
  /* COLOR-3 — stop 9 exists to route the scale AROUND sRGB's dead zone, not
     to add a new semantic step. An SVG <linearGradient> always interpolates
     in sRGB (there is no oklch option on that element), and the 8→10 leap is
     the one long hue arc in this scale: green H146 → blue H256. Straight-line
     sRGB across that arc dips through a desaturated slate around the midpoint,
     so moods 8.5–9.5 rendered greyer than moods on either side of them — the
     scale visibly lost saturation exactly where it should read "good". This
     stop is the OKLCH midpoint of 8 and 10, which keeps chroma up through the
     turn; with it in place the two short sRGB segments track the perceptual
     path closely enough that the dip is gone. */
  --mood-rainbow-9:  #069FA6;
  --mood-rainbow-10: #3282E6;
  --mood-rainbow-bg-opacity: 0.32;

  /* Oranje accent tokens — Rep Max notes in the DAW sequencer + Dag-theme override. */
  --accent-orange:       #FB923C;
  --accent-orange-light: #4A2E10;

  /* NUTRITION-STREAK-DOTS-GOAL-COLOR — "logged but missed kcal goal" color
     for the 7-day consistency dots. Nacht's accent is lime, so the missed
     state uses a calm blue so the eye separates achieved vs missed at a
     glance. The Dag override below switches to black (blue IS Dag's accent). */
  --goal-missed: #5B8DEF;

  /* SEQUENCER-V2-DAW-OVERHAUL (v0.9.526) — teal loop-brace token. No
     existing Feliz hue mapped cleanly to "loop range marker"; teal reads
     on both Dag and Nacht surfaces. allow-hex */
  --loop: #2dd4bf;
}

/* THEME-1-FINISH-REGISTRY — the six finish token blocks. Canon rows
   (bg / surface / accent / on-accent / ink / dim) come straight from the
   spec table; every extra app token is derived inside the finish's family
   with the SAME derivation logic across finishes (line = bg lifted,
   surface-bg below surface in luminance, accent-2 = accent lifted,
   accent-subtle = accent at 20% dark / 12-14% light). Dark finishes
   inherit the dark semantic tokens (training/nutrition/macros/groups/
   day-cycle/mood) from the :root nacht bundle; light finishes restate the
   light semantic set. */

/* nacht — explicit block mirrors the :root default so a stamped
   data-theme-preset="nacht" is self-describing. */
:root[data-theme-preset="nacht"] {
  --bg:        #03040A;
  --surface:   #0A0F1A;
  --ink:       #F0F2F5;
  --ink-2:     #ACB3C1;
  --muted:     #7B8291;
  --line:      #1E2533;
  /* 3.04 : 1 on --surface-card, 3.28 on --surface, 3.50 on --bg. */
  --card-edge: #52658B;
  --line-2:    #151B26;
  --accent:    #2AF0EA;
  --accent-2:  #A0F7F4;
  --on-accent: #03040A;
  --accent-subtle: rgba(42, 240, 234, 0.20);
  --surface-bg:        #05070D;
  --surface-card:      #111827;
  --divider:           #232B3A;
  --toggle-track-off:  #29323F;
  --section-label:     #9CA3AF;
  --day-cycle-sleep-bg: #070A12;
  --hydration:     #4EA8DE;
  --progress-low:  #5F7896;
  color-scheme: dark;
}

/* dag — Brand Dag (factory, light), "Sassu Ice" values (research §3). The
   near-black navy accent (#16263F) is retired: it had zero chroma, so filled
   buttons dissolved into the dark text around them. Its replacement is a deep
   teal on the Nacht hue family — deliberately deeper and less saturated than
   the Nacht cyan, which would read as neon on white. Old stored id blue-day
   maps here in JS. */
:root[data-theme-preset="dag"] {
  --bg:        #F1F5F9;
  --surface:   #FFFFFF;
  --ink:       #0F172A;
  --scrim:     rgba(0, 0, 0, 0.34);
  /* Light finish: the hairline flips from white-on-dark to ink-on-light and
     is carried a touch heavier, because a 1px edge has to survive against a
     near-white page. The ladder itself (--elev-*) is inherited from :root —
     it is expressed purely in terms of these two tokens plus the accent
     glow, so a preset only ever restates the hairline. */
  --hairline:        rgba(15, 23, 42, 0.08);
  --hairline-strong: rgba(15, 23, 42, 0.14);
  --ink-2:     #475569;
  --muted:     #617086;
  --line:      #CBD5E1;
  /* 3.29 : 1 on --surface-card, 3.29 on --surface, 3.00 on --bg. */
  --card-edge: #7690B0;
  --line-2:    #E2E8F0;
  --accent:    #006E7D;
  --accent-2:  #00A8B5;
  --on-accent: #F0F9FF;
  --ok:        #00C853;
  /* COLOR-3 — light-finish domain hues: same hue family as the dark preset,
     dropped in lightness so they stay legible ON white rather than glowing
     off it (the §3 "Dag = dieper, minder verzadigd" rule). --on-training
     stays white because the green keeps the same L as the blue it replaced. */
  --training:  #38853E;
  --on-training: #FFFFFF;
  --nutrition: #B3621E;
  --mood:      #725B9A;
  --mac-p:     #249057;
  --mac-c:     #B37903;
  --mac-f:     #BE563D;
  --fiber:     #26A69A;
  --protein-2: #00C853;
  --favorite:  #FF6D00;
  --surface-muted: rgba(15, 23, 42, 0.04);
  --accent-subtle: rgba(0, 110, 125, 0.12);
  /* v0.9.368 group palette — light-tuned hues, same slot semantics. */
  --group-1: #00B8D4; --on-group-1: #001517;
  --group-2: #FF8F00; --on-group-2: #1A0C00;
  --group-3: #7C4DFF; --on-group-3: #FFFFFF;
  --group-4: #FF5252; --on-group-4: #FFFFFF;
  --group-5: #00BFA5; --on-group-5: #002115;
  --group-6: #64DD17; --on-group-6: #0A1C00;
  --group-7: #F50057; --on-group-7: #FFFFFF;
  --group-8: #304FFE; --on-group-8: #FFFFFF;
  --surface-bg:        #E2E8F0;
  --surface-card:      #FFFFFF;
  --divider:           #CBD5E1;
  --toggle-track-off:  #E2E8F0;
  --toggle-thumb-off:  #FFFFFF;
  --section-label:     #515E71;
  color-scheme: light;
  --cardio-polyline:    var(--accent);
  --cardio-marker:      #1A1A2E;
  --cardio-planned:     #5B8DEF;
  --on-planned:         #FFFFFF;
  --cardio-tile-filter: none;
  --day-cycle-sleep-bg: #DDE4EC;
  --day-cycle-evening-accent: #5B6BFF;
  --day-cycle-sleep-accent:   #6B7280;
  --day-cycle-morning-accent: #E6A042;
  --slip-late-bg:  rgba(91, 107, 255, 0.14);
  --slip-late-fg:  #3D4BCC;
  --slip-early-bg: rgba(0, 200, 83, 0.14);
  --slip-early-fg: #00A347;
  --mood-rainbow-bg-opacity: 0.18;
  --accent-orange:       #D97706;
  --accent-orange-light: #FFE8D6;
  /* Soft blue, deliberately outside the teal accent family so the streak
     "missed" state never reads as an accent highlight. */
  --goal-missed: #5B8DEF;
  /* Deeper than the dark-finish blue — the dark value is used as TEXT on a
     white card and fails contrast there. */
  --hydration:     #1D7FC0;
  /* Dieper dan het donkere goud, om dezelfde reden als de blauwe hierboven: op
     een lichte afwerking is het icoon in de schijf wit. */
  --goal-steps:    #7A5D00;
  --progress-low:  #7A8A9C;
}

/* gauge — premium, dark, amber instrument-cluster accent. */
:root[data-theme-preset="gauge"] {
  --bg:        #101113;
  --surface:   #191B1E;
  --ink:       #E6E4E0;
  --ink-2:     #BAB9B5;
  --muted:     #898881;
  --line:      #2B2D31;
  /* 3.01 : 1 on --surface-card, 3.17 on --surface, 3.47 on --bg. */
  --card-edge: #656A73;
  --line-2:    #232529;
  --accent:    #FFB454;
  --accent-2:  #FFC679;
  --on-accent: #1A1107;
  --accent-subtle: rgba(255, 180, 84, 0.20);
  --surface-bg:        #0A0B0D;
  --surface-card:      #1D2024;
  --divider:           #34373C;
  --toggle-track-off:  #34373C;
  --section-label:     #A6A5A0;
  --day-cycle-sleep-bg: #141518;
  /* Cooled-down against the warm amber cluster — water must still read as
     water, but desaturated so it doesn't fight the accent. */
  --hydration:     #6BADC9;
  --progress-low:  #857C6E;
  color-scheme: dark;
}

/* linen — premium, light, warm paper + soft umber accent. */
:root[data-theme-preset="linen"] {
  --bg:        #F7F4EE;
  --surface:   #FFFFFF;
  --ink:       #262219;
  --scrim:     rgba(0, 0, 0, 0.34);
  /* Warm light finish — same inherited ladder, hairline tinted into umber. */
  --hairline:        rgba(38, 34, 25, 0.08);
  --hairline-strong: rgba(38, 34, 25, 0.14);
  --ink-2:     #4C473F;
  --muted:     #756E5E;
  --line:      #E6E1D6;
  /* 3.31 : 1 on --surface-card, 3.31 on --surface, 3.02 on --bg. */
  --card-edge: #9F8B61;
  --line-2:    #EFEBE2;
  --accent:    #4A4438;
  --accent-2:  #5C5547;
  --on-accent: #F7F4EE;
  --ok:        #00C853;
  /* COLOR-3 — light-finish domain hues: same hue family as the dark preset,
     dropped in lightness so they stay legible ON white rather than glowing
     off it (the §3 "Dag = dieper, minder verzadigd" rule). --on-training
     stays white because the green keeps the same L as the blue it replaced. */
  --training:  #38853E;
  --on-training: #FFFFFF;
  --nutrition: #B3621E;
  --mood:      #725B9A;
  --mac-p:     #249057;
  --mac-c:     #B37903;
  --mac-f:     #BE563D;
  --fiber:     #26A69A;
  --protein-2: #00C853;
  --favorite:  #FF6D00;
  --surface-muted: rgba(38, 34, 25, 0.04);
  --accent-subtle: rgba(74, 68, 56, 0.12);
  --group-1: #00B8D4; --on-group-1: #001517;
  --group-2: #FF8F00; --on-group-2: #1A0C00;
  --group-3: #7C4DFF; --on-group-3: #FFFFFF;
  --group-4: #FF5252; --on-group-4: #FFFFFF;
  --group-5: #00BFA5; --on-group-5: #002115;
  --group-6: #64DD17; --on-group-6: #0A1C00;
  --group-7: #F50057; --on-group-7: #FFFFFF;
  --group-8: #304FFE; --on-group-8: #FFFFFF;
  --surface-bg:        #F1EDE4;
  --surface-card:      #FFFFFF;
  --divider:           #D8D2C4;
  --toggle-track-off:  #E6E1D4;
  --toggle-thumb-off:  #FFFFFF;
  --section-label:     #5C564D;
  color-scheme: light;
  --cardio-polyline:    var(--accent);
  --cardio-marker:      #1A1A2E;
  --cardio-planned:     #5B8DEF;
  --on-planned:         #FFFFFF;
  --cardio-tile-filter: none;
  --day-cycle-sleep-bg: #ECE7DC;
  --day-cycle-evening-accent: #5B6BFF;
  --day-cycle-sleep-accent:   #6B7280;
  --day-cycle-morning-accent: #E6A042;
  --slip-late-bg:  rgba(91, 107, 255, 0.14);
  --slip-late-fg:  #3D4BCC;
  --slip-early-bg: rgba(0, 200, 83, 0.14);
  --slip-early-fg: #00A347;
  --mood-rainbow-bg-opacity: 0.18;
  --accent-orange:       #D97706;
  --accent-orange-light: #FFE8D6;
  --goal-missed: #5B8DEF;
  /* Teal-leaning blue so water reads cool against warm paper without going
     saccharine; both deep enough for text on the white card. */
  --hydration:     #2E7E96;
  /* Dieper dan het donkere goud, om dezelfde reden als de blauwe hierboven: op
     een lichte afwerking is het icoon in de schijf wit. */
  --goal-steps:    #7A5D00;
  --progress-low:  #9A9080;
}

/* volt — premium, dark, lime. Pixel-identical to the retired "Lime Night"
   preset so existing users mapped lime-night→volt see ZERO change. */
:root[data-theme-preset="volt"] {
  --bg:        #121212;
  --surface:   #1E1E1E;
  --ink:       #E8E8E8;
  --ink-2:     #B0B0B0;
  --muted:     #878787;
  --line:      #333333;
  /* 3.10 : 1 on --surface-card, 3.04 on --surface, 3.41 on --bg. */
  --card-edge: #696969;
  --line-2:    #2A2A2A;
  --accent:    #ADFF00;
  --accent-2:  #C5FF33;
  --on-accent: #0A0A0A;
  --accent-subtle: rgba(173, 255, 0, 0.20);
  --surface-bg:        #000000;
  --surface-card:      #1C1C1E;
  --divider:           #38383A;
  --toggle-track-off:  #39393D;
  --section-label:     #9A9A9E;
  --day-cycle-sleep-bg: #131A22;
  --hydration:     #56B0E4;
  --progress-low:  #757A70;
  color-scheme: dark;
}

/* pulse — premium, dark, hot pink. */
:root[data-theme-preset="pulse"] {
  --bg:        #120A10;
  --surface:   #1E1119;
  --ink:       #F4E9EF;
  --ink-2:     #BEAFB7;
  --muted:     #967C8C;
  --line:      #32202B;
  /* 3.01 : 1 on --surface-card, 3.14 on --surface, 3.36 on --bg. */
  --card-edge: #875674;
  --line-2:    #281822;
  --accent:    #FF3B81;
  --accent-2:  #FF619A;
  --on-accent: #14060C;
  --accent-subtle: rgba(255, 59, 129, 0.20);
  --surface-bg:        #0A0509;
  --surface-card:      #231521;
  --divider:           #3C2834;
  --toggle-track-off:  #3C2834;
  --section-label:     #AE9AA6;
  --day-cycle-sleep-bg: #160D14;
  --hydration:     #4FA5D6;
  --progress-low:  #8A7280;
  color-scheme: dark;
}

/* cresco-nacht — CRESCO90-DE-HUISSTIJL-WORDT-EEN-THEMA. De donkere stand is de
   ÉCHTE Cresco-stand: Cresco90 is een donker merk. Elke waarde komt uit
   docs/CRESCO90_HUISSTIJL.md §1, gemeten aan de draaiende oude Cresco-app.
   Geen premium-finish: deze stand hoort bij MEEDOEN aan Cresco90, niet bij
   betalen — de kiezer in app.js toont hem alleen aan een deelnemer.

   --line / --line-2 staan hier OPAAK terwijl de huisstijl rgba(255,255,255,.08)
   noemt. Reden: 83 regels in dit bestand gebruiken var(--line*) als BACKGROUND,
   en een doorschijnende vulling stapelt daar anders dan een haarlijn. #242B38 en
   #1D2432 zijn exact diezelfde 8% en 5% wit, gecomposit over het kaartvlak
   #111827 — als lijn niet van de rgba te onderscheiden, als vlak voorspelbaar. */
:root[data-theme-preset="cresco-nacht"] {
  --bg:        #0A0F1A;
  --surface:   #111827;
  --ink:       #F3F4F6;
  --ink-2:     #D1D5DB;
  --muted:     #9CA3AF;
  --line:      #242B38;
  --line-2:    #1D2432;
  --accent:    #D6FF3F;
  --accent-2:  #E4FF7A;
  --on-accent: #0B0F14;
  --accent-subtle: rgba(214, 255, 63, 0.12);
  --surface-bg:        #05080F;
  --surface-card:      #1F2937;
  --divider:           #2B3546;
  --toggle-track-off:  #2B3546;
  --section-label:     #9CA3AF;
  --day-cycle-sleep-bg: #070B12;
  /* Steunblauw uit de Arena-prompt (#3B82F6), een slag opgelicht zodat water
     ook als TEKST op het kaartvlak leesbaar blijft naast de limoen. */
  --hydration:     #5B9BF0;
  --progress-low:  #6F7A5E;
  color-scheme: dark;
}

/* cresco-dag — de lichte Cresco-stand. Huisstijl §5 laat twee wegen open en
   deze fase heeft ze allebei gemeten:

     a. zoals de oude app: limoen helemaal weg, bijna-zwart (#0A0A0A) als accent;
     b. dezelfde kleurtoon, dieper getrokken richting olijf.

   Weg b haalt de contrasttoets ruim: #4F6B00 staat op 5.8:1 tegen --on-accent
   #F7FCE8, op 6.1:1 tegen het witte kaartvlak en op 5.5:1 tegen --bg #F2F4EC —
   alle drie boven AA. En de tint blijft van Cresco: 76° tegenover de 73° van
   #D6FF3F, dezelfde hoek van de kleurcirkel. Weg a was alleen nodig geweest als
   b was gezakt; hij is niet gezakt, dus de stand houdt zijn kleur.

   Een licht thema moet veel MEER tokens zetten dan een donker: :root is op
   donker afgestemd, dus alles wat hier niet staat erft een donkere waarde.
   Vorm en volgorde volgen bewust het linen-blok hierboven, zodat een volgende
   kleuraudit één patroon leest in plaats van drie. */
:root[data-theme-preset="cresco-dag"] {
  --bg:        #F2F4EC;
  --surface:   #FFFFFF;
  --ink:       #10151C;
  --scrim:     rgba(0, 0, 0, 0.34);
  --hairline:        rgba(16, 21, 28, 0.08);
  --hairline-strong: rgba(16, 21, 28, 0.14);
  --ink-2:     #47505C;
  --muted:     #5C6354;
  --line:      #D5DAC9;
  --line-2:    #E6EADC;
  --accent:    #4F6B00;
  --accent-2:  #6B8F00;
  --on-accent: #F7FCE8;
  --ok:        #00C853;
  --training:  #38853E;
  --on-training: #FFFFFF;
  --nutrition: #B3621E;
  --mood:      #725B9A;
  --mac-p:     #249057;
  --mac-c:     #B37903;
  --mac-f:     #BE563D;
  --fiber:     #26A69A;
  --protein-2: #00C853;
  --favorite:  #FF6D00;
  --surface-muted: rgba(16, 21, 28, 0.04);
  --accent-subtle: rgba(79, 107, 0, 0.12);
  --group-1: #00B8D4; --on-group-1: #001517;
  --group-2: #FF8F00; --on-group-2: #1A0C00;
  --group-3: #7C4DFF; --on-group-3: #FFFFFF;
  --group-4: #FF5252; --on-group-4: #FFFFFF;
  --group-5: #00BFA5; --on-group-5: #002115;
  --group-6: #64DD17; --on-group-6: #0A1C00;
  --group-7: #F50057; --on-group-7: #FFFFFF;
  --group-8: #304FFE; --on-group-8: #FFFFFF;
  --surface-bg:        #E8EBDE;
  --surface-card:      #FFFFFF;
  --divider:           #D5DAC9;
  --toggle-track-off:  #E2E6D6;
  --toggle-thumb-off:  #FFFFFF;
  --section-label:     #5C6354;
  color-scheme: light;
  --cardio-polyline:    var(--accent);
  --cardio-marker:      #1A1A2E;
  --cardio-planned:     #5B8DEF;
  --on-planned:         #FFFFFF;
  --cardio-tile-filter: none;
  --day-cycle-sleep-bg: #E4E8DA;
  --day-cycle-evening-accent: #5B6BFF;
  --day-cycle-sleep-accent:   #6B7280;
  --day-cycle-morning-accent: #E6A042;
  --slip-late-bg:  rgba(91, 107, 255, 0.14);
  --slip-late-fg:  #3D4BCC;
  --slip-early-bg: rgba(0, 200, 83, 0.14);
  --slip-early-fg: #00A347;
  --mood-rainbow-bg-opacity: 0.18;
  --accent-orange:       #D97706;
  --accent-orange-light: #FFE8D6;
  --goal-missed: #5B8DEF;
  --hydration:     #1D7FC0;
  /* Dieper dan het donkere goud, om dezelfde reden als de blauwe hierboven: op
     een lichte afwerking is het icoon in de schijf wit. */
  --goal-steps:    #7A5D00;
  --progress-low:  #7A8A9C;
}

/* ── COLOR-3-DATAVIZ-OKLCH — the perceptual layer ──────────────────────
   Every domain hex above is a gamut-clipped rendering of the oklch() value
   restated here. Two reasons this is a separate @supports block rather than
   oklch() written straight into the token rows:

   1. A custom property accepts any token sequence, so `--training: oklch(…)`
      would PARSE in a browser without oklch support and only fail later, at
      the var() use site — where the declaration is dropped entirely and the
      element loses its colour rather than falling back. Declaring hex first
      and overriding here is the only ordering where an old browser degrades
      to a colour instead of to nothing.
   2. It keeps the authored intent (L, C, H) readable next to the fallback,
      so the next chroma-cap audit reads the numbers it is auditing.

   Chroma is capped at ~0.14 by design: the accent (C 0.16) must stay the
   most saturated thing on any screen, or the "vibrance only in data" rule
   inverts and the domain colours start competing with the signature. */
@supports (color: oklch(50% 0.1 200)) {
  :root {
    --training:  oklch(72% 0.14 145);
    --nutrition: oklch(72% 0.14 55);
    --mood:      oklch(72% 0.10 300);
    --mac-p:     oklch(72% 0.13 155);
    --mac-c:     oklch(75% 0.14 75);
    --mac-f:     oklch(70% 0.13 35);
  }
  :root[data-theme-preset="dag"],
  :root[data-theme-preset="linen"],
  :root[data-theme-preset="cresco-dag"] {
    --training:  oklch(55% 0.13 145);
    --nutrition: oklch(58% 0.13 55);
    --mood:      oklch(52% 0.10 300);
    --mac-p:     oklch(58% 0.13 155);
    --mac-c:     oklch(62% 0.13 75);
    --mac-f:     oklch(58% 0.14 35);
  }
}

/* CRESCO90 MUNTKLEUREN — één kleur per pijler, Nacht en Dag.
   CRESCO90-ELKE-PIJLERDAG-IS-EEN-MUNT-IN-ZIJN-KLEUR, ontwerp §C2.

   GEMETEN, NIET AANGENOMEN. scripts/_bewijs-cresco90-muntkleuren.php rekent van
   alle acht de WCAG-verhouding uit tegen #111827 en tegen wit, en simuleert
   deuteranopie en protanopie (Viénot-Brettel-Mollon 1999, op lineair licht).
   Uitslag 2026-09-28: alle acht halen 3:1 op hun EIGEN ondergrond (Nacht
   6.76-9.24 tegen #111827, Dag 4.33-5.65 tegen wit), dus geen enkele hex is
   verschoven. 3:1 en niet 4.5:1 omdat een munt een GRAFISCH object is (WCAG
   1.4.11) en geen tekst — de tellers ernaast staan in --ink.

   WAT DE SIMULATIE VERTELT EN WAAROM DAT GOED IS. Het zwakste paar is
   kracht/mobiliteit onder protanopie (dE 16.2 in Nacht, 21.6 in Dag): blauw en
   orchidee schuiven naar elkaar toe. Dat is precies waarom het ontwerp zegt dat
   kleur NOOIT het enige signaal is — elke munt draagt het pijlericoon als masker
   en elke ring zijn eigen naam en teller. De kleur maakt een volle ring in één
   oogopslag leesbaar; hij hoeft hem niet alleen te dragen.

   LIMOEN STAAT HIER NIET. --accent is van geen pijler: alleen "vol", "gehaald"
   en "jij". Goud evenmin: dat is streep 3, plek 1 en een record van 90.

   :root en cresco-nacht staan in ÉÉN selectorlijst en niet als twee blokken met
   dezelfde waarden erin: twee plekken met hetzelfde getal is precies hoe een
   kleuraudit er straks één van de twee vergeet. De lichte lijst volgt het
   @supports-blok hierboven, zodat ook wie op "dag" of "linen" staat de munten in
   hun donkere variant ziet in plaats van een lichte kleur op een licht vlak. */
:root,
:root[data-theme-preset="cresco-nacht"] {
  --cr9-strength:  #4DA3FF;
  --cr9-cardio:    #FF7A59;
  --cr9-nutrition: #2ED3A0;
  --cr9-mobility:  #E27BD3;
  /* CRESCO90-DE-ARENA-SORTEERT-OP-JE-REEKS — DE DRIE RANGTEKENS van het bord.
     Precies de drie waarden uit het ontwerp (§D). Goud is hetzelfde goud als
     streep 3 en de kroon: dat is geen toeval maar dezelfde betekenis — dit is
     de bovenste plek. Ze staan hier als token en niet los in de regel, zodat
     een kleuraudit ze op één plek vindt. */
  --cr9-gold:      #FBBF24;
  --cr9-silver:    #D1D5DB;
  --cr9-bronze:    #B08D57;
}
:root[data-theme-preset="dag"],
:root[data-theme-preset="linen"],
:root[data-theme-preset="cresco-dag"] {
  --cr9-strength:  #1F6FD1;
  --cr9-cardio:    #C8471F;
  --cr9-nutrition: #0E8A66;
  --cr9-mobility:  #A43E97;
  /* Dezelfde drie tinten, donkerder, om dezelfde reden als de pijlers hierboven:
     zilver (#D1D5DB) is op een licht vlak geen kleur meer maar een vage vlek, en
     dan zou plek 2 onleesbaar zijn op precies het bord waar het om plekken gaat.
     Dezelfde kleurfamilies, alleen zwaarder ingezet. */
  --cr9-gold:      #B4820E;
  --cr9-silver:    #8B9096;
  --cr9-bronze:    #8C6E43;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overscroll-behavior-y: none;
}

body {
  font-weight: 400;
  letter-spacing: 0.005em;
}

button, input, select, textarea {
  font: inherit;
  color: inherit;
}

a {
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
}
a:hover { border-bottom-color: var(--ink); }

/* ───────── Boot splash ───────── */
.boot {
  /* v0.9.309 — A11Y-4 S7: svh ladder (vh → svh → dvh).
     iOS Safari shrinks the layout viewport when the URL bar collapses,
     pushing buttons off-screen if min-height tracks vh. svh is the
     small-viewport fallback for engines that don't grok dvh. */
  min-height: 100vh;
  min-height: 100svh;
  min-height: 100dvh;
  display: grid;
  place-items: center;
}
/* APP-BOOT-SPLASH-HORSE-ONLY-NO-WORDMARK — the boot splash is nameless: the
   ice-blue horse silhouette inside an indeterminate ring on the navy brand
   plate, no letters. Brand-moment tokens (--horse-plate / --horse-glyph) are
   finish-invariant by design, so the splash reads identically in Dag and
   Nacht. Under prefers-reduced-motion the ring stops turning but stays. */
.boot {
  background: var(--horse-plate);
}
.boot__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-5);
  /* Anker voor .boot__line hieronder — die hangt ABSOLUUT onder het embleem. */
  position: relative;
}

/* LAADSCHERM-TOONT-EEN-ZIN — één zin onder het embleem, voor wie tóch moet
   wachten (koude start, nieuwe shell, trage verbinding).
   ABSOLUUT GEPOSITIONEERD, en dat is de hele truc: de zin komt pas ná het
   eerste paint binnen (app.js leest hem uit localStorage). Stond hij in de
   flexkolom, dan zou het embleem op dat moment omhoog springen — een schok in
   precies het moment dat rust moet uitstralen. Zo verandert er niets aan de
   plek van het paard, of er nu een zin is of niet.
   Kleur: --horse-glyph op 88% over --horse-plate = 9,5:1 contrast (herrekend
   2026-09-10 met de WCAG-formule; de oude regel noemde 7,4:1 bij 72%, gemeten
   is dat 6,7:1 — meer dekking kan hier alleen beter worden). Beide zijn
   finish-invariante merk-tokens, dus de zin leest identiek in Dag en Nacht —
   --text-2 zou hier juist FOUT zijn, want die kantelt mee met het thema
   terwijl de plaat eronder navy blijft.
   NIET afkappen: geen line-clamp, geen ellipsis, geen overflow hidden. De
   langste redactionele zin is 111 tekens; die wil je heel lezen, niet half.
   LAADSCHERM-DE-ZIN-IS-TE-LEZEN (2026-09-10) — Jayy: "de teksten zijn heel
   klein". Groter (clamp 15-18px), dikker (500) en breder vak (360px), zodat
   die 111 tekens niet ineens vier regels worden. */
.boot__line {
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin: var(--space-6) 0 0;
  width: min(360px, 84vw);
  text-align: center;
  /* De zin is tekst om te lezen, geen doel om aan te tikken — en tijdens de
     uitveeg hieronder ligt hij nog een halve seconde over een app die al
     bedienbaar is. Hij mag daar geen tik opeten. */
  pointer-events: none;
  /* Geen font-family: er bestaat GEEN serif-token in dit bestand (alleen
     --font-sans), en een bare serif-stack invoeren zou een tweede lettertype
     op het merkscherm zetten dat nergens anders in de app voorkomt. */
  font-size: clamp(15px, 4.2vw, 18px);
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: .01em;
  color: color-mix(in srgb, var(--horse-glyph) 88%, transparent);
  opacity: 0;
  transition: opacity 150ms var(--ease-standard);
}
.boot__line--in { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .boot__line { transition: none; }
}
.boot__ring {
  position: relative;
  width: 128px;
  height: 128px;
  border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--horse-glyph) 22%, transparent);
  display: grid;
  place-items: center;
}
.boot__ring::before {
  content: '';
  position: absolute;
  inset: -2px;
  border-radius: 50%;
  border: 2px solid transparent;
  border-top-color: var(--horse-glyph);
  border-right-color: var(--horse-glyph);
  animation: boot-ring-spin 1.4s linear infinite;
}
.boot__horse {
  width: 72px;
  height: 72px;
  background-color: var(--horse-glyph);
  -webkit-mask: url('/assets/img/brand/horse-head-alpha-512.png') center / contain no-repeat;
  mask: url('/assets/img/brand/horse-head-alpha-512.png') center / contain no-repeat;
}
@supports not ((-webkit-mask: url('x')) or (mask: url('x'))) {
  .boot__horse {
    background-image: url('/assets/img/brand/horse-head-512.png');
    background-size: contain;
    background-position: center;
    background-repeat: no-repeat;
    background-color: transparent;
    -webkit-mask: none;
            mask: none;
  }
}
@keyframes boot-ring-spin {
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .boot__ring::before { animation: none; }
}

/* APP-BOOT-SPLASH-HOLD-UNTIL-READY — the same .boot markup, lifted out of #app
   by liftBootSplash() so render()'s innerHTML swap can't destroy it. It then
   covers the app shell while the landing tab mounts and fetches. z-index sits
   above the whole in-app scale (top of that scale is 9400) but below the
   LOGO-SPLASH-3D overlay at 99999, which must still play on top. No new visual
   design: the opaque navy brand plate + the horse-and-ring already in .boot. */
.boot--overlay {
  position: fixed;
  inset: 0;
  z-index: 9500;
  background: var(--horse-plate);
  opacity: 1;
  /* LAADTIJD-DE-APP-WACHT-NIET-MEER-OP-ZICHZELF (2026-09-02) — was
     var(--dur-slow) = 500ms (measured). This fade runs AFTER the app is done
     and fully painted underneath, so every one of those milliseconds is dead
     waiting. 150ms still reads as a fade rather than a cut. Deliberately a
     literal and not --dur-slow: the token is right for celebration
     choreography, wrong for getting out of the user's way. */
  transition: background-color 150ms var(--ease-standard);
}
/* LAADSCHERM-DE-ZIN-IS-TE-LEZEN (2026-09-10) — Jayy vroeg of de zin "wat
   langer uit kan vegen". Dat kan GRATIS, want dit is de UITGAANDE animatie:
   het laden is op dit moment al klaar en de app staat er al volledig onder.
   Er wordt dus niets vertraagd, alleen het moment waarop er nog iets te lezen
   valt wordt verlengd.
   WAAROM background-color en niet opacity op de laag zelf: een kind kan zijn
   ouder niet overleven. Faadde .boot--overlay zijn eigen opacity naar 0, dan
   was de zin op 150ms hoe dan ook onzichtbaar, wat je zijn eigen transition
   ook geeft. Nu verdwijnt de PLAAT in 150ms (identiek tempo als voorheen,
   geen milliseconde erbij) en blijft alleen de zin nog 400ms hangen.
   pointer-events: none valt op hetzelfde moment als vroeger, dus elke tik in
   die halve seconde landt gewoon in de app eronder. */
.boot--overlay.boot--out {
  background-color: transparent;
  pointer-events: none;
}
.boot--overlay.boot--out .boot__ring {
  opacity: 0;
  transition: opacity 150ms var(--ease-standard);
}
.boot--overlay.boot--out .boot__line {
  opacity: 0;
  transition: opacity 400ms var(--ease-standard) 140ms;
}
@media (prefers-reduced-motion: reduce) {
  /* Zonder beweging geen naijlen: de zin verdwijnt met de rest mee. De globale
     reduce-regel bovenin dit bestand nult de DUUR al (!important), maar niet de
     vertraging — die moet hier expliciet weg, anders blijft de zin alsnog
     140ms staan nadat alles verder al weg is. */
  .boot--overlay.boot--out .boot__line { transition-delay: 0s; }
}

/* LOGO-SPLASH-3D — fullscreen brand-moment overlay on cold start. The IIFE in
   public/index.html toggles #splash[hidden] off, plays the mp4, then adds
   .splash--out for the fade-out before removing the element. The navy plate
   reuses --horse-plate (the finish-invariant brand-moment token) so it matches
   the video's own black-navy background — no bare hex, css-hex-lint clean. */
#splash {
  position: fixed;
  inset: 0;
  z-index: 99999;        /* above app shell, above iframes, above modals */
  background: var(--horse-plate);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  opacity: 1;
  transition: opacity var(--dur-slow) var(--ease-standard);
  /* iOS safe-area: cover the notch + home-indicator */
  padding: env(safe-area-inset-top) env(safe-area-inset-right)
           env(safe-area-inset-bottom) env(safe-area-inset-left);
}
#splash[hidden] { display: none; }
#splash-video {
  width: 100%;
  height: 100%;
  object-fit: contain;  /* preserve the video's portrait 720×1280 framing without cropping */
  display: block;
  background: var(--horse-plate);
}
.splash--out { opacity: 0; }

/* NAV-TAB-SMOOTH-CONTENT-FADE — content-only tab crossfade. The freshly
   rendered shell starts at .tab-enter (faded out, lifted 12px) and settles to
   .tab-enter-active over 280ms; app.js strips both classes on transitionend so
   no transform lingers. Replaces the v0.9.652 solid var(--bg) overlay band,
   which read as a flash. .nav-transition below is retained as a no-op (no JS
   creates or animates it anymore) to avoid dangling references. */
.nav-transition {
  position: fixed;
  inset: 0;
  opacity: 0;
  pointer-events: none;
}
.tab-enter {
  opacity: 0;
  transform: translateY(-12px);
  will-change: opacity, transform;
}
.tab-enter-active {
  opacity: 1;
  transform: translateY(0);
  transition: opacity var(--dur-base) var(--ease-standard),
              transform var(--dur-base) var(--ease-standard);
}
@media (prefers-reduced-motion: reduce) {
  .tab-enter {
    transform: none;
  }
  .tab-enter-active {
    transform: none;
    /* 60ms is deliberate and stays: under reduce the tab cross-fade must be
       near-instant. Only the curve joins the app signature. */
    transition: opacity 60ms var(--ease-standard);
  }
}

/* ───────── App shell layout ───────── */
.app-shell {
  /* v0.9.309 — A11Y-4 S7: svh ladder, same rationale as .boot above. */
  min-height: 100vh;
  min-height: 100svh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

.app-main {
  flex: 1 1 auto;
  /* v0.9.309 — A11Y-4 S6: safe-area-aware horizontal padding so chrome
     doesn't sit under the rounded-corner clip on landscape notched devices. */
  padding-left:  var(--safe-left);
  padding-right: var(--safe-right);
  padding-bottom: calc(var(--tabbar-h) + var(--safe-bottom) + var(--space-5));
  max-width: 720px;
  width: 100%;
  margin: 0 auto;
  /* Vertical scroll only: horizontal drags stay available to in-page gestures
     (row-swipes, charts) instead of being taken over by browser panning. */
  touch-action: pan-y;
}

/* Auth layout (no tab bar) */
.app-shell--auth .app-main {
  padding-bottom: var(--space-5);
}

/* COACH-HET-HELE-DAGRITME-VAN-JE-KLANT-OP-EEN-PAGINA — de paneelschil in het
   iframe op de coach-pagina. Geen tabbalk, dus ook niet de hoogte ervan vrij
   houden: die lege strook zou in het iframe een dubbele onderrand maken onder de
   voetbalk van de editor. En geen max-width van 720 px: de coach-pagina bepaalt
   de breedte al, en een tweede plafond laat het ritme binnen het iframe
   gecentreerd in een veel bredere kolom zweven. */
.app-shell--panel .app-main {
  padding-bottom: var(--space-5);
  max-width: none;
}

/* DE-TIPS-KRIJGEN-EEN-RUSTIG-TWEEDE-HUIS (2026-09-10) — de tweede plek van de
   laadscherm-tips: de rustige strook onder het laatste kaartje.
   Jayy: "Het moet gewoon lowkey ergens zijn."
   Daarom bewust NIET: een kaart met een rand, een accentkleur, een icoon dat om
   aandacht vraagt, of een sluitkruisje (er valt niets te sluiten als het
   onderaan staat — je bent er al voorbij gescrold).
   Ruime marge BOVEN, zodat de strook zichtbaar niet bij het laatste kaartje
   hoort en niemand hem voor data aanziet.
   Kleur: --ink op 66% over --bg. Gemeten met de WCAG-formule haalt die mix in
   ALLE zes thema-presets minstens 4,96:1 — de rustigste (sepia: #6D6962 op
   #F7F4EE) is de smalste marge en zit nog boven AA. --ink-2 zou hier FOUT zijn:
   die staat in sepia op #8A8273 en meet 3,48:1, dus onder AA. "Rustig" en "te
   licht" liggen dicht bij elkaar; dit is de kant van rustig.

   DE-TIP-ONDERAAN-VOEDING-STAAT-VOOR-DE-PLAAT-NIET-ERACHTER (2026-09-11) — de
   "geen kader, geen kaart"-regel hierboven GELDT NOG STEEDS, maar alleen op
   Dagboek en in de Gym. Op Voeding is hij door Jayy zelf teruggedraaid op
   2026-09-10: daar ligt de strook over de plaat-wallpaper, en rustig-op-
   achtergrond is iets anders dan rustig-op-foto. Zie het paneel bij
   `.tip-strip[data-tab="nutrition"]` verderop. Zet dat dus niet "terug". */
.tip-strip {
  display: block;
  margin: var(--space-7) 0 var(--space-4);
  padding: 0 var(--space-4);
  font-size: 13px;
  line-height: 1.55;
  color: color-mix(in srgb, var(--ink) 66%, transparent);
  /* De strook hangt in renderShell aan `.app-main`, dus als BROER van
     `.nut-mount` — en erft daarmee NIET de regel `.nut-mount > *: position
     relative; z-index: 1` die alle Voedingskaarten boven de wallpaper tilt.
     `.nut-plate-wrap` is wél gepositioneerd (z-index: 0), en een gepositioneerd
     element schildert altijd over een ongepositioneerde broer heen, ongeacht de
     DOM-volgorde. Vandaar dat de plaat de tip bedekte. Dit is exact dezelfde
     behandeling; op Dagboek en in de Gym verandert er niets zichtbaars. */
  position: relative;
  z-index: 1;
}
.tip-strip__label {
  font-weight: 600;
  letter-spacing: .02em;
}
.tip-strip__label::after { content: ' · '; font-weight: 400; }
.tip-strip__text { font-weight: 400; }
/* SASSU-ZEGT-AF-EN-TOE-IETS-EN-DAT-MAG-JE-MISSEN (2026-09-10) — de zachte datum
   achter een bericht van Sassu. Geen eigen kleur: hij ERFT de --ink-66%-mix van
   .tip-strip en haalt daarmee dezelfde AA-marge in alle zes de presets (smalste
   is sepia op 4,96:1). Een extra dimming hier zou hem onder AA duwen, en de
   datum is informatie — zonder hem weet je niet of een oproep van gisteren of
   van vorige maand is.
   Nieuws krijgt VERDER geen enkel eigen kenmerk: geen accentkleur, geen icoon,
   geen badge, geen kader. Dat is Jayy's besluit van 2026-09-08 en geen smaak. */
.tip-strip__date::before { content: ' · '; }
.tip-strip__date { font-weight: 400; white-space: nowrap; }
/* DE-TIP-ONDERAAN-VOEDING-STAAT-VOOR-DE-PLAAT-NIET-ERACHTER (2026-09-11) — op
   Voeding, en ALLEEN daar, wordt de strook een glaspaneel in exact dezelfde taal
   als .hydration-card / .nut-bal. Reden: kale tekst op een foto is te zwak, ook
   waar hij niet bedekt wordt. De --ink-66%-mix hierboven is gemeten tegen --bg,
   NIET tegen een foto — die AA-marge geldt hier dus niet meer. De --glass-*-
   tokens zijn wél voor tekst-over-foto getuned en flippen per thema.
   De 72px marge onder blijft: de Voedingstab draagt een vaste "+"-knop
   rechtsonder en die mag de laatste regel tekst nooit half afdekken.
   De zijmarge komt van 0 naar var(--space-4) zodat het paneel dezelfde zijgoot
   houdt als de kaarten erboven; de padding die eerst die goot maakte verhuist
   naar binnen. */
.tip-strip[data-tab="nutrition"] {
  margin: var(--space-7) var(--space-4) 72px;
  padding: var(--space-3) var(--space-4);
  background: var(--glass-tint-2);
  -webkit-backdrop-filter: blur(16px) saturate(150%);
  backdrop-filter: blur(16px) saturate(150%);
  border: 1px solid var(--glass-edge);
  border-radius: var(--radius-md);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.14) inset;
  color: var(--glass-ink-2);
}
.tip-strip[data-tab="nutrition"] .tip-strip__label { color: var(--glass-ink); }

/* ───────── Top bar ───────── */
.topbar {
  /* NAV-CHAT-LAUNCH-MOVE-TO-TOPBAR-RIGHT (v0.9.696) — anchor the absolutely
     positioned chat-launch chip to the topbar. */
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* v0.9.309 — A11Y-4 S6: pad top by the device's safe-area inset so the
     Strength/Calendar/Settings topbar clears the iPhone 14+ Dynamic Island. */
  padding:
    calc(var(--space-5) + var(--safe-top))
    calc(var(--space-5) + var(--safe-right))
    var(--space-3)
    calc(var(--space-5) + var(--safe-left));
  border-bottom: 1px solid var(--line);
}
/* COACH-MODE-TABBAR-SHELL-SIX-TABS — de modus-hint. Eén token, afgeleid van
   --accent + --surface, dus Dag en Nacht krijgen allebei automatisch de juiste
   tint zonder een tweede kleurenset (het is bewust GEEN apart thema — concept
   §1). Alleen de header kleurt; de rest van de app blijft identiek. */
:root { --coach-tint: color-mix(in srgb, var(--accent) 12%, var(--surface)); }
body.coach-mode .topbar { background: var(--coach-tint); }
.topbar__title {
  margin: 0;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-2);
}
/* CHAT-LAUNCH-VISIBLE-ON-ALL-SURFACES-NO-OVERLAP — reserve the pill's footprint
   (pill width + gap) at the title's right edge so a long title can't run under
   the floating pill. Only when the pill is actually shown. */
body.chat-launch-on .topbar__title { padding-right: 92px; }
/* FASE-9-DE-TELLER-IN-BEELD — the free-AI counter. Muted body text at the
   title's right edge, nothing else: no border, no fill, no progress bar, no
   colour of its own. The app.js gate keeps it hidden until the sixth action, so
   most people never see it at all. When the fixed chat-launch pill is on it
   owns the top-right corner, so step aside by exactly its footprint rather than
   stacking underneath it. */
.topbar__ai-meter {
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
  margin-left: var(--space-3);
}
.topbar__ai-meter[hidden] { display: none; }
/* GRATIS-CHAT-GAAT-OPEN — de kracht- en cardiotab verbergen .topbar, dus daar
   kreeg niemand de teller te zien terwijl juist gym-inspo de zwaarste actie is.
   Zelfde tekst en zelfde drempel, alleen een andere plek: zwevend rechtsboven
   binnen de iframe-wrap. De twee display:none-regels blijven ongemoeid. */
.fz-ai-meter-float {
  position: absolute;
  top: calc(env(safe-area-inset-top) + var(--space-2));
  right: var(--space-3);
  z-index: 6;
  margin-left: 0;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--bg);
  opacity: 0.92;
  pointer-events: none;
}
.strength-iframe-wrap { position: relative; }
body.chat-launch-on .topbar__ai-meter { margin-right: 92px; }
/* GRATIS-CHAT-GAAT-OPEN — body.chat-launch-on staat nog op élke tab, maar de
   zwevende chat-pil wordt sinds v0.9.816 niet meer geïnjecteerd
   (ensureGlobalChatLaunch valt terug op window.fzHubChatEnabled !== true). Die
   92px opzij was dus dode reserve die de zwevende teller 104px uit de hoek
   duwde — op prod gemeten. Deze regel MOET onder de regel hierboven staan: de
   twee zijn even specifiek, dus alleen de volgorde beslist (v0.9.2305 zette hem
   erboven en veranderde daarmee niets). De topbar-chip houdt zijn stap opzij. */
body.chat-launch-on .fz-ai-meter-float { margin-right: 0; }
.topbar__action {
  background: none;
  border: none;
  padding: var(--space-2) var(--space-3);
  font-size: 13px;
  letter-spacing: 0.05em;
  color: var(--muted);
  cursor: pointer;
}
.topbar__action:hover { color: var(--ink); }
/* CHAT-LAUNCH-VISIBLE-ON-ALL-SURFACES-NO-OVERLAP (v0.9.701) — the chat-launch
   pill is now a GLOBAL fixed element appended to <body> (ensureGlobalChatLaunch
   in app.js), not a topbar child. A topbar-scoped pill was invisible on the
   Gym + Cardio tabs, which hide .topbar entirely and paint their iframes over
   it. Fixed to the top-right of the viewport above all chrome; hidden by
   default and revealed only on real authed tabs via body.chat-launch-on.
   Finish-invariant navy brand-moment chip (--horse-plate, brand-moment
   exemption covers this token) carrying the horse-head silhouette at 24px. */
.topbar__chat-launch {
  position: fixed;
  top: max(env(safe-area-inset-top), 12px);
  right: var(--space-4);
  z-index: 1000;
  display: none;
  align-items: center;
  gap: 8px;
  height: 44px;
  padding: 0 14px 0 10px;
  border-radius: var(--radius-pill);
  background: var(--horse-plate);
  color: #FFFFFF;  /* allow-hex: brand-moment — white-on-navy chip (precedent: nav__item--chat / horse-head); finish-invariant by design */
  border: 0;
  cursor: pointer;
}
/* Shown only on real authed tabs (render() sets chat-launch-on); absent on
   /chat, auth screens, and pre-app gates so the pill never floats over them. */
body.chat-launch-on .topbar__chat-launch { display: inline-flex; }
/* Belt-and-suspenders: /chat owns its own topbar (hamburger + close), so the
   pill stays hidden there even if chat-launch-on lingered. */
body.is-chat .topbar__chat-launch { display: none; }
/* CHAT-LAUNCH-HIDE-IN-TRAINER-SHEET-MODE — a coach genuinely acting-as a client
   (body.acting-as-client, set in render() via the strict impersonation guard) is
   coaching, not chatting with Sassu, so the pill is noise on the in-program
   strength sheet/day view. fz-strength-in-program is the iframe→host bridge from
   STRENGTH-PERIODIZATION-TILE-CARD-VIEW-ONLY (set only on the sheet/day-overview,
   absent on the program-overview card list + every other tab). Covers
   coach.sassu.fit automatically — it serves the same app shell + app.js, so the
   same render() sets both classes there. Own-data sheet (no acting-as) and card
   view keep the pill. */
body.acting-as-client.fz-strength-in-program .topbar__chat-launch { display: none; }
/* CHAT-LAUNCH-HIDE-IN-SHEET-MODE-GENERAL — hide chat-launch pill when ANY
   full-screen sheet is open (Strength/Nutrition/Diary). The pill overlaps
   sheet content and competes with sheet CTAs; user is in a different task. */
body.fz-strength-in-program .topbar__chat-launch,
body.fz-nutrition-sheet-open .topbar__chat-launch,
body.fz-diary-sheet-open .topbar__chat-launch { display: none; }
/* COACH-STRENGTH-EDIT-MODAL-OVER-COACH-CHROME — the coach-mode "Coaching:" bar
   (#fzCoachBar, injected by coach/index.php at z-index max-int) floats above the
   ENTIRE Strength iframe in the parent stacking context, so it overlaps the
   in-iframe edit modal (.mo) / rename modal (.ren-o), whose backdrop dims the
   rows but whose content/buttons sit behind the bar. strength.html is sealed, so
   strength-bridge.js mirrors the open state onto the host body as
   [data-strength-modal-open]; while set we hide the bar. visibility (not
   display) → no layout shift; modal closes → attribute drops → bar pops back.
   Harmless on app.sassu.fit (no #fzCoachBar there). */
body[data-strength-modal-open] #fzCoachBar {
  visibility: hidden;
  pointer-events: none;
}
.topbar__chat-launch:active { transform: scale(0.96); }
.topbar__chat-launch__label {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.brand-horse-head--launch { width: 24px; height: 24px; }

/* ───────── Bottom tab bar ───────── */
.tabbar {
  position: fixed;
  bottom: 0; left: 0; right: 0;
  height: calc(var(--tabbar-h) + var(--safe-bottom));
  /* v0.9.309 — A11Y-4 S6: respect landscape side insets so tabs don't fall
     under the rounded-corner clip; bottom inset stays as before. */
  padding-bottom: var(--safe-bottom);
  padding-left:   var(--safe-left);
  padding-right:  var(--safe-right);
  background: var(--surface);
  border-top: 1px solid var(--line);
  display: flex;
  z-index: 50;
}
.tabbar__btn {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  background: none;
  border: 0;
  /* NAV-CHAT-TAB-HUB-KEBAB-AND-20PCT-SMALLER — ~20% less vertical padding; the
     button still fills the (86px) bar via flex so the tap hitbox stays ≥44px.
     NAV-CHAT-TAB-POLISH-LABEL-SIZE-COLOR-AND-10PCT-UP — ~10% up: padding + font. */
  padding: 7px 2px;
  cursor: pointer;
  color: var(--muted);
  font-size: 9px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  position: relative;
  overflow: hidden;
}
.tabbar__btn span {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* ICON-WIRE-SHARED-INFRA-AND-NAV (v0.9.702) — central icon primitive. The brand
   line-art webp is rendered as a CSS mask filled with currentColor, so the icon
   inherits the parent text colour (--ink on body, --accent in a primary button,
   --chat-on-accent on the chat chip) and recolours automatically with the theme
   finish — no per-surface tint overrides. Kept ABOVE .tabbar__icon so the locked
   bottom-nav keeps its own 22px/display:block sizing for fz-icon tab glyphs. */
.fz-icon {
  display: inline-block;
  width: 24px;
  height: 24px;
  background: currentColor;
  -webkit-mask: var(--icon-url) center / contain no-repeat;
          mask: var(--icon-url) center / contain no-repeat;
  vertical-align: middle;
  flex: 0 0 auto;
}
.tabbar__icon {
  /* NAV-CHAT-TAB-POLISH-LABEL-SIZE-COLOR-AND-10PCT-UP — 20px (was 18px) line-art
     glyph size for the inline-SVG nav tabs (hub kebab, challenges). */
  width: 20px;
  height: 20px;
  display: block;
  opacity: 0.7;
}
/* ICON-NAV-BIGGER-AND-CLEARER (v0.9.707) — bump the bottom-nav line-art glyphs from the
   22px tabbar default to 28px so the silhouet reads clearly in the 64px strip;
   contrast(1.05) firms up the alpha-mask edges. Nav-scoped (.tabbar__btn) only:
   the topbar chat-launch pill and Voeding meal-row icons keep the 24px default.
   The 64px tabbar centres icon+label with room to spare, so padding is left as
   the existing balanced baseline (label stays clear of the icon). */
.tabbar__btn .fz-icon {
  /* NAV-CHAT-TAB-POLISH-LABEL-SIZE-COLOR-AND-10PCT-UP — 25px (was 22px). */
  width: 25px;
  height: 25px;
  filter: contrast(1.05);
}
/* NAV-ICONS-DROP-OPACITY-FADE-USE-UNDERLINE-INDICATOR (v0.9.812) — opacity/
   grayscale-fading op inactieve iconen voelde voor Jayy vervaagd op ALLE
   tabs (zelfs de active bleef matig vanwege de mix). Vervangen door: alle
   icons full-color, active = lichte 1.06 zoom + 24×3px accent-pill onder de
   tile. De label-laag (bold actief / muted inactief) blijft de tekstuele
   indicator zoals 'ie altijd was. */
.tabbar__btn__photo {
  /* NAV-CHAT-TAB-POLISH-LABEL-SIZE-COLOR-AND-10PCT-UP — 57px (was 51px), ~10% up;
     the scale() transforms below stay so the effective footprint moves in step. */
  width: 57px;
  height: 57px;
  object-fit: contain;
  border-radius: var(--radius-md);
  display: block;
  user-select: none;
  -webkit-user-drag: none;
  opacity: 1;
  /* NAV-TABBAR-ICONS-GRAYSCALE-WHEN-INACTIVE (v0.9.847) — inactive nav-icons
     desatureren zodat alleen het actieve tab-icon in kleur staat → gratis
     visuele hiërarchie. Active-variant zet filter terug op none. */
  filter: grayscale(100%);
  transition: transform var(--dur-fast) var(--ease-standard), filter var(--dur-fast) var(--ease-standard);
  /* shrink photo-style nav-icons to match HUB emblem footprint (visueel ~gelijk;
     photographic PNG zonder whitespace-padding oogt groter dan flat emblem) */
  transform: scale(0.8);
  transform-origin: center bottom;
}
.tabbar__btn[aria-current="page"] .tabbar__btn__photo {
  transform: scale(0.85);
  filter: none;
}
/* NAV-TABBAR-REMOVE-STRIPE-INDICATORS — boven/onder actieve-tab streepjes weg.
   Sinds v0.9.847 (grayscale-inactive) is kleur het active-signaal; de oude
   ::before (top-streep boven icoon) + ::after (bottom-streep onder label) zijn
   redundant. Color-on-active + bold-label (hieronder) blijven — kleur + bold is
   robuust genoeg voor a11y (kleurenblindheid ziet nog het bold-verschil). */
.tabbar__btn[aria-current="page"] {
  color: var(--ink);
  font-weight: 600;
}
.tabbar__btn[aria-current="page"] .tabbar__icon { opacity: 1; }
.tabbar__btn[aria-disabled="true"] {
  opacity: 0.35;
  cursor: not-allowed;
}
.tabbar__btn[aria-disabled="true"]:active,
.tabbar__btn[aria-disabled="true"]:hover { background: none; }

/* BRICKS-TAB-DISABLED-VISUAL (v0.9.542) — visible-but-disabled tabs.
   State-driven (not key-hardcoded): any tab in the coming-soon set —
   Bricks always, Cardio for the nutrition-only beta cohort — gets the
   same dimmed look. opacity is token-friendly (no hex literal — passes
   css-hex-lint). When the user is ON the coming-soon tab the dimming
   eases up so the active indicator is still legible. */
.tabbar__btn--coming-soon { opacity: 0.4; }
.tabbar__btn--coming-soon[aria-current="page"] { opacity: 0.6; }

/* NAV-DEACTIVATED-TAB-REAL-ICON-BLACK-WITH-WHITE-QUESTION-MARK — a globally-
   deactivated tab keeps its REAL nav icon but renders it fully black, with a
   white '?' badge overlaid top-right. brightness(0) collapses ANY colored PNG/
   SVG/mask glyph to solid black regardless of original colors; grayscale(100%)
   first neutralises hue so brightness(0) lands cleanly. The button is the
   positioning context for the ::after badge. Label treatment is untouched. */
.tabbar__btn--deactivated { position: relative; }
.tabbar__btn--deactivated .tabbar__icon,
.tabbar__btn--deactivated .fz-icon,
.tabbar__btn--deactivated .tabbar__btn__photo {
  filter: grayscale(100%) brightness(0);
  opacity: 1;
}
/* HET-SILHOUET-IS-OOK-S-NACHTS-TE-ZIEN — brightness(0) paints the silhouette black,
   which under the parent's 0.35 opacity disappears into a dark bar. invert(1) makes it
   white, landing on the exact tint of the white '?' badge in that same button. Hangs on
   data-theme-mode, so dark presets get the light silhouette and light presets keep the
   dark one; day mode is byte-identical. */
html[data-theme-mode="dark"] .tabbar__btn--deactivated .tabbar__icon,
html[data-theme-mode="dark"] .tabbar__btn--deactivated .fz-icon,
html[data-theme-mode="dark"] .tabbar__btn--deactivated .tabbar__btn__photo {
  filter: grayscale(100%) brightness(0) invert(1);
}
.tabbar__btn--deactivated::after {
  content: "?";
  position: absolute;
  top: 2px;
  right: 4px;
  font-size: 11px;
  font-weight: 700;
  color: #ffffff; /* allow-hex: white-on-black '?' badge — contrast-locked, not theme-tinted */
  background: rgba(0, 0, 0, 0.55);
  border-radius: 50%;
  width: 14px;
  height: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  pointer-events: none;
}

/* NAV-CHAT-TAB-POLISH-LABEL-SIZE-COLOR-AND-10PCT-UP — the Chat tab is now a
   PLAIN photo nav-tab: identical button markup, img box and label node as
   Dagboek/Gym/Cardio/Voeding. The old v0.9.687 navy-chip erfenis (separate
   tile background, extra margin/padding, white label, overflow-clipped label)
   is fully neutralised — no background, no border-radius, no colour override —
   so the shared .tabbar__btn geometry paints it. NAV-CHAT-ICON-GRAYSCALE-LIKE-
   THE-REST removed the last distinction: chat had a `filter: none` exemption
   justified by "the bottom-nav disappears on /chat so the tab is never active",
   which stopped being true once /chat became a full tab root. Chat now has ZERO
   exemptions — grey when inactive, colour when active, like every other tab.
   Do not reintroduce one. The .tabbar__btn--chat class still exists as the
   badge mount point (app.js paintChatBadge), not as a style hook. */

/* NAV-SETTINGS-TAB-CONVERT-TO-MIJ-HUB-MENU (v0.9.816) — the rightmost 'Me' tab
   shows the user's avatar (photo) or an accent-circle initial fallback, in the
   same 64×64 box as the photo nav-icons so the bar stays aligned. The shared
   .tabbar__btn[aria-current="page"]::after accent-pill already lights it on the
   /settings* surface; we add the same active scale-up the photo tiles get. */
.tabbar__btn__avatar {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--line-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 28px;
  font-weight: 800;
  color: var(--accent);
  user-select: none;
  -webkit-user-drag: none;
  /* NAV-TABBAR-ICONS-GRAYSCALE-WHEN-INACTIVE (v0.9.847) — HUB-emblem/avatar
     volgt dezelfde grayscale-when-inactive regel als de photo-icons. */
  filter: grayscale(100%);
  transition: transform var(--dur-fast) var(--ease-standard), filter var(--dur-fast) var(--ease-standard);
}
.tabbar__btn--hub[aria-current="page"] .tabbar__btn__avatar { transform: scale(1.06); filter: none; }
/* Emblem fallback: contain + padding so the silver-chrome 3D highlight keeps
   breathing room instead of being full-bleed cropped like a photo avatar. */
.tabbar__btn__avatar--emblem { object-fit: contain; padding: 4px; background: transparent; }

/* NAV-HUB-KEBAB-SIZE-AND-LABEL-ALIGN — the hub kebab (iconKebab, inline SVG with
   class .tabbar__icon) rendered at the 20px default, so its icon block was far
   shorter than the 57px photo-img box of the other tabs → the 'HUB' label floated
   ABOVE the DAGBOEK/GYM/CARDIO/VOEDING/CHAT baseline. Give the kebab the SAME
   layout box (57px) and the SAME scale-transform the photo icons carry, so:
   (a) the 57px layout height puts the label on the shared baseline, and
   (b) the identical scale keeps the rendered bounding box in step with the
   photos (transform is visual-only, so it doesn't disturb the baseline).
   The 24-unit kebab viewBox now fills a 57px box, so the three dots read at the
   same visual weight as the photo glyphs (~60%+ of box height) instead of a tiny
   glyph lost in whitespace. Muted-when-inactive stays via the inherited opacity
   (0.7); active lifts it to full ink + the shared scale-up, matching the hub. */
.tabbar__btn--hub .tabbar__icon {
  width: 57px;
  height: 57px;
  /* flex-shrink:0 — unlike the photo <img> (a replaced element whose intrinsic
     size resists column-flex shrink), a bare <svg> shrinks a couple px when the
     column is tight, which pulled the 'HUB' label ~2px above the shared
     baseline. Pin it so the layout box stays exactly 57px like the photos. */
  flex: 0 0 auto;
  transform: scale(0.8);
  transform-origin: center bottom;
}
.tabbar__btn--hub[aria-current="page"] .tabbar__icon {
  transform: scale(0.85);
  opacity: 1;
}

/* COACH-MODE-NAV-ICONS-INSTALL — the chat tab's inline-SVG scale exception is
   gone with the SVG itself: chat now renders nav-chat.png through the shared
   .tabbar__btn__photo box, so it carries the same 57px + scale(0.8) footprint as
   every other photo tab. Only the hub kebab still needs its own icon sizing. */

/* Hub bottom-sheet (the 'Me' menu). Overlay + slide-in sheet mirror the
   nutrition .mo-* primitives; rows reuse the settings .set-row vocabulary so
   they read as familiar settings rows. */
.hub-overlay {
  position: fixed;
  inset: 0;
  background: rgba(20, 20, 30, 0.5);
  z-index: 220;
  display: flex;
  align-items: flex-end;
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-standard);
}
.hub-overlay.hub-sheet--open { opacity: 1; }
.hub-sheet {
  background: var(--bg);
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
  max-height: 85vh;
  border-top-left-radius: var(--radius-lg);
  border-top-right-radius: var(--radius-lg);
  display: flex;
  flex-direction: column;
  transform: translateY(100%);
  transition: transform var(--dur-base) var(--ease-standard);
  padding-bottom: var(--safe-bottom);
  overflow-y: auto;
}
.hub-sheet.hub-sheet--open { transform: translateY(0); }
.hub-grip {
  width: 40px;
  height: 4px;
  background: var(--line);
  border-radius: var(--radius-sm);
  margin: 8px auto;
  flex: 0 0 auto;
  cursor: pointer;
}
/* DE-HUB-HEEFT-DEUREN-NAAR-DE-ANDERE-KANT — direct doors to the tabs on the
   other side, above the tile grid. It has to read as ANOTHER world than the
   tiles under it without adding noise: own ground, one soft border, and more air
   below the strip than between its doors. No accent plane, no second colour —
   the illustrations are the same photoreal tiles as the bottom bar and inherit
   its grayscale-when-inactive behaviour untouched.
   --surface-2 is only declared on .scan-layer, never at :root, so it carries the
   same var(--surface) fallback every other use of it in this file does. */
.hub-doors {
  margin: 4px 16px 8px;
  padding: 12px 8px 10px;
  background: var(--surface-2, var(--surface));
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}
.hub-doors[hidden] { display: none; }
.hub-doors__title {
  font-size: 11px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--section-label);
  font-weight: 500;
  margin: 0 0 4px;
  padding: 0 8px;
}
.hub-doors__row {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 4px;
}
.hub-door {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 2px;
  padding: 6px 2px;
  background: transparent;
  border: 0;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard);
}
.hub-door:active { background: var(--line-2); }
/* The nav photo keeps its own class (and with it the grayscale filter); only its
   footprint is fitted to the strip — the bar's scale(0.8)/bottom origin exists
   for the tabbar's label baseline, which this row does not have. */
.hub-door .tabbar__btn__photo {
  width: 46px;
  height: 46px;
  transform: none;
}
.hub-door__label {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink);
  text-align: center;
  line-height: 1.2;
}

.hub-menu-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
  padding: 12px 16px 16px;
}
.hub-menu-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 18px 12px;
  background: var(--line-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  min-height: 96px;
  cursor: pointer;
  position: relative; /* anchor for .hub-menu-btn__badge */
  transition: transform var(--dur-fast) var(--ease-standard), background var(--dur-fast) var(--ease-standard);
}
.hub-menu-btn:active { transform: scale(0.97); background: var(--line); }
/* HUB-CHALLENGES-COUNT-BADGE — accent count-pill of open challenges. */
.hub-menu-btn__badge {
  position: absolute;
  top: 8px;
  right: 8px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  box-sizing: border-box;
  border-radius: var(--radius-md);
  background: var(--accent);
  color: var(--on-accent);
  font-size: 11px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
}
@media (prefers-reduced-motion: no-preference) {
  .hub-menu-btn__badge { animation: hub-menu-badge-in var(--dur-base) var(--ease-standard); }
  @keyframes hub-menu-badge-in {
    from { transform: scale(0.4); opacity: 0; }
    to   { transform: scale(1);   opacity: 1; }
  }
}
.hub-menu-btn__icon { width: 28px; height: 28px; flex-shrink: 0; color: var(--accent); }
.hub-menu-btn__icon svg { width: 28px; height: 28px; }
.hub-menu-btn__label {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  text-align: center;
  line-height: 1.25;
}
/* Odd final button (e.g. non-coach user with 5 buttons) spans full width. */
.hub-menu-grid > .hub-menu-btn:nth-child(odd):last-child { grid-column: 1 / -1; }

/* HUB-LOW-VISION-TILE — accessibility promotion. Solid-accent tile with an
   oversized eye so a low-vision user can find the toggle at a glance. Normal
   grid width (Jayy 2026-08-07: full-width was too big — "hij past er gewoon
   rustig in"); the accent fill + big eye carry the findability instead.
   --on-accent gives the required contrast on the accent fill. */
.hub-menu-btn--a11y {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.hub-menu-btn--a11y:active { background: var(--accent); filter: brightness(0.94); }
.hub-menu-btn--a11y .hub-menu-btn__icon,
.hub-menu-btn--a11y .hub-menu-btn__icon svg {
  width: 44px;
  height: 44px;
}
/* DIARY-METRICS-SELF-IMPORT-ENTRY — measurement-import sheet body. Rides the
   shared .hub-overlay / .hub-sheet / .hub-grip chrome; only the content is new. */
.mi-sheet { padding: 4px 16px 20px; display: flex; flex-direction: column; gap: 12px; }
.mi-sheet__title { margin: 0; font-size: 18px; font-weight: 700; color: var(--text); }
.mi-sheet__hint { margin: 0; font-size: 14px; line-height: 1.4; color: var(--muted); }
.mi-pick {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
  border: 1px dashed var(--line);
  border-radius: var(--radius-md);
}
.mi-pick__label { font-size: 14px; font-weight: 600; color: var(--text); }
.mi-pick__input { font-size: 14px; color: var(--text); }
/* DIARY-METRIEK-EIGEN-CUSTOM — labelled text/number inputs in the same sheet. */
.mi-field { display: flex; flex-direction: column; gap: 6px; }
/* DIARY-EIGEN-METRIEK-PIL-EN-TYPE-JA-NEE — a yes/no metric hides unit + range
   with the `hidden` attribute, but the display:flex above outranks the user
   agent's [hidden]{display:none}, so those fields stayed on screen. */
.mi-field[hidden] { display: none; }
.mi-field__label { font-size: 14px; font-weight: 600; color: var(--text); }
.mi-field__input {
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--text);
  font-size: 16px;
}
.mi-sheet__status { margin: 0; font-size: 14px; color: var(--muted); min-height: 1em; }
.mi-sheet__status--error { color: var(--danger); }
.mi-summary { display: flex; flex-direction: column; gap: 6px; }
.mi-summary__row { display: flex; gap: 12px; justify-content: space-between; font-size: 14px; }
.mi-summary__label { color: var(--muted); flex: 0 0 auto; }
.mi-summary__value { color: var(--text); text-align: right; }
.mi-summary__warn { margin: 0; font-size: 13px; line-height: 1.4; color: var(--muted); }
.mi-submit {
  margin-top: 4px;
  padding: 12px;
  border: 0;
  border-radius: var(--radius-md);
  background: var(--accent);
  color: var(--on-accent);
  font-size: 16px;
  font-weight: 700;
  cursor: pointer;
}
.mi-submit:disabled { opacity: 0.45; cursor: default; }

.hub-menu-btn--a11y .hub-menu-btn__icon { color: var(--on-accent); }
.hub-menu-btn--a11y .hub-menu-btn__icon svg { stroke-width: 2; }
.hub-menu-btn--a11y .hub-menu-btn__label {
  color: var(--on-accent);
  font-size: 15px;
  font-weight: 700;
}
/* When low-vision mode is ON the whole hub-menu-btn set already stands out (the
   accent fill is now the "on" state); still keep the eye oversized regardless
   of state so the disable-tap is as easy to hit as the enable-tap. */

/* v0.9.320 A11Y-8 S23 — slide the tabbar off-screen while the on-screen
   keyboard is up. body.kb-open is toggled by the VisualViewport listener in
   app.js boot. Uses translateY(100%) so the existing transition (if any) and
   safe-bottom padding both come along; restores instantly when the keyboard
   dismisses. */
body.kb-open .tabbar {
  transform: translateY(100%);
  transition: transform var(--dur-base) var(--ease-standard);
  pointer-events: none;
}

/* Unread-count pill above a tabbar icon. Painted in iOS-system-red (#FF3B30)
   per Jayy — matches the universal notification-dot convention used by
   Instagram/Facebook/WhatsApp. Position anchors to the icon strip (top-right of
   the icon area), not the button bounds, so the active-tab top accent bar
   doesn't compete with it.
   DE-TABBALK-GYM-EN-VOEDING-DRAGEN-GEEN-RODE-BOLLETJES-MEER — Gym and Nutrition
   dropped their counts (Jayy, 2026-09-26). The remaining painters are
   paintChatBadge() and paintCoachWorklistBadge() in app.js, plus the coach
   Review counter in coach.js. */
.tabbar__btn { position: relative; } /* (already set above; defensive — keeps absolute child contained even if the rule above is overridden by future refactors) */
.tabbar__badge {
  position: absolute;
  top: 4px;
  /* Half a tab width — 22px icon → ~12px right of icon centerline.
     Tab width varies (flex:1), so right is anchored as a fraction of the
     button. With 6 tabs on a 390px viewport that's ~65px/btn → 25% of
     btn width = ~16px right inset; clamps prevent extremes on narrow/wide. */
  right: clamp(14px, 22%, 22px);
  min-width: 16px;
  height: 16px;
  /* Geometric centering of the inner digit: inline-flex + line-height:1 +
     tabular-nums prevents the baseline-vs-geometric mismatch that left "7"
     visibly upper-left of the circle center (v0.9.317 → v0.9.318 fix). */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  background: var(--badge-bg);
  color: var(--on-badge);
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  text-align: center;
  letter-spacing: 0;
  text-transform: none;
  box-shadow: var(--elev-1);
  pointer-events: none;
  opacity: 0;
  animation: tabbarBadgeFadeIn var(--dur-base) var(--ease-standard) forwards;
}
@keyframes tabbarBadgeFadeIn {
  to { opacity: 1; }
}

/* COACH-SHARED-RECIPE-UNSEEN-BADGE — dot on the "Mijn recepten" add-submenu
   tile while unseen coach-shares exist. Absolute accent circle, top-right. */
.nut-addmenu__tile { position: relative; }
.nut-addmenu__tile-dot {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 9px;
  height: 9px;
  border-radius: var(--radius-pill);
  background: var(--accent);
  box-shadow: var(--elev-1);
  pointer-events: none;
}

/* COACH-SHARED-RECIPE-UNSEEN-BADGE — brief highlight flash on a just-shared
   recipe row when the client opens My-recipes. Accent-tinted background that
   fades out over ~2.5s to say "this one is new from your trainer", then the
   row settles to its normal state and the dot clears permanently. */
.nut-recipe-shared-flash {
  animation: nutRecipeSharedFlash 2500ms var(--ease-standard) 1;
}
@keyframes nutRecipeSharedFlash {
  0%   { background: var(--accent); color: var(--on-accent); }
  70%  { background: var(--accent); color: var(--on-accent); }
  100% { background: transparent; color: inherit; }
}
@media (prefers-reduced-motion: reduce) {
  /* Static, non-animated fade: hold a subtle accent tint briefly then rely on
     JS removing the class. No motion, still communicates "new". */
  .nut-recipe-shared-flash {
    animation: none;
    background: var(--accent-subtle);
  }
}

/* v0.9.342 REST-TIMER-REWORK — cross-tab thin strip + minimal text line.
   Singleton element appended to <body> by app.js fzMountRestStrip(). Shown
   only when a rest timer is active AND the inline pill in the Strength
   iframe is NOT visible (drawer closed, scrolled away, or different tab).
   Position: fixed top, accent-tinted progress bar + small body line. Tap
   navigates to /strength (the bridge restores pill into view on next tick). */
.fz-rt-strip {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  background: var(--surface);
  border-bottom: 1px solid var(--line-2);
  padding-top: var(--safe-top);
  display: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  box-shadow: var(--elev-1);
}
.fz-rt-strip.is-on { display: block; }
.fz-rt-strip__bar {
  height: 5px;
  background: var(--line-2);
  position: relative;
  overflow: hidden;
}
.fz-rt-strip__fill {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  background: var(--accent);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  width: 0%;
  transition: width var(--dur-loop) linear;
}
.fz-rt-strip__body {
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-2);
  padding: 6px 14px 8px;
  letter-spacing: 0.01em;
  line-height: 1;
}
.fz-rt-strip.is-pulse .fz-rt-strip__fill {
  width: 100% !important;
  animation: fzRtStripPulse var(--dur-pulse) var(--ease-standard) 1;
}
@keyframes fzRtStripPulse {
  0%   { opacity: 1; }
  70%  { opacity: 1; }
  100% { opacity: 0; }
}
/* Shift the shell down when the strip is visible so the topbar doesn't
   sit underneath it. The strip height is fluid (5px bar + ~24px text +
   safe-top) — use 36px + safe-top as the offset; close enough for the
   minimal mode Jayy locked. */
body:has(.fz-rt-strip.is-on) .app-shell {
  padding-top: calc(36px + var(--safe-top));
}
/* MOBILE-COMPAT-HAS-SELECTOR-FALLBACK (F2) — plain-selector twin for engines
   without :has() (Chromium <105 / Samsung Internet <20 / Firefox <121). JS
   toggles body.fz-rt-on in lockstep with .fz-rt-strip.is-on (app.js). Identical
   declarations to the :has() rule above — do NOT diverge. */
body.fz-rt-on .app-shell {
  padding-top: calc(36px + var(--safe-top));
}

/* ───────── Auth screens ───────── */
.auth {
  /* v0.9.309 — A11Y-4 S6+S7: notch-aware vertical padding (--safe-top so the
     "sassu" wordmark clears the Dynamic Island) + svh ladder so the Sign-in
     button stays above the iOS Safari URL bar. */
  min-height: 100vh;
  min-height: 100svh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding:
    calc(var(--space-7) + var(--safe-top))
    calc(var(--space-5) + var(--safe-right))
    calc(var(--space-7) + var(--safe-bottom))
    calc(var(--space-5) + var(--safe-left));
  max-width: 440px;
  margin: 0 auto;
}
/* THEME-2 — auth-screen brand lockup: emblem above the W2 wordmark.
   The wordmark renders via the CSS-mask alpha technique: the element
   paints background var(--ink) clipped to the letterform alpha, so it
   recolors per finish with a single asset (NEVER the emblem at this
   size — wordmark letterforms only, per APP_BRAND_MEDIA spec). The
   'sassu' text stays in the DOM (transparent) so AT still announces
   the brand. */
.auth__brandwrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: var(--space-7);
}
.auth__brandwrap--lg { gap: var(--space-5); }
.auth__emblem {
  width: 40px;
  height: 40px;
  object-fit: contain;
  /* The emblem asset carries its navy plate (no alpha master exists yet);
     rounding it makes the plate read as a deliberate brand badge on light
     finishes instead of a raw square. */
  border-radius: 22%;
}
.auth__brandwrap--lg .auth__emblem {
  width: 96px;
  height: 96px;
}
.auth__brand {
  /* Source alpha PNG is 900×120 (7.5:1); 180×24 keeps the ratio. */
  width: 180px;
  height: 24px;
  overflow: hidden;
  color: transparent;
  user-select: none;
  background: var(--ink);
  -webkit-mask: url('/assets/img/brand/wordmark-alpha.png') center / contain no-repeat;
  mask: url('/assets/img/brand/wordmark-alpha.png') center / contain no-repeat;
}
/* SETTINGS-TOP-WORDMARK-LOGO — Sassu wordmark at the top of /settings.
   Same alpha-mask recolor trick as .auth__brand so the wordmark tints to
   the theme --ink token in both Dag and Nacht. Modest header accent, not
   a hero. Source alpha PNG is 900×120 (7.5:1); 150×20 keeps the ratio. */
.set-brand {
  display: flex;
  justify-content: center;
  margin: var(--space-2) 0 var(--space-5);
}
.set-brand__wordmark {
  width: 150px;
  height: 20px;
  overflow: hidden;
  color: transparent;
  user-select: none;
  background: var(--ink);
  -webkit-mask: url('/assets/img/brand/wordmark-alpha.png') center / contain no-repeat;
  mask: url('/assets/img/brand/wordmark-alpha.png') center / contain no-repeat;
}
/* DE-PROEF-IS-ZICHTBAAR — één statusregel onder de profielkaart. De twee
   beta-etiketten (.set-beta-pill, .set-beta-chip) zijn met deze fase weg.
   INSTELLINGEN-DE-PLUS-PIL-WORDT-EEN-STATUSREGEL — de losse pil is óók weg: de
   stand staat als regel IN de accountkaart (.set-id__plan) en de proefknop is
   een rustige tekstregel eronder. Typografie doet het werk, geen badge. */
.set-plan-try:not(:empty) {
  /* Trekt de proefregel terug naar de accountkaart: de kaart heeft er
     --space-5 onder staan, en zonder deze correctie hangt de knop dichter bij
     de taalknoppen dan bij de stand waar hij bij hoort. Leeg (elke stand
     behalve Gratis) neemt de rij geen ruimte in. */
  margin: calc(-1 * var(--space-4)) 16px var(--space-5);
}
.set-plan-try__btn {
  appearance: none; border: 0; cursor: pointer;
  padding: 0;
  background: none;
  color: var(--accent);
  font-size: 13px; font-weight: 500;
  line-height: 1.3;
  min-height: 28px;
  text-align: left;
}
.premium-status__retry { margin-top: 8px; }
/* Free→Plus pitch in the Settings premium card (EERLIJKE-UPGRADE-IN-DE-APP). */
.premium-card__pitch {
  margin: 8px 0 12px;
  padding: 10px 12px;
  background: var(--accent-subtle);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-md);
}
.premium-card__pitch__lede {
  margin: 0;
  font-size: 13px; line-height: 1.4;
  color: var(--ink);
}
/* PLUS-DE-MAILKNOP-LANDT-OP-KIES-JE-ABONNEMENT — de eerlijke regel boven de
   pitch, alleen bij een echt afgelopen proef. Dezelfde maat als de lede, iets
   zwaarder: hij benoemt wat er veranderd is en de lede eronder wat er is. */
.premium-card__trial-over {
  margin: 0 0 6px;
  font-size: 13px; line-height: 1.4; font-weight: 600;
  color: var(--ink);
}
/* PLUS-DE-PROEFMAIL-ZEGT-VOOR-WIE-HIJ-WAS — de kaart bovenaan voor wie een
   proefmail van een ander account opende. Bewust NIET rood: er is niets fout
   gegaan, er was alleen iets onbenoemd. Vandaar dezelfde rustige omlijsting als
   de rest van de kaart en geen waarschuwingskleur. */
.premium-mailvoor {
  margin: 0 0 12px;
  padding: 12px;
  background: var(--surface-2, var(--accent-subtle));
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}
.premium-mailvoor__line {
  margin: 0;
  font-size: 13px; line-height: 1.5;
  color: var(--ink);
}
.premium-mailvoor__actions {
  display: flex; flex-direction: column; gap: 8px;
  margin-top: 10px;
}
.premium-card__pitch__list {
  margin: 8px 0 0;
  padding-left: 18px;
  font-size: 13px; line-height: 1.5;
  color: var(--ink);
}
.premium-card__pitch__price {
  margin: 8px 0 0;
  font-size: 13px; font-weight: 600;
  color: var(--ink);
}
.fz-admin-panel__link { margin-top: 12px; }
.fz-admin-panel__link-row {
  display: flex; gap: 8px; align-items: center;
  margin-top: 8px;
}
.fz-admin-panel__link-input {
  flex: 1; min-width: 0;
  font-family: monospace; font-size: 12px;
  padding: 8px 10px;
}
/* DE-VOORDEUR-EN-DE-WIDGET — Jayy's front-door control. The remaining number is
   the loudest thing on the screen; everything else is a way to change it. */
.fz-frontdoor__big {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: var(--space-4) 0 var(--space-1);
}
.fz-frontdoor__num {
  font-size: 56px;
  line-height: 1;
  font-weight: 600;
  letter-spacing: -0.03em;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.fz-frontdoor.is-closed .fz-frontdoor__num { color: var(--muted); }
.fz-frontdoor__cap {
  font-size: 13px;
  color: var(--muted);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.fz-frontdoor__state {
  margin: 0 0 var(--space-4);
  font-size: 13px;
  color: var(--muted);
}
.fz-frontdoor__row {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-bottom: 8px;
}
.fz-frontdoor__row .btn--accent { flex: 1; }
.fz-frontdoor__amount {
  flex: 1 1 140px;
  min-width: 120px;
  width: auto;
}
.fz-frontdoor__row .btn--ghost { flex: 0 1 auto; }
.fz-frontdoor__close { width: 100%; margin-top: 4px; }
.fz-admin-invites__pending-row {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  padding: 10px 0;
  border-bottom: 1px solid var(--divider);
}
.fz-admin-invites__pending-row:last-child { border-bottom: 0; }
.fz-admin-invites__pending-meta {
  flex-basis: 100%;
  font-size: 11px; color: var(--muted);
  opacity: 0.7;
}
.auth__title {
  font-size: 22px;
  font-weight: 400;
  margin: 0 0 var(--space-2);
  letter-spacing: -0.005em;
}
.auth__sub {
  font-size: 14px;
  color: var(--muted);
  margin: 0 0 var(--space-6);
}
.auth__footer {
  margin-top: var(--space-6);
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
.auth__footer a {
  color: var(--ink);
  border-bottom: 1px solid var(--line);
}

/* DE-VOORDEUR-EN-DE-WIDGET — the open-front-door note that replaces the invite
   field when Jayy still has code-less spots left. */
.auth__frontdoor {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.auth__frontdoor-line {
  margin: 0;
  color: var(--ink);
}
/* EEN-LINK-IS-JE-TOEGANG-EN-JE-GRATIS-PERIODE-TEGELIJK — the one quiet line
   above the sign-up form that names what the link in your hand carries. Not a
   banner and not an accent block: it has to read as a fact about this form,
   not as an advertisement the visitor has to dismiss before he can type. */
.auth__gift {
  margin: 0 0 var(--space-4);
  color: var(--ink);
}
.auth__linkbtn {
  padding: 0;
  border: 0;
  border-bottom: 1px solid var(--line);
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}

/* BRAIN-CONSENT-REGISTRATION-OPTIN — default-unchecked research consent row on
   the signup + claim forms, and the one-time existing-user prompt overlay. */
.auth__consent {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  margin: var(--space-4) 0 var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface-2, var(--surface));
  cursor: pointer;
}
.auth__consent-box {
  flex: none;
  width: 18px;
  height: 18px;
  margin-top: 2px;
  accent-color: var(--accent);
}
.auth__consent-text {
  font-size: 13px;
  line-height: 1.45;
  color: var(--ink);
}
.fz-consent-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  background: color-mix(in srgb, var(--ink) 45%, transparent);
}
.fz-consent-sheet {
  width: 100%;
  max-width: 360px;
  padding: var(--space-5);
  border-radius: var(--radius-lg);
  background: var(--card, var(--surface));
  box-shadow: var(--elev-3);
}
.fz-consent-sheet__title {
  margin: 0 0 var(--space-2);
  font-size: 19px;
}
.fz-consent-sheet__body {
  margin: 0 0 var(--space-4);
  color: var(--muted);
  font-size: 14px;
  line-height: 1.5;
}
.fz-consent-sheet__actions {
  display: flex;
  gap: var(--space-3);
}
.fz-consent-sheet__actions .btn {
  flex: 1;
}

/* v0.9.319 — A11Y-7 S14: first-run landing at `/`. Two equal-weight CTAs +
   trust line. No form, no inputs — pure orient-and-route. Inherits .auth's
   notch-aware ladder so the headline clears the Dynamic Island and the CTAs
   stay above the iOS Safari URL bar. */
.auth--landing {
  justify-content: center;
  text-align: center;
}
/* THEME-2 — the unauthenticated landing is a BRAND surface: Deep Navy +
   void-horizon background in EVERY finish (scoped --brand-* token
   overrides, per APP_BRAND_MEDIA_UX_SPEC §0 — navy never leaks into
   working screens because the class only exists on the landing shell).
   The webp (14 KB) is a CSS background-image, so the browser loads it on
   demand after first paint; background-color paints instantly beneath it.
   Static image — nothing animated, no reduced-motion pairing needed. */
.app-shell--landing {
  --bg: var(--brand-navy);
  --surface: var(--brand-navy);
  --ink: var(--brand-ink);
  --ink-2: var(--brand-dim);
  --muted: var(--brand-dim);
  --accent: var(--brand-ice);
  --accent-2: var(--brand-ice);
  --on-accent: var(--brand-navy);
  --line: rgba(168, 216, 234, 0.18);
  background-color: var(--brand-navy);
  background-image: url('/assets/img/brand/bg-void-horizon-1280.webp');
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  color: var(--ink);
}
.auth__hero {
  margin-bottom: var(--space-7);
}
.auth__hero-headline {
  font-size: 26px;
  font-weight: 400;
  line-height: 1.25;
  letter-spacing: -0.01em;
  margin: 0;
  color: var(--ink);
}
.auth__hero-sub {
  font-size: 15px;
  line-height: 1.5;
  margin: var(--space-3) 0 0;
  color: var(--muted);
}
/* CHAT-VOEG-VRIENDEN-TOE-EN-EEN-SCAN-MAAKT-JULLIE-VRIENDEN — de uitlegregel voor
   een wachtende vriend-QR op de landingspagina. ALLEEN afstand: kleur en
   uitlijning komen van .auth__sub (var(--muted)) en .auth--landing
   (text-align:center), die op ditzelfde merkoppervlak al door .auth__hero-sub
   leesbaar bewezen zijn. Geen eigen kleur = geen thema-gevoeligheid, dus deze
   regel hoort niet in DARKSET. */
.auth__sub--friendqr {
  margin: var(--space-5) 0 0;
}
/* WACHTWOORD-VERGETEN-ZEGT-WAT-JE-KUNT-DOEN-ALS-ER-NIETS-KOMT — de losse regel
   die zegt hoe lang de reset-link geldig is, onder de twee zinnen die WEL een
   handeling vragen. ALLEEN grootte: kleur en marge erven van .auth__sub
   (var(--muted)), dus deze regel heeft geen eigen kleur en is niet
   thema-gevoelig — hij hoort daarom niet in DARKSET. */
.auth__sub--fine {
  font-size: 13px;
}
.auth__cta-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.auth__trust {
  margin: var(--space-6) 0 0;
  font-size: 12px;
  color: var(--muted);
  text-align: center;
}
/* REGISTER-REQUEST-ACCESS-NO-CODE-PATH — the 'brievenbus': a quiet tertiary
 * text link for code-less visitors, so an empty invite field is never a dead
 * end. Muted + centred; not an accent control (no --on-accent needed). */
.auth__request-access {
  margin: calc(var(--space-3) * -1) 0 var(--space-5);
  font-size: 13px;
  color: var(--muted);
  text-align: center;
}
.auth__request-access a {
  color: var(--muted);
  border-bottom: 1px solid var(--line);
  cursor: pointer;
}
/* INVITE-LOCALE-FIRST (v0.9.657) — bilingual hint above the NL/EN picker
 * buttons. Self-labels in both languages by design (user has not chosen yet),
 * so it stays muted and centred and is never run through t(). */
.auth__locale-hint {
  margin: 0 0 var(--space-6);
  font-size: 13px;
  color: var(--muted);
  text-align: center;
}

/* Track A2 — "Sign in with Google". Empty until the server reports it's
 * configured, so .auth__google-wrap collapses to nothing when hidden. */
.auth__google-wrap:empty {
  display: none;
}

/* Beta Terms-of-Service gate (v0.9.169) — mandatory accept checkbox. */
.form-check {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  font-size: 14px;
  line-height: 1.45;
  color: var(--ink);
  margin: var(--space-5) 0 var(--space-6);
  cursor: pointer;
}
.form-check input[type="checkbox"] {
  width: 20px;
  height: 20px;
  margin-top: 1px;
  flex: 0 0 auto;
  accent-color: var(--accent);
}

/* Legal links in Settings (v0.9.170) — stacked, tappable rows.
   v0.9.508 SETTINGS-PADDING-AND-PROFILE-KERNING-V2 — horizontal padding was
   2px so the rows bled to the card's left edge. Aligned with the standard
   .set-row inset (var(--space-4) = 16px) + min-height 44px for tap target. */
.set-legal {
  display: flex;
  flex-direction: column;
}
.set-legal a {
  padding: 12px var(--space-4);
  min-height: 44px;
  display: flex;
  align-items: center;
  color: var(--ink);
  border-bottom: 1px solid var(--line);
  text-decoration: none;
  font-size: 15px;
}
.set-legal a:last-child { border-bottom: 0; }
.auth__divider {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-5) 0;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}
.auth__divider::before,
.auth__divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}
/* KAHUNAS-KLANT-OPENT-DE-LINK-EN-ZIET-METEEN-ZIJN-SCHEMA — "stuur me een nieuwe
   link" onder de verlopen-link-melding. Eigen klasse, geen .auth__footer: die
   staat op ditzelfde scherm al onder de knop. */
.auth__resend {
  margin-top: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.auth__google {
  display: flex;
  justify-content: center;
}

/* ───────── INLOGGEN-MET-GOOGLE-IS-OOK-EEN-BEVESTIGING ─────────
   The one confirmation block, used on four surfaces (mood vault on/off,
   change email, delete account). It has no background and no border on
   purpose: it lives INSIDE the host's card or dialog and must read as the
   last field of that form, not as a box within a box. Only the spacing and
   the two small labels are ours; the password field is whatever the host
   handed us, so every surface keeps its own field styling.
   Nothing here paints on the accent, so there is no colour to hardcode —
   the Google button is drawn by Google's own library. */
.stepup {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.stepup__google {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
}
/* The instruction when Google is the ONLY way through — it carries the same
   weight as a field label because it is one. */
.stepup__lbl {
  margin: 0;
  align-self: flex-start;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
/* The quiet alternative UNDER a password field. Centred and lowercase: it is
   an aside, not a second instruction competing with the field above it. */
.stepup__alt {
  margin: var(--space-2) 0 0;
  font-size: 13px;
  color: var(--muted);
}
.stepup__btn {
  width: 100%;
}
/* While a credential is being checked the button must stop taking taps —
   Google's iframe cannot be disabled, so the wrapper does it. */
.stepup__google.is-busy {
  opacity: 0.55;
  pointer-events: none;
}
/* Deel D — the honest sentence that replaces a button that could not work. */
.stepup__blocked {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted);
}
.stepup__help {
  color: var(--accent);
  text-decoration: underline;
}

/* ───────── Form primitives ───────── */
.form-group {
  margin-bottom: var(--space-4);
}
.form-label {
  display: block;
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: var(--space-2);
}
/* v0.9.329 — A11Y-11B S33: hairline underline stays (minimal Sassu), but the
   whole field gains a soft background tint on :focus-visible so the active tap
   target is unambiguous. Horizontal padding lifted from 0 → 12px so the tint
   reads as the whole field, not just under the caret. border-radius applied to
   the top corners so the tint clips against the underline cleanly. */
/* PLUS-CODE-HET-VELD-STAAT-NIET-MEER-PLAT — het codeveld op Instellingen droeg
   in de DOM geen enkele invoerklasse en stond daardoor kaal op het scherm. Het
   erft hier de bestaande veldstijl mee in plaats van een tweede te verzinnen. */
.form-input,
.plus-code__input {
  display: block;
  width: 100%;
  padding: 14px 12px;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--line);
  font-size: 16px;
  color: var(--ink);
  outline: none;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  transition: background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
}
.form-input:focus,
.form-input:focus-visible,
.plus-code__input:focus,
.plus-code__input:focus-visible {
  background: var(--surface-muted);
  border-bottom-color: var(--ink);
}
.form-input::placeholder,
.plus-code__input::placeholder { color: var(--muted); }
.form-input[aria-invalid="true"] { border-bottom-color: var(--danger); }

/* v0.9.311 — A11Y-6 S18: password reveal toggle. The wrap is the
   positioning context for the absolutely-placed eye button; the input keeps
   its full-width baseline, the button rides flush-right inside the field's
   own padding box so it never overlaps the typed characters at common
   screen widths. 44×44 hit-area meets WCAG SC 2.5.5 + 2.5.8. */
.form-input-wrap {
  position: relative;
  display: block;
}
.form-input-wrap .form-input {
  padding-right: 44px;
}
.form-input__reveal {
  position: absolute;
  top: 50%;
  right: 0;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  background: transparent;
  border: 0;
  color: var(--muted);
  cursor: pointer;
  border-radius: var(--radius-md);
}
.form-input__reveal:hover,
.form-input__reveal:focus-visible { color: var(--ink); outline: none; }
.form-input__reveal[aria-pressed="true"] { color: var(--ink); }
.form-input__reveal svg { width: 22px; height: 22px; }

/* v0.9.311 — A11Y-6 S19: ToS gate PDF tiles. Two equal-weight tiles stacked
   above the consent checkbox, replacing the inline "View ToS · Privacy" link
   pair. Each tile is a full-width tap target with the PDF name + a chevron;
   the visual weight makes "this is a document you should open" obvious. */
.tos-tiles {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: var(--space-4) 0 var(--space-5);
}
.tos-tile {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--ink);
  text-decoration: none;
  font-size: 14px;
  min-height: 44px;
}
.tos-tile:hover,
.tos-tile:focus-visible { border-color: var(--ink); outline: none; }
.tos-tile__label { display: flex; flex-direction: column; gap: 2px; }
.tos-tile__name { font-weight: 500; }
.tos-tile__meta {
  font-size: 11px;
  color: var(--muted);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.tos-tile__chev {
  flex: 0 0 auto;
  color: var(--muted);
}
.tos-tile__chev svg { width: 18px; height: 18px; display: block; }

.btn {
  display: inline-block;
  width: 100%;
  background: var(--ink);
  color: var(--bg);
  border: 1px solid var(--ink);
  padding: 16px 20px;
  font-size: 14px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-weight: 500;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
  border-radius: 0;
}
.btn:hover { background: var(--ink-2); border-color: var(--ink-2); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
.btn--ghost {
  background: transparent;
  color: var(--ink);
}
.btn--ghost:hover {
  background: var(--ink);
  color: var(--bg);
}
.btn--accent {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.btn--accent:hover { background: var(--accent-2); border-color: var(--accent-2); }
/* Ghost-danger — destructive secondary action. Never a filled slab; calm red
   outline that reads as destructive without shouting. Disabled = subdued muted
   outline (transparent bg), not a grey slab. */
.btn--dangerghost {
  background: transparent;
  color: var(--danger);
  border-color: var(--danger);
}
.btn--dangerghost:hover { background: var(--danger); color: var(--on-danger); border-color: var(--danger); }
.btn--dangerghost:disabled { opacity: 1; background: transparent; color: var(--muted); border-color: var(--line); cursor: not-allowed; }
/* COACH-CLAIM-PAGE-ADD-SIGN-IN-WITH-EXISTING-ACCOUNT-OPTION (v0.9.511) —
   outline peer for the primary .btn. Used as the "Sign in with existing
   account" CTA below the divider on /claim. Token-driven so both dag + nacht
   inherit; same vertical rhythm as .btn (44px+ tap target via the inherited
   padding). */
.btn--secondary {
  background: transparent;
  color: var(--ink);
  border-color: var(--line);
}
.btn--secondary:hover {
  background: var(--surface-muted);
  border-color: var(--ink);
  color: var(--ink);
}

.btn-row { display: flex; gap: var(--space-3); }

.form-error {
  background: transparent;
  border-left: 2px solid var(--danger);
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-4);
  font-size: 13px;
  color: var(--danger);
}

.form-divider {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-5) 0;
  color: var(--muted);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
.form-divider::before,
.form-divider::after {
  content: "";
  flex: 1 1 0;
  height: 1px;
  background: var(--line);
}

/* ───────── Generic page ───────── */
.page {
  padding: var(--space-5);
}
.page__title {
  font-size: 28px;
  font-weight: 300;
  margin: 0 0 var(--space-2);
  letter-spacing: -0.01em;
}
.page__lede {
  font-size: 14px;
  color: var(--muted);
  margin: 0 0 var(--space-6);
}

.placeholder {
  border: 1px dashed var(--line);
  padding: var(--space-7) var(--space-5);
  text-align: center;
  color: var(--muted);
}
.placeholder strong {
  display: block;
  color: var(--ink-2);
  margin-bottom: var(--space-2);
  font-weight: 500;
  font-size: 15px;
}

/* ───────── Settings (v0.9.160 UX pass) ───────── */
/* v0.9.347 SETTINGS-IOS-STYLE-RESTYLE — the Settings screen paints --surface-bg
   (slightly tinted neutral in light, near-black in dark) so the cards visually
   pop. Horizontal padding tightens because the per-card inner padding now
   provides the gutter. v0.9.490 SETTINGS-PADDING-BLEED — top reduced from
   --space-5 (24px) to --space-3 (12px) so the Profile header sits close to the
   tab nav; bottom reduced from --space-7 (48px) to --space-4 (16px) because
   .app-main already adds calc(--tabbar-h + --safe-bottom + --space-5) bottom
   clearance, the extra 48px was double-padding that left the danger Restore
   row sitting in dead space. The 16px breathing room before the parent nav-
   clearance kicks in gives the last section a calm trailing margin. */
.set { padding: var(--space-3) var(--space-4) var(--space-4); background: var(--surface-bg); min-height: 100%; }

/* Profile header */
.set-profile {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4);
  margin-bottom: var(--space-5);
  border-bottom: 0;
  background: var(--surface-card);
  border-radius: var(--radius-lg);
}
.set-avatar {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 19px;
  font-weight: 600;
  letter-spacing: 0.02em;
  flex: 0 0 auto;
}
/* v0.9.508 SETTINGS-PADDING-AND-PROFILE-KERNING-V2 — .set-id is a <span>;
   v0.9.490 added margin-top to the email but spans are inline by default,
   so the rule had no effect and the name + email rendered on a SINGLE line.
   Flex-column forces the children onto separate lines so the email reads
   as the secondary label below the display_name. */
.set-id {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.set-id__name {
  font-size: 18px;
  font-weight: 500;
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.set-id__email {
  font-size: 13px;
  color: var(--muted);
  margin-top: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* INSTELLINGEN-DE-PLUS-PIL-WORDT-EEN-STATUSREGEL — de abonnementsstand als
   derde regel in de accountkaart, onder naam en e-mail. Geen pil, geen kleur:
   iets kleiner en in --ink, zodat hij zwaarder weegt dan het e-mailadres maar
   de naam niet aanvecht. Hij mag WEL afbreken — "Opgezegd · Plus tot 4 oktober
   2026" past op 390px niet op één regel en een ellipsis zou de datum opeten,
   precies het gegeven waarvoor deze regel bestaat. */
.set-id__plan {
  font-size: 13px;
  color: var(--ink);
  margin-top: 6px;
  line-height: 1.3;
  overflow-wrap: anywhere;
}
/* EEN-KLANT-VAN-EEN-COACH-BETAALT-NIET-VOOR-DE-APP — de rustige regel die de
   Plus-pitch vervangt zodra de coach Sassu voor deze mens betaalt. Bewust geen
   accentkleur en geen icoon: dit is een constatering, geen aanbieding. */
.set-coachpay { display: flex; flex-direction: column; gap: 4px; }
.set-coachpay__line { font-size: 15px; color: var(--ink); line-height: 1.35; }
.set-coachpay__coach { font-size: 12px; color: var(--muted); }
.set-coachpay__manage { align-self: flex-start; margin-top: var(--space-3); }

/* GELD-DE-KLANT-BETAALT-ZIJN-REKENING-ZELF-IN-DE-APP — de openstaande losse
   rekeningen bij de coach, onder de abonnementskaart. Alles op tokens, dus licht
   en donker volgen zonder een tweede regel. Per rij: omschrijving links, bedrag
   rechts, datum eronder, één knop. Geen statuskleur en geen rood op een te late
   rekening: dit scherm vraagt om betalen, het maant niet. */
/* .set-card geeft ZELF geen padding: elk direct kind is een rijpositie en draagt
   zijn eigen ruimte (zie .set-card > .set-help hieronder). Zonder deze regels
   plakt de tekst tegen de afgeronde rand — gemeten op de mock, niet bedacht. */
.set-bills__title,
.set-bills__list { padding: var(--space-4); }
.set-bills__title {
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0;
  padding-bottom: 0;
}
.set-bills__list { display: flex; flex-direction: column; }
.set-bills__row { display: flex; flex-direction: column; gap: 4px; padding: var(--space-3) 0; }
/* Een scheidslijn TUSSEN de rijen en niet eronder: een streep onder de laatste rij
   zou de kaart lijken af te kappen terwijl de knop er nog onder staat. */
.set-bills__row + .set-bills__row { border-top: 1px solid var(--divider); }
.set-bills__row:first-child { padding-top: 0; }
.set-bills__head { display: flex; align-items: baseline; gap: var(--space-3); }
.set-bills__desc { font-size: 15px; color: var(--ink); line-height: 1.35; }
/* tabular-nums: twee bedragen onder elkaar lijnen op de komma uit in plaats van te
   dansen op de breedte van hun cijfers. */
.set-bills__amount {
  margin-left: auto;
  font-size: 15px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  white-space: nowrap;
}
.set-bills__date { font-size: 12px; color: var(--muted); margin: 0; }
/* width:auto EXPLICIET: .btn staat op width:100%, en align-self:flex-start haalt
   alleen het uitrekken weg — de breedte blijft dan gewoon de volle kaart. Zonder
   deze regel staat er per rekening een accentbalk over de hele kaart, en bij drie
   openstaande rijen is dat drie balken die om dezelfde tik schreeuwen. */
.set-bills__pay,
.set-bills__more {
  align-self: flex-start;
  width: auto;
  padding: 10px 18px;
  font-size: 12px;
}
.set-bills__pay { margin-top: var(--space-2); }
.set-bills__more { margin: 0 var(--space-4) var(--space-4); }

/* CREDITS-KLANT-ZIET-SALDO — read-only session-credit card under the profile
   header. Only rendered for accounts that actually hold credits. */
.set-credits { display: flex; flex-direction: column; gap: var(--space-3); margin-bottom: var(--space-5); }
.set-credits__card {
  background: var(--surface-card);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
}
.set-credits__head { display: flex; align-items: baseline; gap: var(--space-2); margin-bottom: var(--space-3); }
.set-credits__title { font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.set-credits__coach { font-size: 12px; color: var(--muted); margin-left: auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* INSTELLINGEN-JE-ZIET-IN-EEN-OOGOPSLAG-HOEVEEL-TRAININGEN-JE-NOG-HEBT —
   de drie even grote getallen (.set-credits__nums) zijn weg: drie gelijke
   getallen beantwoorden de enige vraag niet die de klant heeft. Eén groot
   getal (het saldo), daaronder een balk met de verdeling, dan de legenda. */
.set-credits__hero { display: flex; flex-direction: column; gap: 2px; margin-bottom: var(--space-3); }
.set-credits__hero-value {
  font-size: 40px;
  line-height: 1.05;
  font-weight: 600;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
}
.set-credits__hero-label { font-size: 12px; color: var(--muted); }
.set-credits__bar {
  display: flex;
  height: 8px;
  border-radius: 4px;
  overflow: hidden;
  background: var(--line);
}
.set-credits__seg { display: block; height: 100%; }
.set-credits__seg--used { background: var(--muted); }
.set-credits__seg--left { background: var(--accent); }
/* Vervallen is een feit, geen alarm: dezelfde rode betekenis, gedempt, zodat
   het segment niet harder roept dan het saldo ernaast. */
.set-credits__seg--expired { background: var(--danger); opacity: 0.35; }
.set-credits__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  margin-top: var(--space-2);
  font-size: 12px;
  color: var(--muted);
}
.set-credits__leg { display: inline-flex; align-items: center; gap: 6px; }
.set-credits__dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
.set-credits__dot--used { background: var(--muted); }
.set-credits__dot--left { background: var(--accent); }
.set-credits__dot--expired { background: var(--danger); opacity: 0.35; }
.set-credits__leg-value { font-variant-numeric: tabular-nums; color: var(--ink-2); }
/* WAT NU — één rustige regel onder de balk zodra het saldo bijna op is. Nooit
   een knop: credits koop je bij je coach, niet in de app. */
.set-credits__ask { margin: var(--space-3) 0 0; font-size: 13px; line-height: 1.4; color: var(--ink-2); }
.set-credits__ask-link {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--accent);
  text-decoration: underline;
  cursor: pointer;
}
.set-credits__hist { margin-top: var(--space-3); }
.set-credits__hist-sum {
  font-size: 13px;
  color: var(--muted);
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 6px;
}
.set-credits__hist-sum::-webkit-details-marker { display: none; }
/* Caret drawn with borders, never a glyph or emoji: rotates on open. */
.set-credits__hist-sum::after {
  content: '';
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  transition: transform 0.15s ease;
}
.set-credits__hist[open] .set-credits__hist-sum::after { transform: rotate(180deg); }
.set-credits__hist-list { margin-top: var(--space-2); display: flex; flex-direction: column; }
.set-credits__hist-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: 6px 0;
  border-top: 1px solid var(--hairline);
  font-size: 13px;
}
.set-credits__hist-date { color: var(--muted); flex: 0 0 auto; }
.set-credits__hist-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.set-credits__hist-delta { flex: 0 0 auto; font-variant-numeric: tabular-nums; color: var(--muted); }
.set-credits__hist-delta--in { color: var(--accent); }
/* CREDITS-KWARTAAL-GELDIGHEID — the expiry stamp rides under the purchase
   label, the lapsed rest is its own muted line above the history. */
.set-credits__hist-label { white-space: normal; }
.set-credits__hist-valid { display: block; font-size: 11px; color: var(--muted); }
/* .set-credits__expired — de losse 'Vervallen'-regel is opgegaan in de balk en
   zijn legenda (INSTELLINGEN-JE-ZIET-IN-EEN-OOGOPSLAG-HOEVEEL-TRAININGEN-JE-
   NOG-HEBT); hetzelfde getal twee keer tonen was ruis. */
/* TRAININGSCREDITS-ZIJN-DUIDELIJK — the sub-line that says what this card is
   NOT, and the pre-expiry warning (existing --warning token, no emoji). */
.set-credits__sub {
  margin: -6px 0 var(--space-3); font-size: 12px; line-height: 1.4; color: var(--muted);
}
/* Rustige regel met een icoon ervoor, geen gekleurd vlak: het is een
   vooraankondiging, geen storing. */
.set-credits__warn {
  display: flex; align-items: flex-start; gap: 6px;
  margin: var(--space-3) 0 0; font-size: 12px; line-height: 1.4; color: var(--warning);
}
.set-credits__warn-icon { width: 14px; height: 14px; flex: 0 0 auto; margin-top: 1px; }

.set-signout {
  background: none;
  border: 0;
  padding: var(--space-2);
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  flex: 0 0 auto;
}
.set-signout:hover { color: var(--danger); }

/* Groups — v0.9.347 SETTINGS-IOS-STYLE-RESTYLE: the wrapper itself is now
   fill-less; a child .set-card holds the rows in a rounded fill. The
   uppercase section label (.set-group__title) sits ABOVE the card. */
.set-group {
  padding: 0;
  border-bottom: 0;
  margin-bottom: var(--space-5);
}
.set-group:last-child { margin-bottom: 0; }
.set-group__title {
  font-size: 11px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--section-label);
  margin: 0 0 var(--space-2);
  padding: 0 var(--space-4);
  font-weight: 500;
}
/* Sub-label inside a card (e.g. "Accent" / "Theme" inside Appearance).
   v0.9.323 — A11Y-11A picker decouple needed two labelled rows in one group.
   Inside a card: pad to the card inset and use --section-label colour. */
.set-group__hint {
  font-size: 13px;
  color: var(--section-label);
  margin: 0;
  padding: var(--space-3) var(--space-4) var(--space-2);
}
.set-card > .set-group__hint:first-child { padding-top: var(--space-4); }
.set-group__hint--gap { margin-top: 0; }

/* SETTINGS-COLLAPSE-ALL-DEFAULT-AND-LANG-TOGGLE-PILL — Taal / Language
   promoted out of the accordion into a compact inline pill pair that sits
   between the profile-card and the first accordion section. Mirrors the
   INVITE-LOCALE-FIRST picker: active = navy (--accent) fill, inactive =
   outline. */
.set-lang-toggle {
  display: flex;
  gap: 8px;
  align-items: center;
  margin: var(--space-3) var(--space-4);
}
.set-lang-toggle__btn {
  flex: 0 0 auto;
  padding: 8px 16px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: transparent;
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.set-lang-toggle__btn[aria-pressed='true'] {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}

/* v0.9.347 SETTINGS-IOS-STYLE-RESTYLE — rounded card. --radius-lg matches
   iOS 13+ grouped-list rhythm. No shadow — iOS uses flat fills since
   iOS 13. Every direct child of the card is a row position; hairline
   dividers appear BETWEEN siblings via the inset ::before pseudo. */
.set-card {
  background: var(--surface-card);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.set-card > * { position: relative; }
.set-card > * + *::before {
  content: '';
  position: absolute;
  top: 0;
  left: var(--space-4);
  right: 0;
  height: 1px;
  background: var(--divider);
  transform: scaleY(0.5);
  transform-origin: top;
  pointer-events: none;
}
/* Suppress the divider between a sub-label / description hint and whatever
   row follows it — they're a logical pair, NOT two row positions. Applies
   to .set-themes tile-rows AND .set-row pill-toggle rows (e.g. "Turn off
   pre-filled weight suggestions" + Manual-mode toggle). */
.set-card > .set-group__hint + *::before,
.set-card > .set-help + *::before { display: none; }

/* Appearance tiles — v0.9.347 SETTINGS-IOS-STYLE-RESTYLE: multi-choice
   picks (Theme, Accent, Strength view) stay as a tile-row but now sit
   INSIDE a card as one logical row position. Padding is supplied by the
   .set-themes wrapper so the tiles align with the card inset. Binary
   on/off toggles (Pinch Zoom, Sounds chime, Manual mode) are no longer
   tile-pairs — they're .set-toggle pill toggles, see below. */
.set-themes {
  display: flex;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4) var(--space-4);
}
.set-theme {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: var(--space-3) var(--space-3);
  background: transparent;
  border: 1px solid var(--divider);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-standard), background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
  color: var(--ink);
  font-family: inherit;
}
.set-theme:hover { border-color: var(--ink-2); }
/* Generic multi-choice ON state — paints with --accent so the active tile
   matches the user-chosen theme colour (Sassu deviation from iOS green).
   .set-theme-mode / .set-pinch-zoom / .set-rest-chime / .set-strength-view /
   .set-strength-engine inherit this. The Accent picker tiles have their
   own .on override below — they should NOT fill with --accent because the
   tile IS the picker for that very colour. */
.set-theme.on {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--on-accent);
}
.set-theme.on .set-theme__label { color: var(--on-accent); }
.set-theme__dot {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  position: relative;
}
.set-theme__dot::after {
  content: "";
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'><polyline points='20 6 9 17 4 12'/></svg>");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 60% 60%;
}
.set-theme.on .set-theme__dot::after { display: flex; }
.set-theme__label { font-size: 13px; font-weight: 500; color: var(--ink-2); }
.set-theme.on .set-theme__label { color: var(--on-accent); }
/* Accent picker — its tiles are coloured DOTS, not filled tiles, so the
   ON state should outline (not fill) so the dot stays the visual focus. */
.set-card .set-themes .set-theme:has(.set-theme__dot).on {
  background: transparent;
  border-color: var(--accent);
  color: var(--ink);
}
.set-card .set-themes .set-theme:has(.set-theme__dot).on .set-theme__label {
  color: var(--ink);
}
/* MOBILE-COMPAT-HAS-SELECTOR-FALLBACK (F2) — plain-selector twins for engines
   without :has(). Keyed on a static .set-theme--dot marker class that mirrors
   the :has(.set-theme__dot) scoping. NOTE: this accent-dot picker is currently
   dormant (retired accent axis, THEME-PRESETS-UNIFY v0.9.362 — no live JS/DOM
   producer found via grep), so these twins are defensive/forward-compatible.
   Identical declarations to the :has() rules above — do NOT diverge. */
.set-card .set-themes .set-theme.set-theme--dot.on {
  background: transparent;
  border-color: var(--accent);
  color: var(--ink);
}
.set-card .set-themes .set-theme.set-theme--dot.on .set-theme__label {
  color: var(--ink);
}

/* SETTINGS-THEME-PRESETS-UNIFY (v0.9.362) — iOS grouped-list row for a
   single named theme preset. One card holds N preset rows separated by
   the standard inset hairline (.set-card__divider). Each row carries a
   24×24 split swatch (background half + accent half), the preset name
   and a one-line description, and a right-side checkmark that only
   renders on the active row. Min-height 44pt per WCAG 2.5.5 + Apple
   HIG. Selecting a row tints its background subtly via accent-mix so
   the checkmark + name read as the active state without flipping the
   whole row to --accent (which would compete with the swatch). */
.set-theme-preset {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  min-height: 56px;
  padding: var(--space-3) var(--space-4);
  background: transparent;
  border: 0;
  cursor: pointer;
  text-align: left;
  color: var(--ink);
  font-family: inherit;
  transition: background var(--dur-fast) var(--ease-standard);
}
.set-theme-preset:hover { background: var(--surface-muted); }
.set-theme-preset.is-on { background: var(--surface-muted); }
.set-theme-preset__swatch {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  border-radius: var(--radius-md);
  border: 1px solid var(--divider);
  overflow: hidden;
  position: relative;
  background: var(--swatch-bg, var(--surface));
}
.set-theme-preset__swatch::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 50%;
  background: var(--swatch-accent, var(--accent));
}
.set-theme-preset__main {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.set-theme-preset__name {
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
}
.set-theme-preset__sub {
  font-size: 12px;
  color: var(--section-label);
}
.set-theme-preset__check {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--accent);
  opacity: 0;
  transform: scale(.8);
  transition: opacity var(--dur-fast) var(--ease-standard), transform var(--dur-fast) var(--ease-standard);
}
.set-theme-preset__check svg { width: 22px; height: 22px; }
.set-theme-preset.is-on .set-theme-preset__check { opacity: 1; transform: scale(1); }
/* THEME-FOLLOW-THE-SUN — when the flag is on, the two finishes the sun rotates
   between get a hairline marker. Deliberately NOT a disabled/greyed state: the
   rows stay tappable, and such a tap is what switches the automatism off. */
.set-theme-preset--sun { box-shadow: inset 2px 0 0 0 var(--accent); }
/* THEME-1-FINISH-REGISTRY — premium finishes stay visible but lock-tagged
   for non-premium users (existing is_premium gate; tap shows the Premium
   upsell toast instead of switching). */
.set-theme-preset__lock {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--section-label);
}
.set-theme-preset__lock svg { width: 18px; height: 18px; }
.set-theme-preset.is-locked .set-theme-preset__name,
.set-theme-preset.is-locked .set-theme-preset__sub { opacity: .6; }
.set-theme-preset.is-locked .set-theme-preset__swatch { opacity: .75; }
/* Explicit hairline between two preset rows inside the same card. The
   generic .set-card > * + *::before rule already handles divider drawing,
   but the divider element is included as a sibling so JS row ordering
   stays explicit — re-orderable as future presets land without rewriting
   the cascade rule. */
.set-card__divider {
  height: 0.5px;
  background: var(--divider);
  margin-left: var(--space-4);
}

/* v0.9.508 SETTINGS-PADDING-AND-PROFILE-KERNING-V2 — wrapper for an
   inline button placed at the bottom of a Settings card (e.g. App →
   Force refresh). Supplies the same 16/12 gutter as .set-row so the
   button doesn't bleed to the card edges. */
.set-card__action {
  padding: var(--space-3) var(--space-4) var(--space-4);
}

/* v0.9.347 SETTINGS-IOS-STYLE-RESTYLE — pill toggle component. ~51pt ×
   31pt fully rounded with a 27pt circular thumb (2pt inset). OFF: track
   --toggle-track-off, thumb --toggle-thumb-off. ON: track --accent, thumb
   --on-accent (per feedback_accent_buttons_on_accent_token — never
   hardcoded white; respects per-brand swap). Smooth 200ms transition. */
.set-toggle {
  -webkit-appearance: none;
  appearance: none;
  position: relative;
  flex: 0 0 auto;
  width: 51px;
  height: 31px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--toggle-track-off);
  cursor: pointer;
  transition: background var(--dur-base) var(--ease-standard);
  font-family: inherit;
}
.set-toggle__thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 27px;
  height: 27px;
  border-radius: 50%;
  background: var(--toggle-thumb-off);
  box-shadow: var(--elev-1);
  transition: left var(--dur-base) var(--ease-standard), background var(--dur-base) var(--ease-standard);
}
.set-toggle.is-on { background: var(--accent); }
.set-toggle.is-on .set-toggle__thumb { left: 22px; background: var(--on-accent); }
.set-toggle:focus-visible { outline-offset: 4px; }

/* Disclosure rows (Change password, lists) — v0.9.347 SETTINGS-IOS-STYLE-
   RESTYLE: inside a card these become 44pt-min-height drilldown-style
   rows. The summary mimics a .set-row; the body unfolds beneath. */
.set-disclose { border-top: 0; }
.set-disclose:first-of-type { border-top: 0; }
.set-disclose__sum {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 11px var(--space-4);
  font-size: 15px;
  color: var(--ink);
}
.set-disclose__sum::-webkit-details-marker { display: none; }
.set-disclose__title { flex: 0 0 auto; }
.set-disclose__count { color: var(--section-label); flex: 1 1 auto; }
.set-disclose__sum::after {
  content: "›";
  color: var(--section-label);
  font-size: 20px;
  line-height: 1;
  transition: transform var(--dur-fast) var(--ease-standard);
  flex: 0 0 auto;
}
.set-disclose[open] > .set-disclose__sum::after { transform: rotate(90deg); }
.set-disclose__body { padding: 0 var(--space-4) var(--space-4); }

/* Search inside a list */
.set-search {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  padding: 10px 12px;
  margin-bottom: var(--space-3);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-size: 14px;
  color: var(--ink);
}
.set-search::placeholder { color: var(--muted); }
.set-search:focus { outline: none; border-color: var(--ink-2); }

/* INSTELLINGEN-JE-ZOEKT-EEN-KNOP-EN-VINDT-HEM — de trefferlijst van het
   zoekveld bovenaan Instellingen. Eén regel per treffer: eerst de deur waar de
   instelling achter woont (gedempt), dan de instelling zelf. Het gedempte deel
   gebruikt --section-label, het bestaande token voor een bijschrift naast een
   rijtitel; een --ink-3 bestaat in deze stylesheet niet. */
.set-hit__door { color: var(--section-label); }
.set-hit__leaf { color: var(--ink); }
/* Niets gevonden is geen gebeurtenis: één rustige regel, geen illustratie. */
.set-hits__empty {
  margin: 0;
  padding: 11px var(--space-4);
  color: var(--section-label);
  font-size: 15px;
}
/* Het aanwijzen na een tik op een treffer: een zachte accentrand om de rij,
   géén kleurvlak (dat zou de rij als "aan" of "fout" laten lezen). 1,2 s, en
   met prefers-reduced-motion staat hij er even maar zonder de fade. */
.set-row--hl {
  box-shadow: inset 0 0 0 1.5px var(--accent);
  border-radius: var(--radius-sm);
  animation: set-row-hl-fade 1.2s ease-out both;
}
@keyframes set-row-hl-fade {
  0%, 70% { box-shadow: inset 0 0 0 1.5px var(--accent); }
  100%    { box-shadow: inset 0 0 0 1.5px transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .set-row--hl { animation: none; }
}

.set-list { display: block; }

/* Generic rows — v0.9.347 SETTINGS-IOS-STYLE-RESTYLE: 44pt min-height per
   Apple HIG touch target; horizontal padding matches the card inset; the
   line divider is supplied by the .set-card > * + *::before rule (inset
   to the label x-coord), so .set-row itself no longer carries a border. */
.set-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 44px;
  padding: 11px var(--space-4);
  border-top: 0;
  font-size: 15px;
  gap: var(--space-3);
}
.set-row:first-child { border-top: 0; }
.set-row__label { color: var(--ink); flex: 1 1 auto; }
/* CONNECTOR-KLANTNUMMER-BIJNAAM-EN-GEEN-IDENTITEIT — one muted advice line
   underneath a settings row. Advice, not an error: no danger colour. */
.set-row__hint {
  margin: -4px 0 0;
  padding: 0 var(--space-4) 10px;
  color: var(--ink-muted, var(--ink));
  font-size: 13px;
  line-height: 1.4;
}
.set-row__hint[hidden] { display: none; }
/* SETTINGS-MCP-REMOVE-AND-MARKDOWN-EXPORT (v0.9.667) — two-line tappable row:
   title on top, muted subtitle beneath (data-export tile). */
.set-row__label--stack { display: flex; flex-direction: column; gap: 2px; }
/* VRIENDEN-CHATTEN-IN-DEZELFDE-LIJST — a settings row carrying MORE than one
   mini-button. Without this a long name pushes the second button past the card
   edge: the label column would not shrink, so the buttons did. */
.set-row__acts { display: flex; align-items: center; gap: var(--space-2); flex: 0 0 auto; }
.set-row--acts .set-row__label { min-width: 0; }
.set-row--acts .set-row__title,
.set-row--acts .set-row__sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* CHAT-EEN-VRIENDENRIJ-IS-EEN-DEUR — the tappable name block of a row that ALSO
   carries mini-buttons. It cannot BE the row (a button inside a button is
   invalid HTML), so it is a button sibling of .set-row__acts that takes the
   remaining width; the chevron rides at its right edge, next to the actions.
   Chrome reset mirrors the button.set-row rule further down. */
.set-row__door {
  display: flex;
  align-items: center;
  flex: 1 1 auto;
  min-width: 0;
  padding: 0;
  background: transparent;
  border: 0;
  text-align: left;
  font: inherit;
  font-size: 15px;
  color: var(--ink);
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
.set-row__door .set-row__chev { margin-left: auto; }
.set-row__door:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.set-row__title { color: var(--ink); }
.set-row__sub { color: var(--section-label); font-size: 0.82rem; line-height: 1.3; }
.set-row__value { color: var(--section-label); font-variant-numeric: tabular-nums; }
.set-row__value.ok { color: var(--ok); }
/* SETTINGS-ROW-BUTTON-RESET-AND-CHROME-UNIFY (v0.9.1082, S-04) — any <button>
   built with the generic .set-row class inherited raw user-agent chrome
   (grey bevel, shrunk width) because only .set-row--nav carried a reset and
   .set-row--tappable had no rule at all. Reset button chrome here so every
   plain set-row button renders as a full-width flat row identical to the
   nav drill-in rows in both Dag and Nacht. Token-only, no raw hex. */
button.set-row,
.set-row--tappable {
  width: 100%;
  background: transparent;
  border: 0;
  text-align: left;
  font: inherit;
  font-size: 15px;
  color: var(--ink);
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
button.set-row:not(.set-theme-preset):not(.set-row--danger-action):hover,
.set-row--tappable:hover { background: var(--surface-2, var(--surface)); }

/* CHECKIN-METRICS-SETTINGS-SELECTION-AND-ORDER — per-row reorder controls +
   main-metric (hoofdmetric) badge in Settings → Metrics. */
.set-reorder { display: inline-flex; flex-direction: column; gap: 2px; flex: 0 0 auto; }
.set-reorder__btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 19px; padding: 0;
  background: transparent; border: 0; cursor: pointer;
  color: var(--section-label); border-radius: var(--radius-sm);
}
.set-reorder__btn svg { width: 16px; height: 16px; }
.set-reorder__btn:disabled { opacity: 0.3; cursor: default; }
.set-reorder__btn:not(:disabled):hover { color: var(--ink); }
.set-row__badge {
  flex: 0 0 auto; font-size: 0.68rem; font-weight: 600;
  padding: 2px 8px; border-radius: var(--radius-pill);
  background: var(--accent); color: var(--on-accent);
  text-transform: uppercase; letter-spacing: 0.04em;
}

/* COACH-SPRINT-7 — Privacy & Coaching permission matrix */
.set-coach-perm { padding: var(--space-2) 0; }
.set-coach-perm__row { gap: var(--space-3); }
.set-select {
  padding: 6px var(--space-3); font-size: 0.95rem; color: var(--ink);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); box-sizing: border-box; min-width: 9.5rem;
}
.set-select:focus { outline: none; border-color: var(--accent); }
/* Sassu AI section (SASSU-AI-HARNESS-MEMORY-AND-MODEL-GATEWAY) */
.set-row--stack-full { align-items: flex-start; flex-wrap: wrap; gap: var(--space-2); }
.set-row--stack-full .set-select { min-width: 100%; }
.set-subhead { margin: var(--space-3) 0 0; font-size: 0.95rem; font-weight: 600; color: var(--ink); }
.set-chatai-memlist { display: flex; flex-direction: column; }
.set-chatai-memrow { align-items: flex-start; gap: var(--space-2); }
.set-chatai-badge {
  align-self: flex-start; margin-top: 3px; padding: 1px 8px;
  font-size: 0.72rem; text-transform: lowercase; color: var(--section-label);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-pill);
}
.set-icon-btn {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0; color: var(--ink);
  background: transparent; border: 0; border-radius: var(--radius-sm); cursor: pointer;
}
.set-icon-btn svg { width: 18px; height: 18px; }
.set-icon-btn--danger { color: var(--danger); }
.set-icon-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.set-row__value.bad { color: var(--danger); }
/* v0.9.347 — drilldown chevron, lives on rows that navigate to a sub-screen. */
.set-row--drill::after {
  content: "›";
  color: var(--section-label);
  font-size: 20px;
  line-height: 1;
  margin-left: 2px;
  flex: 0 0 auto;
}

/* Link-style row with chevron (Switch to coach) — v0.9.347: 44pt-height
   row inside a card; the card supplies dividers, so the linkrow's own
   bottom-border is dropped. */
.set-linkrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  min-height: 44px;
  padding: 11px var(--space-4);
  border: 0;
  background: none;
  cursor: pointer;
  font-size: 15px;
  color: var(--ink);
  text-align: left;
  font-family: inherit;
}
.set-linkrow:hover { color: var(--ink); }
.set-linkrow::after { content: "›"; color: var(--section-label); font-size: 20px; line-height: 1; }
/* v0.9.317 — non-interactive variant for reserved-feature affordances (e.g.
   CONNECT API reference for non-trainers). Visible + readable but not a link.
   pointer-events:none is load-bearing — blocks future accidental click wires. */
.set-linkrow.is-disabled { opacity: 0.4; cursor: default; pointer-events: none; }
.set-linkrow.is-disabled:hover { color: var(--ink-2); }
.set-linkrow.is-disabled::after { color: var(--section-label); }

/* v0.9.190 — Switch-to-coach is a prominent themed CTA (was a plain link row).
   Painted in the selected theme accent with --on-accent for legibility on lime.
   v0.9.347 — inside a card it stays accent-painted but loses its own radius
   and shadow so it integrates with the card's flat fill + dividers. */
.set-coach-cta {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  width: 100%; padding: 14px 16px; margin-bottom: var(--space-4);
  border: 0; border-radius: var(--radius-lg);
  background: var(--accent); color: var(--on-accent);
  cursor: pointer; text-decoration: none;
  box-shadow: var(--elev-1);
  transition: background var(--dur-fast) var(--ease-standard), transform var(--dur-fast) var(--ease-standard);
}
.set-coach-cta:hover  { background: var(--accent-2); }
.set-coach-cta:active { transform: scale(.99); }
.set-coach-cta__main  { display: flex; flex-direction: column; gap: 2px; text-align: left; }
.set-coach-cta__label { font-size: 15px; font-weight: 700; }
.set-coach-cta__sub   { font-size: 12px; font-weight: 500; opacity: .85; }
.set-coach-cta__arrow { font-size: 22px; line-height: 1; font-weight: 600; }
.set-card > .set-coach-cta { border-radius: 0; margin-bottom: 0; box-shadow: none; }

/* v0.9.490 SETTINGS-PREMIUM-FEATURES — accent-highlighted upsell row used by
   the new Premium Features section (Switch to Coach + future Connect-with-AI).
   Subtle accent-tinted background so the eye scans these as paywall-gated
   features at a glance. Token-only — --accent-light is the soft fill, --accent
   is the icon/label color, both Dag (blauw) + Nacht (lijmgroen) inherit. */
.set-row--premium {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  min-height: 56px;
  padding: 12px var(--space-4);
  background: var(--accent-light);
  border: 0;
  text-align: left;
  font: inherit;
  font-size: 15px;
  color: var(--accent);
  cursor: pointer;
  text-decoration: none;
  gap: var(--space-3);
}
.set-row--premium:hover { background: var(--accent-light); filter: brightness(0.96); }
.set-row--premium .set-row__label { color: var(--accent); font-weight: 600; flex: 1 1 auto; }
.set-row--premium .set-row__chev { color: var(--accent); font-size: 22px; line-height: 1; flex: 0 0 auto; }

.set-help {
  font-size: 13px;
  color: var(--section-label);
  margin: 0 0 var(--space-3);
  line-height: 1.45;
}
.set-card > .set-help { padding: var(--space-3) var(--space-4); margin: 0; }

/* Item rows (archived programs, custom exercises) — v0.9.347: 44pt min
   height matches the card-row rhythm. Dividers inside lists are still
   per-item (full-width inside the disclosure body); divider colour
   token-driven. */
.set-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: 44px;
  padding: 10px 0;
  border-top: 1px solid var(--divider);
}
.set-item:first-child { border-top: 0; }
.set-item__name { font-size: 14px; color: var(--ink); }
.set-item__meta { font-size: 11px; color: var(--section-label); margin-top: 2px; }
.set-item__actions { display: flex; gap: 6px; flex: 0 0 auto; }
.set-mini {
  background: none;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 6px 12px;
  font-size: 12px;
  color: var(--ink-2);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.set-mini:hover { border-color: var(--ink-2); }
.set-mini--danger:hover { border-color: var(--danger); color: var(--danger); }

/* PANTRY-ADMIN-MANAGEMENT-UI — shared-pantry membership roster. */
.set-role-chip {
  display: inline-block;
  padding: 1px 8px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  font-size: 11px;
  color: var(--ink-2);
  margin-left: 6px;
  vertical-align: middle;
}
.set-role-chip--admin { border-color: var(--accent); color: var(--accent); }
.set-row--sub { padding-left: 44px; }
.set-row--actions { display: flex; gap: 8px; }
.set-priv-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0 14px; }
.set-priv-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  font-size: 12px;
  color: var(--ink-2);
}
.set-priv-chip svg { width: 14px; height: 14px; }
.set-priv-chip.is-on { border-color: var(--accent); color: var(--accent); }
.set-friend-picker { display: flex; flex-direction: column; margin: 4px 0 8px; }

/* MCP Sprint 4 (v0.9.359) — API & Integrations card.
   .set-apikey-reveal: shown once after a mint succeeds; carries the readonly
   key input + Copy button. .set-apikey-buttons: side-by-side Generate +
   Revoke buttons inside a .set-row, using the same gap rhythm as the
   surrounding set-mini affordances. Tokens only — no hardcoded hex. */
.set-apikey-reveal {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--divider);
}
.set-apikey-buttons {
  display: flex;
  gap: var(--space-2);
  flex: 0 0 auto;
}

/* MCP-CONNECTOR-2 — Settings → API & Koppelingen card.
   Three vertical sub-blocks inside one .set-card: Verbind met Claude (top),
   Verbonden apps (middle), Voor ontwikkelaars (bottom). The bottom block is
   intentionally lighter visual weight (smaller heading + muted copy) so the
   OAuth Verbind path reads as the canonical entry point and the bearer mint
   reads as the developer fallback. All tokens; --on-accent on accent fills. */
.mcp-connect { padding: var(--space-4); }
.mcp-connect__title {
  margin: 0 0 var(--space-2);
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
}
.mcp-connect__copy {
  margin: 0 0 var(--space-4);
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-2);
}
.mcp-connect__ctas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.mcp-connect__cta {
  width: auto;
  flex: 0 1 auto;
  padding: 10px 16px;
  font-size: 13px;
  letter-spacing: 0.04em;
  text-transform: none;
  border-radius: var(--radius-sm);
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.mcp-connect__icon {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
}

.mcp-apps {
  padding: var(--space-4);
  border-top: 1px solid var(--divider);
}
.mcp-apps__title {
  margin: 0 0 var(--space-3);
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
}
.mcp-apps__list { display: flex; flex-direction: column; gap: var(--space-2); }
.mcp-apps__empty {
  margin: 0;
  font-size: 13px;
  color: var(--ink-2);
  line-height: 1.5;
}
.mcp-apps__row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--surface-muted);
  border-radius: var(--radius-sm);
}
/* S-11 — top-align the Ontkoppelen button and let the name+meta column claim
   the remaining width so "gekoppeld … laatst gebruikt …" stacks under the
   name instead of wrapping mid-phrase against the button at 390 px. */
.mcp-apps__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.mcp-apps__name { font-size: 14px; font-weight: 600; color: var(--ink); }
.mcp-apps__meta { font-size: 12px; color: var(--ink-2); }
.mcp-apps__revoke {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.mcp-apps__revoke-icon { width: 14px; height: 14px; flex: 0 0 auto; }

/* SETTINGS-KOPPELINGEN-SUBSCREEN-AND-CONNECTION-GROUPING — the /settings/
   koppelingen drill-in. Connector paste-URL row + connections grouped by
   client_name (one expandable row per app, count badge, per-connection
   Ontkoppelen inside) + the API-key mint/revoke block. Tokens only; no raw
   hex so css-hex-lint stays clean across all finishes. */
.kopp-linkrow { display: flex; gap: var(--space-2); padding: var(--space-3) var(--space-4); }
.kopp-link {
  flex: 1 1 auto;
  min-width: 0;
  background: var(--surface-muted);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 9px 10px;
  font-size: 12px;
  color: var(--ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.kopp-apps { display: flex; flex-direction: column; }

/* USER-CALENDAR-FEEDS-IMPORT-EXPORT — calendar feed import list + add form. */
.kopp-cal { display: flex; flex-direction: column; }
.kopp-cal-list { display: flex; flex-direction: column; }
.kopp-cal-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
}
.kopp-cal-row + .kopp-cal-row { border-top: 1px solid var(--line); }
.kopp-cal-row__text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.kopp-cal-row__name { font-size: 14px; color: var(--ink-1); }
.kopp-cal-row__meta {
  font-size: 12px; color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.kopp-cal-add {
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--line);
}
.kopp-cal-input {
  background: var(--surface-muted);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 9px 10px;
  font-size: 13px;
  color: var(--ink-1);
}
.set-mini--on { border-color: var(--accent); color: var(--accent); }

/* Grouped app — a <details>; summary is the browse-level row, body holds the
   per-connection detail rows revealed on tap. */
.kopp-group { border: 0; }
.kopp-group + .kopp-group { border-top: 1px solid var(--line); }
.kopp-group__sum {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  min-height: 44px;
  cursor: pointer;
  list-style: none;
}
.kopp-group__sum::-webkit-details-marker { display: none; }
.kopp-group__icon {
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  border-radius: var(--radius-md);
  background: var(--accent-light);
  display: flex;
  align-items: center;
  justify-content: center;
}
.kopp-group__icon svg { width: 19px; height: 19px; color: var(--accent); }
.kopp-group__text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.kopp-group__name { font-size: 14.5px; font-weight: 600; color: var(--ink); }
.kopp-group__badge {
  display: inline-block;
  margin-left: 8px;
  font-size: 11px;
  font-weight: 600;
  color: var(--accent);
  background: var(--accent-light);
  border-radius: var(--radius-pill);
  padding: 1px 8px;
  vertical-align: middle;
}
.kopp-group__meta { font-size: 12px; color: var(--ink-2); }
.kopp-group__chev { flex: 0 0 auto; color: var(--ink-2); display: flex; transition: transform var(--dur-fast) var(--ease-standard); }
.kopp-group__chev svg { width: 20px; height: 20px; }
.kopp-group[open] .kopp-group__chev { transform: rotate(90deg); }
.kopp-group__body { background: var(--surface-muted); }

.kopp-conn {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  padding: 11px var(--space-4) 11px 62px;
}
.kopp-conn + .kopp-conn { border-top: 1px solid var(--line); }
.kopp-conn__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.kopp-conn__name { font-size: 13px; font-weight: 600; color: var(--ink); }
.kopp-conn__meta { font-size: 11.5px; color: var(--ink-2); }
.kopp-conn__revoke { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 6px; }
.kopp-conn__revoke-icon { width: 14px; height: 14px; flex: 0 0 auto; }

/* CONNECTOR-KLANT-ZIET-HET-EN-BESLIST — the client's AI gate + its read log.
   The greyed state is on the ROW, not just the button: the second switch is
   unreachable until the first is on, and that has to be legible at a glance
   rather than only discoverable by tapping. */
.kopp-aigate { display: flex; flex-direction: column; }
.kopp-aigate .set-row--disabled { opacity: 0.45; }
.kopp-aigate__log { display: flex; flex-direction: column; gap: 2px; }
.kopp-aigate__logrow {
  display: flex;
  justify-content: space-between;
  gap: var(--space-2);
  padding: 6px 0;
  font-size: 12.5px;
}
.kopp-aigate__logwhat { color: var(--ink); min-width: 0; }
.kopp-aigate__logwhen { color: var(--ink-2); flex: 0 0 auto; }

/* CONNECTOR-TOESTEMMING-ZIEN-INTREKKEN-EN-TERUGLEZEN — his own answer, above
   the switch. The answer line is the only sentence in this block in full ink:
   it is a statement about HIM, while everything under the switch is the
   explanation. The read-back of an older consent text is set back on purpose —
   it is a quotation of words that are no longer in force, and reading as loudly
   as the current paragraph is exactly how the two get mixed up. */
.kopp-aigate__answer:not(:empty) { padding-bottom: var(--space-2); }
.kopp-aigate__answerline { color: var(--ink); }
.kopp-aigate__oldtext {
  border-left: 2px solid var(--line);
  padding-left: var(--space-2);
  opacity: 0.8;
}

.kopp-dev { display: flex; flex-direction: column; }
.kopp-apikey-reveal { padding-top: var(--space-2); }

/* NUTRITION-HISTORY-IMPORT-UI-SETTINGS-AND-COACH — import block. Tokens only. */
.kopp-import { display: flex; flex-direction: column; gap: var(--space-2); padding: 0 var(--space-4) var(--space-3); }
.kopp-import__srcs { display: flex; flex-wrap: wrap; gap: var(--space-2); padding-top: var(--space-2); }
.kopp-import__guide { margin: 0; }
.kopp-import__filename { margin: 0; font-style: italic; }
.kopp-import__warn { margin: 0; color: var(--danger); }
.kopp-import__map { display: flex; flex-direction: column; gap: var(--space-2); }
.kopp-import__maprow { display: flex; align-items: center; gap: var(--space-2); }
.kopp-import__maplbl { flex: 0 0 90px; font-size: 13px; color: var(--ink-2); }
.kopp-import__mapsel {
  flex: 1 1 auto; min-width: 0;
  background: var(--surface-muted);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 8px 10px;
  font-size: 13px;
  color: var(--ink-1);
}
.kopp-import__actions { display: flex; gap: var(--space-2); flex-wrap: wrap; padding-top: var(--space-2); }
.kopp-import__result { display: flex; flex-direction: column; }
.kopp-import__preview {
  display: flex; flex-direction: column; gap: var(--space-2);
  background: var(--surface-muted);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: var(--space-3);
  margin-top: var(--space-2);
}
.kopp-import__sum { margin: 0; font-size: 14px; color: var(--ink-1); font-weight: 600; }
.kopp-import__ok { margin: 0; font-size: 14px; color: var(--accent); font-weight: 600; }
.kopp-import__warns { margin: 0; padding-left: 18px; font-size: 12px; color: var(--ink-2); }
.kopp-import__table { width: 100%; border-collapse: collapse; font-size: 12px; }
.kopp-import__table th {
  text-align: left; color: var(--ink-2); font-weight: 600;
  padding: 4px 6px; border-bottom: 1px solid var(--line);
}
.kopp-import__table td {
  padding: 4px 6px; color: var(--ink-1);
  border-bottom: 1px solid var(--line);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 120px;
}
.kopp-import__batches { display: flex; flex-direction: column; }
.kopp-import__batch {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) 0;
}
.kopp-import__batch + .kopp-import__batch { border-top: 1px solid var(--line); }
.kopp-import__batch--muted { opacity: 0.55; }
.kopp-import__batch-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.kopp-import__batch-name { font-size: 14px; color: var(--ink-1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kopp-import__batch-meta { font-size: 12px; color: var(--ink-2); }

/* NUTRITION-HISTORY-BULK-SCREENSHOT-IMPORT — the screenshot route rides the
 * kopp-import wizard shell; only the extra controls it adds live here. The
 * preview date is an EDITABLE input because a screenshot often shows
 * "Gisteren" and only the user knows which year that was. */
.kopp-shot { display: flex; flex-direction: column; gap: var(--space-2); }
.kopp-shot__anchor,
.kopp-shot__date {
  font: inherit;
  color: var(--ink-1);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 6px 8px;
  min-height: 44px;   /* thumb target — the date is meant to be corrected here */
}
.kopp-shot__date { min-height: 36px; font-size: 12px; padding: 2px 6px; }
.kopp-shot__count { margin: 0; }
/* A guessed date is marked with a warning rule, not a tinted fill: the fill
   colour would have to be a new token in all six finishes to stay readable. */
.kopp-shot__row--low td { background: var(--surface-muted); }
.kopp-shot__row--low td:first-child { box-shadow: inset 2px 0 0 var(--warning); }
.kopp-shot__flag {
  display: block;
  margin-top: 2px;
  font-size: 11px;
  font-weight: 600;
  color: var(--ink-2);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
/* The provenance tag on an imported log row — quiet, never a second banner. */
.nut-entry__estimated {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--surface-muted);
  color: var(--ink-2);
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
}
.nut-empty__import {
  align-self: center;
  background: none;
  border: none;
  padding: var(--space-2);
  min-height: 44px;
  color: var(--ink-2);
  font: inherit;
  font-size: 13px;
  text-decoration: underline;
  cursor: pointer;
}

/* CHAT-GEMINI-1 (v0.9.656) — Settings → Premium → Klantbeheer · Chat-toegang.
 * Per-row layout mirrors .mcp-apps__row's two-column shape: name + state
 * label on the left, control (toggle or locked badge) on the right. Tokens
 * only — no raw hex so css-hex-lint stays clean across all six finishes. */
.chatgrant-list { display: flex; flex-direction: column; gap: var(--space-2); }
/* COACH-CLIENT-ROW-COMPACT-LAYOUT (v0.9.734) — with up to four trailing
   controls (Link to existing account + Send claim link + Premium badge + the
   chat-access toggle) the nowrap row overflowed on a phone. flex-wrap lets the
   secondary actions drop to a second line when they don't fit; the name+label
   block grows to hold the first line and keeps everything single-line when
   there's room. */
.chatgrant-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-1) var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: var(--surface-muted);
  border-radius: var(--radius-sm);
}
.chatgrant-row__text { flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.chatgrant-row__name { font-size: 14px; font-weight: 600; color: var(--ink); }
.chatgrant-row__label { font-size: 12px; color: var(--ink-2); }
/* DE-STRANGLER-FIG-LIJST-TELT-ZICHZELF-AF — de migratielijst. De
   waarschuwingsmarkering zegt dat een datum VOORBIJ is, niet dat er iets is
   afgepakt: er vervalt in deze fase niets. Icoon, nooit een emoji. */
.migration-row__warn {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  font-size: 12px;
  font-weight: 600;
  color: var(--danger);
}
.migration-row__warn svg { width: 14px; height: 14px; flex: 0 0 auto; }
.migration-row__edit {
  flex: 1 1 100%;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.migration-row__date {
  flex: 1 1 auto;
  min-width: 0;
  padding: var(--space-1) var(--space-2);
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.migration-row__save { flex: 0 0 auto; color: var(--on-accent); }
.chatgrant-row__locked {
  flex: 0 0 auto;
  margin-left: auto;
  white-space: nowrap;
  padding: 2px var(--space-2);
  font-size: 11px;
  font-weight: 600;
  color: var(--on-accent);
  background: var(--accent);
  border-radius: var(--radius-pill);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
/* COACH-CLIENT-PREMIUM-TOGGLE (2026-06-22) — stacked Premium + Chat-access
   toggle pair on each client tile. Reuses .set-toggle + .chatgrant-row__locked
   styling; this only lays out the two labelled rows. */
.client-tile__toggles {
  flex: 0 0 auto;
  margin-left: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  align-items: flex-end;
}
.client-tile__toggles .toggle-row {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
}
.client-tile__toggles .toggle-label {
  font-size: 12px;
  font-weight: 500;
  color: var(--ink-2);
}
.client-tile__toggles .chatgrant-row__locked { margin-left: 0; }

/* COACH-LINK-DUMMY (v0.9.692) — trainer-admin-only merge button. Neutral
   outline (not accent-filled) so it reads as a secondary action and sidesteps
   the accent-contrast lint rule. */
.chatgrant-row__merge {
  flex: 0 0 auto;
  white-space: nowrap;
  padding: var(--space-1) var(--space-2);
  font-size: 11px;
  font-weight: 500;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--divider);
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.chatgrant-row__merge:hover { background: var(--surface-muted); }
.chatgrant-row__merge:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.mcp-dev {
  padding: var(--space-4);
  border-top: 1px solid var(--divider);
  opacity: 0.92;
}
.mcp-dev__title {
  margin: 0 0 var(--space-2);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--section-label);
  text-transform: uppercase;
}
.mcp-dev__copy {
  margin: 0 0 var(--space-3);
  font-size: 12px;
  line-height: 1.5;
  color: var(--ink-2);
}
.mcp-dev__guide { font-size: 13px; }

/* QR sheet — reuses the .ui-dlg primitive. The QR is paint, not chrome, so its
   quiet zone stays white and its modules black inside the SVG whatever the theme:
   a camera needs that contrast, and gym light is not kind. Used by the friend
   "My QR" sheet (EEN-QR-EN-JULLIE-ZIJN-VRIENDEN). The URL is deliberately NOT
   shown as copyable text — this code is handed over face to face, not forwarded. */
.ui-dlg--qr { max-width: 360px; }
.fz-qr__svgwrap {
  display: flex; justify-content: center;
  margin: 0 0 var(--space-3);
  padding: var(--space-3);
  background: #ffffff; /* allow-hex: QR quiet-zone must be white in both themes — camera contrast */
  border-radius: var(--radius-sm);
}
.fz-qr__svgwrap svg { width: 100%; max-width: 240px; height: auto; display: block; }
.fz-qr__note {
  margin: 0 0 var(--space-4);
  font-size: 12px;
  color: var(--ink-2);
  text-align: center;
}
/* DE-QR-KAN-OOK-ALS-LINKJE-DE-DEUR-UIT — the third button in the QR sheet.
   Explicit pixels on the glyph (lint rule m: a percentage-sized inline svg has
   no intrinsic size), and the row wraps because three labels do not fit on one
   line at 390px. */
.ui-dlg--qr .ui-dlg__row { flex-wrap: wrap; }
.ui-dlg--qr .ui-dlg__row { row-gap: var(--space-2); }
/* Two class names deep on purpose: .ui-dlg__btn { flex: 1 1 0 } is declared
   LATER in this file, so a single-class rule here loses the tie. */
.ui-dlg--qr .fz-qr__share { flex: 1 1 100%; display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
.ui-dlg--qr .fz-qr__share svg { width: 15px; height: 15px; flex: 0 0 15px; }
/* Confirm sheet after a scan: the face above the question, so the answer to
   "who am I adding" is looked at before the button is tapped. */
.fz-qr__face {
  width: 64px; height: 64px;
  margin: 0 auto var(--space-3);
  border-radius: 50%;
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg);
  border: 1px solid var(--line);
}
.fz-qr__facepic { width: 100%; height: 100%; object-fit: cover; display: block; }
.fz-qr__faceinit { font-size: 22px; font-weight: 650; color: var(--ink-2); }

/* v0.9.330 — DELETE-ACCOUNT danger zone at the bottom of Settings. Calm
   red-token cues: section title in --danger, "Delete" button uses the
   existing .set-mini--danger pattern (border + text red, NOT fill). The
   wrapper adds a hairline top rule and breathing room so it visually
   separates from the dimmer-toned account-meta sections above. Visibility
   gated in renderSettings (is_trainer hides it). */
.set-group__title--danger { color: var(--danger); }
/* DIARY-METRICS-BODY-MEASUREMENTS (v0.9.495) — small-caps sub-eyebrow that
   sits INSIDE a .set-card to delineate a grouping of related rows (e.g.
   "Body measurements" within Diary metrics). Mirrors the look of
   .set-group__title but slightly smaller and padded to the card inset. */
.set-group__title-sub {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--section-label);
  font-weight: 700;
  margin: var(--space-2) 0 0;
  padding: var(--space-3) var(--space-4) 0;
}
.danger-zone {
  margin-top: var(--space-6);
  padding: 0;
  border-top: 0;
  margin-bottom: var(--space-5);
}
.danger-zone__hint {
  font-size: 13px;
  color: var(--section-label);
  line-height: 1.5;
  margin: 0;
  padding: 0 var(--space-4) var(--space-2);
}
.set-mini--danger { border-color: var(--danger); color: var(--danger); }

/* v0.9.330 — login-cancel banner for users who signed in during the 30-day
   deletion grace. role=status aria-live=polite. The cancel CTA mirrors the
   .set-mini--danger pattern (calm red outline, not red fill). */
.delbanner {
  margin: var(--space-3) var(--space-4);
  padding: 14px 16px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 3px solid var(--danger);
  border-radius: var(--radius-sm);
  display: flex; flex-direction: column; gap: var(--space-3);
}
.delbanner__msg { font-size: 13px; color: var(--ink-2); line-height: 1.5; margin: 0; }
.delbanner__msg strong { color: var(--ink); font-weight: 600; }
.delbanner__btn {
  align-self: flex-start;
  padding: 8px 16px;
  background: transparent;
  border: 1px solid var(--danger);
  color: var(--danger);
  border-radius: var(--radius-sm);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
  cursor: pointer;
  font-family: inherit;
}
.delbanner__btn:disabled { opacity: 0.5; cursor: progress; }

/* AANMELDEN-ZONDER-FRICTIE-EEN-PLUS-CODE-IS-JE-SLEUTEL — de uitnodigingsstrook.
   Zelfde vorm als hierboven, maar een uitnodiging is geen waarschuwing: de
   streep en de knop dragen de accentkleur in plaats van rood. De "Later" ernaast
   is bewust even bereikbaar en even leesbaar als het ja — dat is wat de strook
   onderscheidt van de overname die hij vervangt. */
.delbanner--invite { border-left-color: var(--accent); flex-direction: row; align-items: center; flex-wrap: wrap; }
.delbanner--invite .delbanner__msg { flex: 1 1 220px; }
.delbanner--invite .delbanner__btn {
  align-self: auto;
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.delbanner__later {
  padding: 8px 12px;
  background: transparent;
  border: 0;
  color: var(--ink-2);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
  cursor: pointer;
  font-family: inherit;
}

/* SPOED-JE-BLIJFT-NIET-STIL-HANGEN-OP-AKKOORD — de regel onder de Akkoord-knop
   op het AI-toestemmingsscherm. Danger-kleur omdat hij alleen verschijnt als de
   knop faalde; de foutcode erachter is klein en gedempt, want die is voor wie
   meekijkt op een schermafdruk, niet voor de gebruiker zelf. */
.chat-consent-card__err {
  color: var(--danger);
  margin-top: var(--space-3);
}
.chat-consent-card__code { color: var(--ink-2); font-size: 11px; opacity: 0.8; }
.chat-consent-card__resend { margin-top: var(--space-2); }

/* Error rows (nested disclosure) */
.set-err {
  margin-bottom: var(--space-2);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.set-err__sum {
  list-style: none;
  cursor: pointer;
  padding: var(--space-3);
  font-size: 13px;
  line-height: 1.35;
}
.set-err__sum::-webkit-details-marker { display: none; }
.set-err__meta { opacity: 0.6; margin-right: 6px; }
.set-err__body {
  margin: 0 var(--space-3) var(--space-3);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  white-space: pre-wrap;
  word-break: break-word;
  opacity: 0.8;
}

/* ───────── Themed dialog (replaces native confirm/prompt/alert) ───────── */
.ui-dlg-overlay { position: fixed; inset: 0; z-index: 1000; background: rgba(0,0,0,0.5);
  display: flex; align-items: center; justify-content: center; padding: var(--space-5); }
.ui-dlg { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm);
  width: 100%; max-width: 360px; padding: var(--space-5); }
.ui-dlg__title { font-size: 17px; font-weight: 600; color: var(--ink);
  margin: 0 0 var(--space-2); letter-spacing: -0.01em; }
.ui-dlg__msg { font-size: 14px; line-height: 1.5; color: var(--ink-2); margin: 0 0 var(--space-5); }
.ui-dlg__input { width: 100%; padding: 10px 12px; margin: 0 0 var(--space-4); background: var(--bg);
  border: 1px solid var(--line); border-radius: var(--radius-sm); font-size: 16px; color: var(--ink); }
.ui-dlg__input:focus { outline: none; border-color: var(--ink-2); }
.ui-dlg__input--area { resize: vertical; min-height: 48px; line-height: 1.4; font-family: inherit; }
/* The consequence line under promptDialog's optional checkbox. Indented to the label,
   not to the box, so it reads as the rest of that sentence rather than a new item. */
.ui-dlg__note { margin: var(--space-1) 0 0 26px; font-size: 12px; line-height: 1.4; color: var(--muted); }
/* GYM-TECHNIEK-TAGS — chip picker inside the standard dialog shell. The list is
   a closed vocabulary, so it scrolls rather than paginating; the blocked state
   is the ERR-needs-shoulder-or-hip rule made visible before the save instead of
   after the server rejects it. */
.ui-dlg__tags { max-height: 46vh; overflow-y: auto; margin: 0 0 var(--space-4); }
.ui-dlg__tags-head { font-size: 11px; font-weight: 600; letter-spacing: 0.4px; text-transform: uppercase;
  color: var(--ink-2); margin: var(--space-3) 0 var(--space-2); }
.ui-dlg__tags-head:first-child { margin-top: 0; }
.ui-dlg__tags-row { display: flex; flex-wrap: wrap; gap: 6px; }
.ui-dlg__tag { padding: 5px 11px; font-size: 12px; font-weight: 600; color: var(--ink-2);
  background: var(--bg); border: 1px solid var(--line); border-radius: 999px; cursor: pointer; }
.ui-dlg__tag:hover:not(:disabled) { color: var(--ink); border-color: var(--ink-2); }
.ui-dlg__tag.is-on { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.ui-dlg__tag.is-blocked { opacity: 0.4; cursor: not-allowed; }
.ui-dlg__row { display: flex; gap: var(--space-3); }
.ui-dlg__btn { flex: 1 1 0; padding: 12px; border-radius: var(--radius-sm); cursor: pointer;
  font-size: 13px; font-weight: 600; letter-spacing: 0.04em; border: 1px solid; font-family: inherit; }
.ui-dlg__btn--cancel { background: transparent; border-color: var(--line); color: var(--ink-2); }
.ui-dlg__btn--cancel:hover { color: var(--ink); border-color: var(--ink-2); }
.ui-dlg__btn--go { background: var(--ink); border-color: var(--ink); color: var(--bg); }
.ui-dlg__btn--danger { background: var(--danger); border-color: var(--danger); color: var(--on-danger); }

/* BEGINSCHERM-ZET-SASSU-OP-JE-TELEFOON — het scherm ná de ToS-poort plus de
   iOS-stappenlijst in Instellingen. ÉÉN stappenlijst (.instg__steps) voor beide
   ingangen: dezelfde copy, dezelfde tekeningen, één plek om te wijzigen.
   De tekeningen zijn EIGEN vormen; wat de duim moet raken staat in de
   accentkleur en de rest blijft rustig, zodat elke stap in één blik leesbaar is. */
.ui-dlg--install { max-width: 380px; }
.ui-dlg--install .instg__steps { margin-bottom: var(--space-4); }

.instg { gap: var(--space-4); justify-content: flex-start; }
.instg__skip {
  align-self: flex-end;
  background: none; border: 0; padding: 6px 4px; margin: 0 0 var(--space-2);
  min-height: 44px;
  font-family: inherit; font-size: 14px; color: var(--muted);
  text-decoration: underline; cursor: pointer;
}
.instg__skip:hover { color: var(--ink); }
.instg__title { font-size: 22px; line-height: 1.25; font-weight: 700; color: var(--ink); margin: 0; }
.instg__sub   { font-size: 14px; color: var(--muted); margin: 6px 0 var(--space-4); }
.instg__cta {
  display: inline-flex; align-items: center; justify-content: center;
  width: 100%; min-height: 48px; padding: 12px 18px;
  border: 0; border-radius: var(--radius-lg);
  background: var(--accent); color: var(--on-accent);
  font-family: inherit; font-size: 16px; font-weight: 600; cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-standard);
}
.instg__cta:active { transform: scale(.99); }

.instg__steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.instg__step {
  display: flex; align-items: center; gap: var(--space-3);
  padding: 10px 12px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg);
}
.instg__step-body { display: flex; align-items: flex-start; gap: var(--space-2); flex: 1 1 auto; }
.instg__num {
  flex: 0 0 22px; height: 22px; width: 22px; display: inline-flex;
  align-items: center; justify-content: center; border-radius: 50%;
  background: var(--accent); color: var(--on-accent); font-size: 12px; font-weight: 700; line-height: 1;
}
.instg__lbl { font-size: 14px; color: var(--ink); line-height: 1.45; }
.instg__art { flex: 0 0 84px; width: 84px; height: 52px; display: block; }
.instg__art .instg__ln   { fill: none; stroke: var(--ink-2); stroke-width: 1.4; opacity: .55; }
.instg__art .instg__fl   { fill: var(--ink-2); stroke: none; opacity: .45; }
.instg__art .instg__sf   { fill: var(--surface-muted); stroke: none; }
.instg__art .instg__ac   { fill: none; stroke: var(--accent); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.instg__art .instg__acf  { fill: var(--accent); stroke: none; }
.instg__art .instg__ring { fill: var(--accent-subtle); stroke: none; }
.instg__art .instg__knob { fill: var(--on-accent); stroke: none; }
@media (max-width: 359px) { .instg__art { flex-basis: 64px; width: 64px; height: 40px; } }

/* CONNECTOR-TOESTEMMING-BIJ-DE-INTAKE-TWEE-VINKJES — het onboardingscherm met
   de twee losse vinkjes. Leent de maten van .instg hierboven (zelfde soort
   scherm, zelfde plek in de rij), maar met eigen klassen: de twee schermen
   zullen uit elkaar lopen en een gedeelde klasse maakt dan van elke wijziging
   aan het ene een stille wijziging aan het andere.
   Elke rij is een <label> met het hokje ervoor — het hele vlak is dus tikbaar,
   en dat is hier belangrijker dan elders: een vinkje van 13px raken op een
   telefoon is precies het soort frictie dat een vrijwillige keuze onvrijwillig
   laat uitvallen. De knop "meer lezen" staat BUITEN dat label (zie app.js).
   Alle kleuren via tokens, dus beide thema's volgen zonder tweede regel. */
.aicons { gap: var(--space-4); justify-content: flex-start; }
.aicons__title { font-size: 22px; line-height: 1.25; font-weight: 700; color: var(--ink); margin: 0; }
.aicons__sub   { font-size: 14px; color: var(--muted); margin: 6px 0 var(--space-4); line-height: 1.45; }
.aicons__rows  { display: flex; flex-direction: column; gap: var(--space-3); }
.aicons__row {
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding: 12px 14px;
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  background: var(--surface); cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-standard);
}
.aicons__row:hover { border-color: var(--ink-2); }
.aicons__row:has(.aicons__box:checked) { border-color: var(--accent); }
.aicons__box { flex: 0 0 auto; width: 20px; height: 20px; margin: 2px 0 0; accent-color: var(--accent); }
.aicons__txt { flex: 1 1 auto; display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.aicons__lbl { font-size: 15px; font-weight: 600; color: var(--ink); line-height: 1.35; }
.aicons__help, .aicons__more { font-size: 13px; color: var(--muted); line-height: 1.5; }
.aicons__extra { display: flex; flex-direction: column; gap: 6px; padding: 0 14px; }
.aicons__more  { margin: 0; }
.aicons__morebtn {
  align-self: flex-start;
  background: none; border: 0; padding: 6px 0; margin: 0;
  min-height: 44px;
  font-family: inherit; font-size: 13px; color: var(--muted);
  text-decoration: underline; cursor: pointer;
}
.aicons__morebtn:hover { color: var(--ink); }
.aicons__err { font-size: 13px; color: var(--danger); margin: 0; line-height: 1.45; }
.aicons__cta {
  display: inline-flex; align-items: center; justify-content: center;
  width: 100%; min-height: 48px; padding: 12px 18px;
  border: 0; border-radius: var(--radius-lg);
  background: var(--accent); color: var(--on-accent);
  font-family: inherit; font-size: 16px; font-weight: 600; cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-standard);
}
.aicons__cta:active     { transform: scale(.99); }
.aicons__cta:disabled   { opacity: .6; cursor: default; }

/* CONNECTOR-TOESTEMMING-NAAST-HET-CHATAKKOORD — hetzelfde losse vinkje, nu
   naast het bestaande chat-akkoord. Eigen klassen en niet .aicons hergebruikt,
   om de reden die daar al staat: dit vinkje woont in een .set-card met een
   eigen ritme, en een gedeelde klasse maakt van elke wijziging aan het ene
   scherm een stille wijziging aan het andere. Alle kleuren via tokens, dus
   beide thema's volgen zonder tweede regel; de "meer lezen"-knop staat buiten
   het label (zie app.js), zodat lezen nooit aanvinken is. */
.chatcons__intro { margin-top: var(--space-4); font-weight: 600; color: var(--ink); }
.chatcons__row {
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding: 12px 14px; margin: 0;
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  background: var(--surface); cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-standard);
}
.chatcons__row:hover { border-color: var(--ink-2); }
.chatcons__row:has(.chatcons__box:checked) { border-color: var(--accent); }
.chatcons__box { flex: 0 0 auto; width: 20px; height: 20px; margin: 2px 0 0; accent-color: var(--accent); }
.chatcons__txt { flex: 1 1 auto; display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.chatcons__lbl { font-size: 15px; font-weight: 600; color: var(--ink); line-height: 1.35; }
.chatcons__extra { display: flex; flex-direction: column; gap: 6px; padding: 0 14px; }
.chatcons__more { font-size: 13px; color: var(--muted); line-height: 1.5; margin: 0; }
.chatcons__morebtn {
  align-self: flex-start;
  background: none; border: 0; padding: 6px 0; margin: 0;
  min-height: 44px;
  font-family: inherit; font-size: 13px; color: var(--muted);
  text-decoration: underline; cursor: pointer;
}
.chatcons__morebtn:hover { color: var(--ink); }

/* v0.9.321 — A11Y-9 S24: install-Sassu row in Settings. Calm secondary
   surface (not the prominent --accent CTA pattern used by switch-to-coach,
   so installs don't visually outweigh sign-out / change-password). */
.set-install {
  display: flex; align-items: center; gap: var(--space-3);
  width: 100%; padding: 12px 14px;
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  background: var(--surface); color: var(--ink);
  cursor: pointer; text-align: left; font-family: inherit;
  transition: border-color var(--dur-fast) var(--ease-standard), background var(--dur-fast) var(--ease-standard), transform var(--dur-fast) var(--ease-standard);
}
.set-install:hover  { border-color: var(--ink-2); }
.set-install:active { transform: scale(.99); }
.set-install__icon { flex: 0 0 28px; height: 28px; width: 28px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-md); background: var(--accent-light); color: var(--accent); }
.set-install__icon svg { width: 18px; height: 18px; }
.set-install__lbl   { flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; }
.set-install__main  { font-size: 14px; font-weight: 600; color: var(--ink); }
.set-install__sub   { font-size: 12px; color: var(--muted); }
.set-install__arrow { flex: 0 0 auto; font-size: 22px; line-height: 1; color: var(--muted); }

/* v0.9.321 — A11Y-9 S32: one-shot Nutrition introduction coachmark.
   Non-modal speech-bubble pointing at the Nutrition tab in the tabbar; the
   rest of the screen is dimmed so the user knows where to look. Dismiss
   button + CTA. NEVER an emoji per feedback_no_emojis_in_ux. The bubble's
   bottom + arrow.left are positioned at runtime relative to the Nutrition
   tab's getBoundingClientRect(); the CSS handles look + feel only. */
.nut-intro {
  position: fixed; inset: 0; z-index: 950;
  background: rgba(0, 0, 0, 0.45);
  /* Avoid blocking tap-through on iOS rubber-band scroll. */
  touch-action: none;
}
.nut-intro__bubble {
  position: fixed;
  left: 50%; transform: translateX(-50%);
  max-width: min(360px, calc(100vw - 32px));
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 14px 16px 12px;
  box-shadow: var(--elev-3);
}
.nut-intro__title {
  font-size: 15px; font-weight: 700; color: var(--ink);
  margin-bottom: 4px; letter-spacing: -0.01em;
}
.nut-intro__body {
  font-size: 13px; line-height: 1.45; color: var(--ink-2);
  margin: 0 0 12px;
}
.nut-intro__row {
  display: flex; gap: 8px; justify-content: flex-end;
}
.nut-intro__dismiss,
.nut-intro__cta {
  border: 0; border-radius: var(--radius-md); padding: 9px 14px;
  font-size: 13px; font-weight: 600; cursor: pointer; font-family: inherit;
}
.nut-intro__dismiss {
  background: transparent; color: var(--ink-2);
  border: 1px solid var(--line);
}
.nut-intro__dismiss:hover { border-color: var(--ink-2); color: var(--ink); }
.nut-intro__cta {
  background: var(--accent); color: var(--on-accent);
}
.nut-intro__cta:hover { filter: brightness(0.95); }
/* Tail under the bubble pointing at the Nutrition tab. Arrow.left is set
   inline by JS so the tip aligns with the tab center. */
.nut-intro__arrow {
  position: absolute; bottom: -8px; left: 50%;
  width: 16px; height: 16px;
  background: var(--surface);
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  transform: translateX(-50%) rotate(45deg);
}

/* ──────────────────────────────────────────────────────────
   Strength tab — embedded prototype iframe
   ────────────────────────────────────────────────────────── */
.strength-iframe-wrap {
  /* STRENGTH-SHEET-BOTTOM-PADDING-FOR-WORKOUT-DONE-BUTTON (v0.9.896) — 100dvh
     (dynamic viewport height) i.p.v. 100vh: op mobile is 100vh de LARGE
     viewport (url-bar ingeklapt), waardoor de iframe-bodem (WORKOUT DONE knop)
     achter de bottombar verdween. 100dvh volgt de actueel zichtbare viewport
     zodat de iframe exact boven de tabbar eindigt en de knop bereikbaar blijft. */
  height: calc(100dvh - var(--tabbar-h) - var(--safe-bottom) - 1px);
  margin-top: -1px;
  width: 100%;
  /* Strength tab wrap: flex-column hosts the autoreg bell row above the iframe. */
  display: flex;
  flex-direction: column;
}
/* CARDIO-DE-LOSSE-TAB-GAAT-DICHT (2026-09-10) — the one-time line at the top of
   Gym after the cardio tile disappeared. flex-shrink:0 so it never squeezes the
   iframe; it is removed from the DOM on close, and painted at most once per
   user (localStorage), so no layout budget is reserved for it. Token colours
   only — this row has to read in both themes. */
.fz-cardio-moved {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  background: var(--surface-muted);
  border-bottom: 1px solid var(--line);
  color: var(--ink-2);
  font-size: 13px;
  line-height: 1.35;
}
.fz-cardio-moved__text { flex: 1 1 auto; min-width: 0; }
.fz-cardio-moved__close {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: inherit;
  cursor: pointer;
}
/* Two crossed bars — a drawn glyph, never an emoji or a text "x" that would
   inherit a font's idea of a multiplication sign. */
.fz-cardio-moved__x { position: relative; width: 12px; height: 12px; display: block; }
.fz-cardio-moved__x::before,
.fz-cardio-moved__x::after {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  width: 100%;
  height: 1.5px;
  background: currentColor;
  border-radius: var(--radius-pill);
}
.fz-cardio-moved__x::before { transform: translateY(-50%) rotate(45deg); }
.fz-cardio-moved__x::after  { transform: translateY(-50%) rotate(-45deg); }

.strength-iframe-area {
  position: relative; /* v0.9.204 — anchor the FOUC cover over the iframe only */
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
}
.strength-iframe {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

/* STRENGTH-PERIODIZATION-AS-LINK-IN-THIS-WEEK (v0.9.717) — the stand-alone
   Periodisering paneel that used to sit between the topbar and the iframe was
   removed; its periodisation overview is now reached via an inline link inside
   the iframe's "This week" card header (.this-week__periodization-link, styled
   in strength.html). This collapses the vertical gap above This Week. */

/* v0.9.204 — FOUC cover over the strength iframe until the bridge signals
   hydrate-complete (sassu:ready) or a fallback fires (see mountStrength()).
   Background tracks the parent theme so it matches the dark UI the iframe
   settles to — no light flash, no mock-card flash. */
.strength-iframe-cover {
  position: absolute;
  inset: 0;
  z-index: 5;
  background: var(--bg);
  pointer-events: none;
  opacity: 1;
  transition: opacity var(--dur-base) var(--ease-standard);
}
:root[data-theme-mode="dark"] .strength-iframe-cover { background: var(--bg); }
.strength-iframe-cover.is-hidden { opacity: 0; }
/* v0.9.309 — A11Y-4 S20: surface a calm label inside the cover so the user
   knows the app is alive during the 200-400 ms cold-start blank moment.
   Centered, muted, fades with the cover. */
.strength-iframe-cover__lbl {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 14px;
  letter-spacing: 0.02em;
  color: var(--muted);
  text-align: center;
  padding: 0 var(--space-5);
}
:root[data-theme-mode="dark"] .strength-iframe-cover__lbl { color: var(--ink-2); }
body.tab-strength .topbar { display: none; }
/* CHAT-LAUNCH-VISIBLE-ON-ALL-SURFACES-NO-OVERLAP (v0.9.701) reserveerde
   safe-area-inset-top + 52px om de floating chat-launch pill + autoreg bell
   + periodisering chip vrij van de back-button bar te houden. Jayy 2026-06-19
   (v0.9.810+): die 52px reserve voelde als overbodige witruimte op /strength.
   STRENGTH-IFRAME-TOP-PADDING-REMOVE-CHAT-PILL-RESERVE dropte de +52px;
   pill+bell+chip mogen visueel overlappen met de fz-sp-bar — accepted
   trade-off voor minder top-witruimte. */
body.tab-strength .strength-iframe-wrap {
  box-sizing: border-box;
  padding-top: env(safe-area-inset-top);
}

/* ──────────────────────────────────────────────────────────
   Coach hub — the coach app embedded fullscreen
   (COACH-IN-APP-3-GATED-TAB-MOUNT). Mirrors the strength-iframe geometry:
   the frame fills everything between the safe-area top and the tabbar, and
   the topbar is hidden because the coach SPA carries its own header.
   ────────────────────────────────────────────────────────── */
.coach-hub-wrap {
  height: calc(100dvh - var(--tabbar-h) - var(--safe-bottom) - 1px);
  margin-top: -1px;
  width: 100%;
  display: flex;
  flex-direction: column;
  background: var(--bg);
}
.coach-hub-iframe {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  border: 0;
  display: block;
}
body.tab-coach-hub .topbar { display: none; }
body.tab-coach-hub .coach-hub-wrap {
  box-sizing: border-box;
  padding-top: env(safe-area-inset-top);
  /* COACH-IN-APP-5-CONTINUITEIT-POLISH — the lateral insets are the parent's
     job: inside an iframe env(safe-area-inset-*) resolves to 0, so a landscape
     notch or a home indicator would clip the coach UI if we didn't reserve the
     gutters out here. */
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}

/* ──────────────────────────────────────────────────────────
   GYM-COACH-PERSISTENT-IFRAMES — the pool that keeps the Gym and Coach-Hub
   sub-documents warm across tab switches. The pool itself is an unstyled
   container at the end of <body>: giving it a stacking context (z-index:-1)
   would trap its fixed children behind the app shell, so the SLOTS carry the
   geometry and the pool carries nothing.
   visibility (not display:none) hides an inactive slot — display:none suspends
   rAF/IntersectionObserver/media in several engines, which is exactly the state
   we are trying to avoid.
   ────────────────────────────────────────────────────────── */
.fz-persistent-slot {
  position: fixed;
  top: 0; left: 0; right: 0;
  bottom: calc(var(--tabbar-h) + var(--safe-bottom));
  z-index: 10;
  visibility: hidden;
  pointer-events: none;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  height: auto;      /* overrides the 100dvh the wrap classes carry when in-flow */
  margin-top: 0;
  padding-top: env(safe-area-inset-top);
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}
body.tab-strength .fz-persistent-gym,
body.tab-coach-hub .fz-persistent-coach {
  visibility: visible;
  pointer-events: auto;
}
/* The in-#app stand-ins: the real surface paints from the pool, so these only
   have to occupy nothing and add no scroll. */
.strength-shell-ph,
.coach-shell-ph { height: 0; }

/* ──────────────────────────────────────────────────────────
   CARDIO-DE-EXTRA-S-ZIJN-WEG-ALLEEN-LOGGEN-BLIJFT (2026-10-03, L102 kill 1)

   Hier stond het stijlblad van de cardio-TAB: het blijvende iframe buiten #app
   (.fz-cardio-persistent + .cardio-iframe en zijn voorganger -wrap/-cover), de
   beslispoort met zijn hoofdknop (.cardio-gate*), de laadspinner en de
   foutkaart (.cardio-loader*, .cardio-error*), de `body.tab-cardio .topbar`-
   verberger, en de GPS-mini-speler boven de tabbalk (.fz-gps-mini* +
   @keyframes fz-gps-pulse).

   Alle elf wortels hingen aan één HTML-element dat niemand meer maakt: er is
   geen cardio-tab, geen iframe, geen tracker en geen `body.tab-cardio`-klasse
   meer. Stijl zonder element is geen risico maar wel een valstrik — de volgende
   bouwer die `.cardio-gate` ziet, gaat zoeken naar het scherm erbij.

   WAT HIER BEWUST BLIJFT STAAN: het veel grotere blok rond regel 12.000
   (.fz-cardio-routes*, #fz-cardio-app, .fz-cardio-picker, de .leaflet-*
   -overrides). Dat is óók dood — niets buiten dit stijlblad noemt die klassen
   nog — maar het is ~1700 regels met eigen keyframes en tokens, en het weghalen
   verdient een eigen fase met eigen visuele bewijzen. Zie
   CARDIO_GEPARKEERD.md.
   ────────────────────────────────────────────────────────── */

/* De reduced-motion-tak van het grote (nog bestaande) routes-blok. De
   .cardio-loader__spinner-regel die hier naast stond is mee met de poort. */
@media (prefers-reduced-motion: reduce) {
  .fz-cardio-routes__input-wrap.is-loading::after {
    animation: none;
    border: 2px solid var(--accent);
  }
}

/* v0.9.30 — Strength player overlays host chrome.
   When the iframe's `.hero-timer.active` fires, the bridge toggles
   `body.feliz-player-on` on this document. Iframe wrap expands to full
   viewport and the bottom tab bar is hidden so the player's top + bottom
   controls are never clipped.
   v0.9.35: 100vh → 100dvh. On iOS Safari `100vh` reports the LARGEST
   possible viewport (URL bar collapsed), so the bottom of the iframe sits
   below the current visible bottom when the URL bar is showing — that's
   why the player kept clipping at the bottom across v0.9.30 → 34. `100dvh`
   tracks the CURRENT visible viewport and resizes when Safari's URL bar
   shows/hides. Same on iPhone home-screen PWAs (no URL bar) where dvh ==
   vh. Also lock body scroll so the host page can't drift under the player. */
body.feliz-player-on .strength-iframe-wrap {
  height: 100dvh;
  height: 100vh; /* fallback for browsers without dvh */
  height: 100dvh;
}
body.feliz-player-on .tabbar {
  display: none;
}
body.feliz-player-on {
  overflow: hidden;
  position: fixed;
  inset: 0;
  width: 100%;
}

/* COACH-TIMERS-ECHT-SCHERMVULLEND — the same problem as body.feliz-player-on,
   one nesting level deeper: a rest / Tabata / PAILs&RAILs timer inside the coach
   panel is position:fixed against the INNERMOST frame, so it can never cover
   more than that box. The coach turns the device towards the client, so the
   client's dossier, notes and North Star must not be readable behind it — and
   this tabbar sits above the coach frame regardless of z-index inside it.
   The gutters go too: the slot normally reserves the device insets out here
   because env() reads 0 inside an iframe, but an edge-to-edge timer needs them
   INSIDE, so they are handed down over postMessage and this padding steps aside.
   100dvh, never 100vh: on iOS vh reports the LARGEST viewport, which is what put
   the bottom of the player under the URL bar in v0.9.30–34. */
body.coach-timer-fs .tabbar { display: none; }
body.coach-timer-fs .fz-persistent-coach {
  bottom: 0;
  padding-top: 0;
  padding-left: 0;
  padding-right: 0;
}
body.coach-timer-fs .coach-hub-wrap {
  height: 100dvh;
  padding-top: 0;
  padding-left: 0;
  padding-right: 0;
}
body.coach-timer-fs { overflow: hidden; }

/* DOSSIER-KRIJGT-HET-HELE-SCHERM — a client file fills the height: the bar
   slides away (same translateY idiom as body.kb-open, so it is a movement and
   not a blink) and the coach slot reclaims the strip it was reserving, the
   bottom safe-area inset included, so no dead band and no overlap is left. The
   top/left/right insets stay put: only the bottom bar leaves. */
body.coach-dossier-fs .tabbar {
  transform: translateY(100%);
  transition: transform var(--dur-base) var(--ease-standard);
  pointer-events: none;
}
body.coach-dossier-fs .fz-persistent-coach { bottom: 0; }
body.coach-dossier-fs .coach-hub-wrap { height: 100dvh; }

/* ──────────────────────────────────────────────────────────
   Strength tab (LEGACY — deprecated, kept for ref but unused)
   ────────────────────────────────────────────────────────── */

.strength {
  padding: var(--space-4) var(--space-4) var(--space-6);
}

/* ───── Session header ───── */
.sess-head {
  padding: var(--space-3) var(--space-1) var(--space-5);
  border-bottom: 1px solid var(--line);
  margin-bottom: var(--space-5);
}
.sess-name-input {
  display: block;
  width: 100%;
  background: transparent;
  border: 0;
  border-bottom: 1px dashed transparent;
  font-size: 24px;
  font-weight: 300;
  letter-spacing: -0.005em;
  color: var(--ink);
  margin-bottom: var(--space-3);
  padding: 2px 0;
  outline: none;
}
.sess-name-input:focus,
.sess-name-input:hover {
  border-bottom-color: var(--line);
}
.sess-stats {
  display: flex;
  gap: var(--space-5);
}
.sess-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.sess-stat__lbl {
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
.sess-stat__val {
  font-size: 14px;
  color: var(--ink);
  font-feature-settings: 'tnum' 1;
}

/* ───── Exercise list ───── */
.exlist {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.exw {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.exw--ss {
  border-left: 3px solid var(--accent);
}

.ex-h {
  display: grid;
  grid-template-columns: 16px 1fr 118px 14px;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--line-2);
  cursor: pointer;
  user-select: none;
}
.ex-drag {
  letter-spacing: -2px;
  color: var(--muted);
  font-size: 13px;
  text-align: center;
  cursor: grab;
}
.ex-nm {
  font-size: 15px;
  color: var(--ink);
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ex-meta {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  width: 118px;
}
.ex-tb {
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  border: 1px solid var(--line);
  padding: 2px 6px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
}
.ex-sum {
  font-size: 12px;
  color: var(--muted);
}
.ex-chev {
  color: var(--muted);
  font-size: 18px;
  text-align: center;
}

/* ───── Sets ───── */
.ex-sets {
  padding: var(--space-2) 0;
}
.ex-si {
  display: flex;
  flex-direction: column;
}
.ex-empty {
  padding: var(--space-3) var(--space-4);
  font-size: 12px;
  color: var(--muted);
  text-align: center;
}

.sr {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--line-2);
  font-feature-settings: 'tnum' 1;
}
.sr:last-child { border-bottom: 0; }
.sr--done {
  background: rgba(58, 122, 77, 0.04);
}
.sr--done .sr-num { color: var(--ink); }

.sr-num-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  font-size: 11px;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 50%;
  font-weight: 500;
  flex-shrink: 0;
}
.sr-bw {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  font-size: 10px;
  letter-spacing: 0.05em;
  color: var(--muted);
  border: 1px solid var(--line);
  padding: 2px 4px;
  border-radius: var(--radius-sm);
}
.sr-bw.on {
  background: var(--ink);
  color: var(--bg);
  border-color: var(--ink);
}

.sr-w, .sr-r, .sr-d {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  min-width: 0;
}
.sr-w { width: 78px; justify-content: flex-end; }
.sr-r { width: 64px; justify-content: flex-end; }
.sr-d { width: 64px; justify-content: flex-end; }

.sr-num {
  font-size: 15px;
  color: var(--ink);
  font-weight: 500;
}
.sr-num--input {
  background: transparent;
  border: 0;
  border-bottom: 1px dashed transparent;
  outline: none;
  width: 56px;
  text-align: right;
  padding: 1px 2px;
  font-size: 16px;
  color: var(--ink);
  font-weight: 500;
  font-feature-settings: 'tnum' 1;
  border-radius: 0;
}
.sr-num--input:focus,
.sr-num--input:hover {
  border-bottom-color: var(--line);
}
.sr-num--input::placeholder { color: var(--muted); }
.sr-rpe-input { width: 36px; }
.sr-rpe-wrap {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  margin-left: var(--space-2);
}
.sr-rpe-lbl {
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--muted);
  text-transform: uppercase;
}
.sr-w.is-computed .sr-num {
  color: var(--ink-2);
  font-style: italic;
}

.sr-unit {
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--muted);
  text-transform: lowercase;
}

.sr-x {
  font-size: 12px;
  color: var(--muted);
}

.sr-combo {
  display: inline-flex;
  width: 18px;
  height: 18px;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  font-weight: 600;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 50%;
}

.sr-rpe {
  font-size: 11px;
  color: var(--muted);
  letter-spacing: 0.06em;
  margin-left: var(--space-2);
}

.sr-meta {
  font-size: 11px;
  color: var(--muted);
  margin-left: var(--space-2);
}

.sr-ck {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--muted);
  font-size: 14px;
  flex-shrink: 0;
  background: transparent;
  cursor: pointer;
}
.sr-ck.on {
  background: var(--ok);
  color: var(--bg);
  border-color: var(--ok);
}

/* ───── PAILs / RAILs block ───── */
.pr-block {
  padding: var(--space-3) var(--space-4) var(--space-4);
}
.pr-cfg {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-3);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--line-2);
  margin-bottom: var(--space-3);
}
.pr-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-align: center;
}
.pr-stat__lbl {
  font-size: 9px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
.pr-stat__val {
  font-size: 14px;
  color: var(--ink);
  font-feature-settings: 'tnum' 1;
}
.pr-rounds {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.pr-round {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg);
}
.pr-round.done {
  border-color: var(--ok);
  background: rgba(58, 122, 77, 0.05);
}
.pr-round__n {
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 500;
}
.pr-round.done .pr-round__n { color: var(--ok); }
.pr-round__lbl {
  font-size: 12px;
  color: var(--muted);
}

/* ───── Add set / Add exercise ───── */
.sr-add {
  display: block;
  width: 100%;
  background: transparent;
  border: 0;
  border-top: 1px solid var(--line-2);
  padding: var(--space-3) var(--space-4);
  text-align: center;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  font-weight: 500;
}
.sr-add:hover { color: var(--ink); background: var(--line-2); }

.ex-x {
  background: transparent;
  border: 0;
  width: 14px;
  height: 14px;
  font-size: 18px;
  line-height: 1;
  color: var(--muted);
  padding: 0;
  cursor: pointer;
}
.ex-x:hover { color: var(--danger); }

.sess-add-ex {
  margin-top: var(--space-5);
}

/* ───── Session actions ───── */
.sess-actions {
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  align-items: center;
}
.btn-link {
  background: transparent;
  border: 0;
  padding: var(--space-2) var(--space-3);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
}
.btn-link:hover { color: var(--ink); }
.btn-link--danger:hover { color: var(--danger); }

.strength-empty {
  text-align: center;
  padding: var(--space-7) var(--space-5);
}
.strength-empty .btn {
  margin-top: var(--space-5);
  max-width: 240px;
  margin-left: auto;
  margin-right: auto;
}

/* ──────────────────────────────────────────────────────────
   Calendar tab
   ────────────────────────────────────────────────────────── */
.cal-mount {
  padding: var(--space-3) var(--space-3);
}

/* CALENDAR-DAY-NIGHT-BACKGROUND-WITH-GLASS-PANELS — the Diary/Calendar tab is
   a full-bleed photo with the content as liquid-glass panels, same visual
   language as the Nutrition plate. Two variants: the day photo by default, the
   night photo once calIsNight() (calendar/index.js) puts .cal-night on <body>.
   The photo follows the CLOCK; the glass tint follows the THEME (the
   --glass-* tokens flip via html[data-theme-mode], see the Nutrition glass
   block) — all four combinations are readable.

   CALENDAR-BG-ADOPT-NUTRITION-PLATE-IDIOM-NO-FILL — the photo follows the
   `.nut-plate` idiom: a 3:2 box on the ordinary app background, its
   edges feathered into var(--bg) by a radial gradient painted OVER the image.
   No fill colour anywhere — a fill turned the whole page brown.

   CALENDAR-BG-PHOTO-FULL-VIEWPORT-WIDTH — the box is min(100vw, 720px), not the
   inherited 390px mock width: on any phone 100vw wins and the photo reaches both
   screen edges, while the 720px cap keeps it inside the .app-main column on
   desktop instead of spanning the whole window. No border-radius: at full bleed
   rounded corners read as clipped corners; the feather gradient alone carries
   the transition into the background.

   CALENDAR-BG-TRULY-FIXED-FULLBLEED-LAYER — iOS Safari ignores
   `background-attachment: fixed` and scrolls the image with the document, so
   the photo is a real `position: fixed` layer instead. It hangs off <body>,
   never off .app-main: the swipe-tabs controller (NAV-SWIPE-TABS-WITH-PEEK)
   puts an inline transform on .app-main, and a transformed ancestor would make
   `fixed` resolve against that ancestor — the photo would drag along with the
   horizontal swipe. z-index 0 + `.app-shell { position: relative; z-index: 1 }`
   (the .nut-plate-wrap idiom) rather than z-index -1: body is not a stacking
   context, so a negative-z child escapes to the root context and would paint
   UNDER the html background, which is painted (html, body { background }) and
   would hide the photo entirely.

   Every glass rule below is scoped under body.tab-calendar so nothing leaks to
   Nutrition / Gym / Settings. */
body.tab-calendar {
  --cal-bg-image: url('/assets/img/calendar-bg-day.webp');
  /* transparent, not var(--bg): the html background still paints the canvas, so
     the app keeps its base colour, but body's own box no longer covers ::before. */
  background: transparent;
}

/* DIARY-GLASS-LOWER-TINT-LET-THE-PHOTO-THROUGH — the global --glass-tint is
   tuned for the Nutrition plate (declared "done", must not move), where the
   photo is decoration behind dense numeric content. On the Diary tab the photo
   IS the surface, and at 0.80 light / 0.62 dark it was erased. These overrides
   are scoped to this tab only; the `html[data-theme-mode="light"] body...`
   selector (0,2,2) is what beats the global `html[data-theme-mode="light"]`
   (0,1,1), and the dark rule works because it sits after :root. The paired
   blur bumps below (16→22 / 24→30) are what keeps the text readable: less
   opacity has to buy more blur, or the photo's detail comes through as noise
   under the glyphs.

   0.42/0.34 was the design target. tests/visual/diary-glass-plate.spec.js
   measured it at 2.86:1 on the busiest band of the night photo, and the walk up
   in 0.04 steps landed here: 0.62/0.54 light and 0.60/0.52 dark is the LOWEST
   pair where all four secondary runs clear 4.5:1 in both themes over both
   photos — 0.58/0.56 still failed at 4.19:1. Do not lower these by eye; the
   harness is the referee. The light theme is where the win is: 0.80 → 0.62.
   ONE exception exists: DIARY-RING-PLATE-50PCT halves the tint on the ring
   plate alone by explicit Jayy instruction (2026-08-07), and that run carries
   its own lower threshold in diary-glass-plate.spec.js — every other run on
   this tab is still refereed at 4.5:1.

   DIARY-CHECKIN-CTA-FULL-WIDTH-SOLID re-walked the DARK pair 0.60/0.52 →
   0.66/0.58. Nothing about the glass itself got worse: the photo is a FIXED
   layer, so CONTENT HEIGHT decides which pixels sit behind a run. The CTA grew
   ~30px and slid .analytics-ring__sub into a brighter band of BOTH photos
   (3.65:1). The old pair had zero headroom, so any height change on this tab
   re-opens this walk. Light was unaffected and stays 0.62/0.54. */
body.tab-calendar {
  --glass-tint:   rgba(0, 0, 0, 0.66);
  --glass-tint-2: rgba(0, 0, 0, 0.58);
}
html[data-theme-mode="light"] body.tab-calendar {
  --glass-tint:   rgba(255, 255, 255, 0.62);
  --glass-tint-2: rgba(255, 255, 255, 0.54);
}

body.tab-calendar::before {
  content: '';
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  /* both photos are 1536x1024 = 1.5, so a box of the same ratio crops nothing */
  width: min(100vw, 720px);
  height: calc(min(100vw, 720px) / 1.5);
  z-index: 0;
  /* the gradient is listed FIRST so it paints above the photo and feathers its
     edges into the real app background, exactly like .nut-plate::after */
  background-image:
    radial-gradient(ellipse at center, transparent 38%, var(--bg) 78%),
    var(--cal-bg-image);
  background-position: center center;
  background-size: 100% 100%, cover;
  background-repeat: no-repeat;
  pointer-events: none;
}
body.tab-calendar .app-shell {
  position: relative;
  z-index: 1;
}
/* Full bleed to the very TOP: the topbar sits over the photo instead of over a
   flat band. Secondary glass recipe (16px) so the title stays readable, and the
   hard 1px rule goes — the blur edge is the separation. */
body.tab-calendar .topbar {
  background: var(--glass-tint-2);
  -webkit-backdrop-filter: blur(22px) saturate(155%);
  backdrop-filter: blur(22px) saturate(155%);
  border-bottom-color: transparent;
}
body.tab-calendar.cal-night {
  --cal-bg-image: url('/assets/img/calendar-bg-night.webp');
}

/* Primary panels: the week strip and every day-detail / mood / analytics card
   get the full 24px blur. */
body.tab-calendar .cal-grid--week,
body.tab-calendar .cal-card,
body.tab-calendar .cal-wk-total {
  background: var(--glass-tint);
  -webkit-backdrop-filter: blur(30px) saturate(165%);
  backdrop-filter: blur(30px) saturate(165%);
  border: 1px solid var(--glass-edge);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.14) inset;
}

/* DIARY-ANALYTICS-AND-LABEL-CHIPS-ADOPT-GLASS-IDIOM — the inline analytics
   goal cards (rendered by analytics/index.js into the Diary tab, class
   `.analytics-goal-card`) were mounted on --surface-card BEFORE the
   Diary-tab glass extension, so the calendar background photo stopped at
   their top edge and they read as opaque slabs on top of the glassed
   .cal-card panels. Bringing them into the same 24px-blur token set as the
   .nut-meal / .cal-card idiom above makes the photo continuous behind the
   entire Diary surface. Scoped under body.tab-calendar so the standalone
   /analytics route (not a tab, doesn't set tab-calendar) stays unchanged. */
/* DIARY-RING-ON-GLASS — the compliance ring was the ONLY element on the tab
   with no panel under it: donut, percentage and caption sat bare on the
   background photo. It joins the same primary 24px-blur tier (it is the tab's
   head stat) at full CONTENT width, so it shares the left/right margin every
   other panel uses and the photo gutters stay intentional. */
/* DAGBOEK-DE-WEEK-IN-EEN-BLIK — de weekgrafiek en de twee cijferkaarten nemen
   de opening van de ring over en dus ook zijn plaat. Bare op de foto zou exact
   de fout van DIARY-RING-ON-GLASS hierboven terugzetten: een legenda over een
   koffiekopje is onleesbaar, en de foto hoort ACHTER de panelen door te lopen. */
/* DAGBOEK-DE-DAGVRAAG-RIJ-JA-OF-NEE — de dagvraag-rij hoort op dezelfde plaat:
   zeven vormen en een zin bare op de achtergrondfoto is precies de fout die
   DIARY-RING-ON-GLASS hierboven al een keer heeft rechtgezet. */
/* DAGBOEK-DE-TRENDS-KRIJGEN-HUN-GLAZEN-PANEEL — de Trends-sectie was de laatste
   kale strook op deze tab: geen plaat, dus zijn kop en zijn lege-staat-zin lagen
   op de foto en zakten naar 1,14-2,58:1. Hij stapt in DEZE lijst en niet in een
   eigen recept — één glas per tab, anders wandelt de volgende tintmeting maar de
   helft van de panelen mee. */
/* DAGBOEK-JE-CRESCO90-WEEK-STAAT-IN-JE-DAGBOEK — de Cresco90-kaart hangt direct
   onder de weekgrafiek en moet dus letterlijk dezelfde plaat zijn. Zijn eigen
   recept (--surface + --line) zou op deze tab een DEKKENDE slab tussen twee
   glazen panelen zijn: de foto stopt dan bij zijn bovenrand en de kaart leest als
   een reclameblok in plaats van als het volgende Dagboek-paneel. Dat is precies
   wat DIARY-ANALYTICS-AND-LABEL-CHIPS hierboven al een keer heeft rechtgezet. */
body.tab-calendar .cr9card,
body.tab-calendar .diary-trends,
body.tab-calendar .fz-weekchart,
body.tab-calendar .fz-dayq,
body.tab-calendar .fz-weekavg__card,
body.tab-calendar .fz-weekavg--empty,
body.tab-calendar .analytics-goal-card,
body.tab-calendar .analytics-ring--toggle {
  background: var(--glass-tint);
  -webkit-backdrop-filter: blur(30px) saturate(165%);
  backdrop-filter: blur(30px) saturate(165%);
  border: 1px solid var(--glass-edge);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.14) inset;
}

/* DIARY-RING-PLATE-50PCT — Jayy 2026-08-07 explicit override: exactly half
   the walked tint, ONLY on the ring plate. Owner accepted the readability
   tradeoff; do not 're-walk' this back up without a new Jayy instruction.
   Blur/border/shadow stay as-is — he asked for transparency, not compensation. */
body.tab-calendar .analytics-ring--toggle { background: rgba(0, 0, 0, 0.33); }
html[data-theme-mode="light"] body.tab-calendar .analytics-ring--toggle { background: rgba(255, 255, 255, 0.31); }

/* The ring track defaults to --surface-2 — an opaque hole on glass. It rides
   the existing --glass-hair token, which already flips with Dag/Nacht. */
body.tab-calendar .analytics-ring__track { stroke: var(--glass-hair); }
/* __pct is an SVG <text>, so it takes `fill`, not `color`. */
body.tab-calendar .analytics-ring__pct { fill: var(--glass-ink); }
body.tab-calendar .analytics-ring__sub,
body.tab-calendar .analytics-ring__chevron { color: var(--glass-ink-2); }

/* The direction chip inside a goal card and the "empty" placeholder in the
   labels strip both used --surface-muted for grouping against a flat
   background — over glass that reads as an opaque hole. Swap to a glass
   hairline, transparent fill. */
body.tab-calendar .analytics-goal-card__dir {
  background: transparent;
  border: 1px solid var(--glass-edge);
  color: var(--glass-ink-2);
}
/* Text-on-glass — same rationale as the .cal-card__meta block below:
   secondary text on --ink-2 cannot reach 4.5:1 over a blurred mid-luminance
   photo, so it lifts onto --glass-ink-2. Primary labels lift to --glass-ink
   for parity with the rest of the glass surfaces. */
body.tab-calendar .analytics-goal-card__label { color: var(--glass-ink); }

body.tab-calendar .analytics-goal-card__sub { color: var(--glass-ink-2); }

@media (prefers-reduced-transparency: reduce) {
  body.tab-calendar .analytics-goal-card,
  body.tab-calendar .analytics-ring--toggle {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    background: var(--surface);
  }
}

/* The CHECK-IN band's surface-muted tint existed to group its cards against a
   flat --bg. Over a photo the cards already separate themselves, and stacking
   glass on glass would blur an already-tinted layer and crush the contrast, so
   the band goes fully transparent and only its children carry glass. */
body.tab-calendar .cal-band--checkin {
  background: transparent;
}

/* Readability: --muted is mid-luminance and cannot reach 4.5:1 against a
   blurred mid-luminance photo at any tint — the same finding as the Nutrition
   glass block. Secondary text inside the glassed panels lifts onto
   --glass-ink-2, which flips with the theme. Never hardcoded. */
body.tab-calendar .cal-card__meta,
body.tab-calendar .cal-ex__sets,
body.tab-calendar .cal-kcal span,
body.tab-calendar .cal-note__k::after,
body.tab-calendar .cal-wk-total__sep {
  color: var(--glass-ink-2);
}

/* DIARY-TAB-CHECKIN-AND-ANALYTICS-GROUPING (v0.9.488) — two-band IA on
   the Diary tab. CHECK-IN (Day Cycle widget + mood entry + sleep/weight
   quick-log) and ANALYTICS (week stats + week goals + selected-day
   insights + mood analytics link) each get an eyebrow + tight in-band
   spacing; bands separate via a felt-distance gap. CHECK-IN carries a
   subtle surface-muted tint behind its cards so the cluster reads as one
   group; ANALYTICS keeps the default surface. Tokens-only — both dag and
   nacht inherit. */
.cal-band {
  margin: 0 calc(-1 * var(--space-3));
  padding: var(--space-3) var(--space-3) var(--space-4);
}
.cal-band + .cal-band {
  margin-top: var(--space-3);
}
.cal-band--checkin {
  background: var(--surface-muted);
  border-radius: var(--radius-lg);
}
.cal-band__body {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.cal-band__body > * {
  margin: 0;
}

/* "How was today?" mood entry card — chrome reuses .cal-card; the
   contained .diary-composer keeps its existing layout but no longer
   carries the chart-icon footer (the analytics path moved to the
   ANALYTICS-band link). */
.cal-mood-card .cal-card__hd {
  margin-bottom: 6px;
}

.cal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-2) var(--space-3) var(--space-3);
}
.cal-title {
  font-size: 18px;
  font-weight: 400;
  letter-spacing: 0.02em;
}
.cal-year {
  color: var(--muted);
  font-weight: 300;
  margin-left: 6px;
}
.cal-nav {
  background: transparent;
  border: 1px solid var(--line);
  width: 32px; height: 32px;
  border-radius: 50%;
  font-size: 16px;
  color: var(--ink-2);
  cursor: pointer;
}
.cal-nav:hover { color: var(--ink); }

/* DAGBOEK-OPRUIMING-NA-DE-WEEKGRAFIEK — .cal-layers / .cal-layer* (the
   Strength/Cardio/Nutrition/Agenda filter pills) and the Month/Week mode
   toggle + its dropdown are gone: the Diary opens on one week chart and has
   no mode to switch to, so nothing renders those elements anymore. */

.cal-grid {
  position: relative;
}
/* v0.9.328 CAL-WEEKSTRIP-UNIFY — Week strip reads as ONE chip (Jayy 2026-05-30:
   "this calendar should look a little bit more unified as one thing"). The
   tinted container wraps the DOW header + dates row so the eye groups them
   as one block; the inner hairline divider and the inner top/bottom padding
   are dropped so the container provides the rhythm. */
.cal-grid--week {
  background: var(--surface-muted);
  border-radius: var(--radius-lg);
  padding: 8px;
}
.cal-week {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  padding: 4px 0;
  border-bottom: 1px solid var(--line);
  text-align: center;
}
.cal-grid--week .cal-week {
  border-bottom: none;
  padding: 0 0 4px;
}
.cal-grid--week .cal-days {
  padding: 0;
}
.cal-week span {
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.cal-days {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
  padding: 6px 0;
}
.cal-cell {
  position: relative;
  aspect-ratio: 1 / 1;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  /* Bottom padding always reserves the day-bar zone, even on empty days, so the
     day number sits at an identical height in every cell — no jumpy grid. */
  padding: 4px 4px 11px;
  font-family: inherit;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  color: var(--ink-2);
}
.cal-cell:hover { border-color: var(--line); }
.cal-cell--out { visibility: hidden; }
.cal-cell--today {
  background: var(--ink);
  color: var(--bg);
}
.cal-cell--today:hover { border-color: var(--ink-2); }
.cal-cell__n {
  font-size: 13px;
  font-weight: 500;
  font-feature-settings: 'tnum' 1;
}
.cal-cell__count {
  position: absolute;
  top: 4px;
  right: 4px;
  font-size: 9px;
  color: var(--muted);
}
.cal-cell--today .cal-cell__count { color: rgba(255,255,255,0.7); }
/* DIARY-DAY-BARS — replaces the loose per-day dots. COLOUR SAYS WHAT, LENGTH
   SAYS HOW FAR: row position + hue encode the domain (top = strength, always
   --training; bottom = nutrition, always --nutrition) and status lives in the
   FILL of the strength bar, never in a third hue — so colour is never the sole
   carrier of meaning and nobody has to memorise a legend. An empty day gets no
   bar and no rail: absence must read as absence. */
.cal-bars {
  position: absolute;
  left: 50%;
  bottom: 5px;
  transform: translateX(-50%);
  width: 26px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  pointer-events: none;
}
.cal-bar { height: 3px; border-radius: 99px; }
.cal-bar--str { background: var(--training); }
.cal-bar--planned { width: 33%; opacity: .45; }
.cal-bar--wip     { width: 66%; }
.cal-bar--done    { width: 100%; }
.cal-bar--nut { width: 100%; background: var(--nutrition); }

/* v0.9.x — Calendar Week View (week-strip + agenda). */
.cal-wk-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 10px 12px;
  margin: var(--space-2) 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.cal-wk-total__lbl { font-size: 12px; letter-spacing: 0.04em; color: var(--ink-2); }
.cal-wk-total__val { font-size: 13px; font-weight: 600; color: var(--ink); font-feature-settings: 'tnum' 1; }
/* v0.9.255 — colour each metric by its domain (Jayy 2026-05-28). The "·"
   separators stay neutral so only the values carry colour. */
/* COLOR-3 — this row was the worst offender against the one-dominant-hue rule:
   four fully saturated colours inside a single line of 13px text. Two changes,
   both about hue COUNT rather than the domain coding Jayy asked for:
   protein is a sub-metric of nutrition, not a domain of its own, so it now
   reads as a quieter tone of the same amber instead of claiming a fourth hue
   (and a green that would have collided with training's new one); mood stops
   borrowing --accent, which made the signature colour look like just another
   metric. Browsers without color-mix drop that declaration and land on the
   --ink-2 line above it — muted, which is the intent anyway. */
.cal-wk-total__m--vol  { color: var(--training); }
.cal-wk-total__m--kcal { color: var(--nutrition); }
.cal-wk-total__m--pro  { color: var(--ink-2); }
.cal-wk-total__m--pro  { color: color-mix(in oklch, var(--nutrition) 60%, var(--ink-2)); }
.cal-wk-total__m--mood { color: var(--mood); }
.cal-wk-total__sep     { color: var(--muted); font-weight: 400; }

/* Week strip = day selector. Selected day fills; today (when not selected)
   shows an accent ring. Scoped to .cal-grid--week so the month grid's
   filled-today behaviour is untouched. */
.cal-grid--week .cal-cell--sel { background: var(--ink); color: var(--bg); }
.cal-grid--week .cal-cell--sel .cal-cell__n { color: var(--bg); }
.cal-grid--week .cal-cell--today:not(.cal-cell--sel) {
  background: transparent;
  box-shadow: var(--ring-accent-inset);
}
.cal-grid--week .cal-cell--today:not(.cal-cell--sel) .cal-cell__n { color: var(--accent); }

/* Selected / today fill the cell with --ink, which swallows both bar hues.
   Lift them to the light-on-dark variants and raise the planned bar's opacity
   so a planned day still reads inside the dark chip. */
.cal-cell--sel .cal-bar--str,
.cal-cell--today .cal-bar--str { background: #86D489; } /* allow-hex: on-dark-chip lift of --training; the chip is always --ink in both presets, so this pair is contrast-locked, not theme-tinted. COLOR-3 re-derived it in the new green family — left as the old blue it would have been the one place on the calendar still showing the retired training hue */
.cal-cell--sel .cal-bar--nut,
.cal-cell--today .cal-bar--nut { background: #F9B96C; } /* allow-hex: on-dark-chip lift of --nutrition — same rationale as the --training pair above */
.cal-cell--sel .cal-bar--planned,
.cal-cell--today .cal-bar--planned { opacity: .6; }

/* Selected-day summary (inline, no pop-up). */
.cal-sel { padding: var(--space-2) 0 var(--space-6); }
.cal-sel__day { font-size: 15px; font-weight: 600; color: var(--ink); padding: 6px 2px var(--space-2); }
.cal-sel__day .tdy { color: var(--accent); font-weight: 700; margin-left: 6px; font-size: 12px; }
.cal-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  margin-bottom: 10px;
}
.cal-card__hd { display: flex; align-items: baseline; gap: 8px; margin-bottom: 8px; }
.cal-card__icon { color: var(--ink-2); display: inline-flex; flex: 0 0 auto; }
.cal-card__icon svg { width: 16px; height: 16px; }
.cal-card__icon.leaf { color: var(--ok); }
.cal-card__name { font-size: 15px; font-weight: 600; color: var(--ink); }
.cal-card__meta { font-size: 12px; color: var(--muted); margin-left: auto; font-feature-settings: 'tnum' 1; }
.cal-ex { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-top: 1px solid var(--line-2); }
.cal-ex__name { font-size: 13px; color: var(--ink-2); }
.cal-ex__type {
  font-size: 9px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted);
  border: 1px solid var(--line); border-radius: var(--radius-pill); padding: 2px 7px; white-space: nowrap;
}
.cal-ex__sets { font-size: 12px; color: var(--muted); margin-left: auto; font-feature-settings: 'tnum' 1; white-space: nowrap; }
.cal-kcal { font-size: 22px; font-weight: 600; color: var(--ink); font-feature-settings: 'tnum' 1; }
.cal-kcal span { font-size: 12px; font-weight: 400; color: var(--muted); margin-left: 4px; }
.cal-macros { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 16px; margin-top: 8px; border-top: 1px solid var(--line-2); padding-top: 8px; }
.cal-macro { display: flex; justify-content: space-between; font-size: 13px; }
.cal-macro__k { color: var(--ink-2); }
.cal-macro__v { color: var(--ink); font-weight: 500; font-feature-settings: 'tnum' 1; }
.cal-open { background: transparent; border: 0; font-family: inherit; font-size: 12px; color: var(--accent); padding: 8px 2px 0; cursor: pointer; }
.cal-ag-rest { font-size: 12px; color: var(--muted); padding: 8px 2px; }

/* VOEDING-IN-DE-KALENDER-ZIE-JE-WAT-JE-KOOKTE — de Gekookt-kaart. Zelfde frame
   als elke andere dagkaart; alleen de pan in de kop draagt de voedingskleur, zodat
   de kaart in één blik bij Voeding hoort zonder een tweede accentkleur te worden.
   Elke regel is een volle-breedte rij met dezelfde anatomie als .cal-ex: naam,
   cijfers, en rechts de staat. Rijen die openen zijn knoppen, dus alle knop-eigen
   uiterlijk (achtergrond, rand, font) wordt expliciet teruggezet. */
.cal-card--cooked .cal-card__icon { color: var(--nutrition); }
.cal-cook-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 2px 10px;
  width: 100%;
  text-align: left;
  background: transparent;
  border: 0;
  border-top: 1px solid var(--line-2);
  font-family: inherit;
  padding: 7px 0;
}
.cal-cook-row--open { cursor: pointer; }
.cal-cook-row--open:hover .cal-cook-row__name { color: var(--accent); }
.cal-cook-row__name { font-size: 13px; color: var(--ink); font-weight: 500; }
.cal-cook-row__meta {
  grid-column: 1;
  font-size: 12px; color: var(--muted);
  font-feature-settings: 'tnum' 1;
}
/* De staat staat rechts van de naam op de EERSTE rij: "op" of "nog in de pan" is
   het antwoord op de vraag waarmee je hier komt, niet een voetnoot onder de
   cijfers. Grid-row 1 expliciet, anders zakt hij onder de macro's. */
.cal-cook-row__state {
  grid-column: 2; grid-row: 1;
  align-self: center;
  font-size: 11px; color: var(--muted);
  white-space: nowrap;
}
.cal-cook-row__state--open { color: var(--nutrition); }

/* v0.9.300 — Week View per-day Strength card upgrade (Jayy 2026-05-29).
   PR highlights = a single inline row of comma-separated names under a small
   "PR" label pill; notes = key/value lines (Session: …, Exercise name: …)
   stacked under the PR block. Both blocks reuse the existing .cal-ex /
   .cal-macro typography rather than inventing new sizes — per
   feedback_reuse_sheet_ui_not_minimal_widgets. Each gets its own top border
   so the card reads as stacked sections (header → PR → notes → CTA) instead
   of free-floating text. Hidden when empty (rendered conditionally). */
.cal-prs {
  display: flex; align-items: baseline; gap: 8px;
  padding: 8px 0 6px; border-top: 1px solid var(--line-2);
}
.cal-prs__lbl {
  font-size: 9px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--accent); font-weight: 700;
  border: 1px solid var(--accent); border-radius: var(--radius-pill); padding: 2px 7px;
  flex: 0 0 auto;
}
.cal-prs__list { font-size: 13px; color: var(--ink); line-height: 1.35; }
.cal-notes { padding: 8px 0 4px; border-top: 1px solid var(--line-2); }
.cal-note { display: flex; gap: 8px; padding: 3px 0; font-size: 13px; line-height: 1.4; }
.cal-note__k {
  flex: 0 0 auto; max-width: 42%;
  color: var(--ink-2); font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cal-note__k::after { content: ':'; margin-left: 1px; color: var(--muted); }
.cal-note__v { color: var(--ink-2); flex: 1 1 auto; }

/* v0.9.441 DIARY-TODAY-UNIFIED-CARD — single "Today" card holds two stacked
   sections (Strength then Nutrition) separated by a thin token-coloured rule.
   Each section is hidden when its data is empty; the wrapping card is omitted
   entirely when both are empty. Token-only colours per css-hex-lint. */
.cal-card--today { display: flex; flex-direction: column; }
.cal-today-sec { padding-top: 10px; margin-top: 10px; border-top: 1px solid var(--line-2); }
.cal-today-sec:first-of-type { padding-top: 0; margin-top: 0; border-top: 0; }
.cal-today-sec__hd {
  font-size: 9px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--muted); font-weight: 700; margin-bottom: 8px;
}
.cal-today-sec--strength .cal-today-sess { padding-top: 8px; }
.cal-today-sec--strength .cal-today-sess + .cal-today-sess {
  margin-top: 8px; border-top: 1px solid var(--line-2);
}
.cal-today-sess__hd {
  display: flex; align-items: baseline; gap: 8px;
}
.cal-today-sess__name { font-size: 14px; font-weight: 600; color: var(--ink); }
.cal-today-sess__meta { font-size: 12px; color: var(--muted); margin-left: auto; font-feature-settings: 'tnum' 1; }

.cal-loading {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(248, 248, 246, 0.7);
  font-size: 13px;
  color: var(--muted);
}

/* ──────────────────────────────────────────────────────────
   Activity card (v0.9.427 DIARY-4-TRACKER-FRAMEWORK)
   Sleep / Burned / Net rows below a heart-rate icon header. Reuses the
   same .cal-card frame as Nutrition + Strength; the per-row layout
   mirrors .cal-macro (k on the left, v right-aligned, tnum). Token-only
   colours — no hex per the css-hex-lint rule.
   ────────────────────────────────────────────────────────── */
.cal-card--tracker { display: flex; flex-direction: column; }

/* USER-CALENDAR-FEEDS-IMPORT-EXPORT — external calendar events as a calm,
   dimmed, non-interactive background layer. Muted so Sassu's own cards lead. */
.cal-ext { opacity: 0.72; pointer-events: none; }
.cal-ext .cal-card__name { color: var(--ink-2); }
.cal-ext__row {
  display: flex;
  gap: var(--space-2);
  align-items: baseline;
  padding: 4px 0;
  font-size: 13px;
  color: var(--ink-2);
}
.cal-ext__time { flex: 0 0 auto; min-width: 54px; font-variant-numeric: tabular-nums; }
.cal-ext__title { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* CALENDAR-CLIENT-SEES-COACH-SESSIONS — the coach's appointments. Full ink,
   NOT the dimmed .cal-ext treatment: this is first-line Sassu content. */
.cal-coach__row {
  display: flex;
  gap: var(--space-2);
  align-items: baseline;
  padding: 4px 0;
  font-size: 13px;
  color: var(--ink);
}
.cal-coach__time { flex: 0 0 auto; min-width: 54px; font-variant-numeric: tabular-nums; }
.cal-coach__title { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-coach__chip {
  flex: 0 0 auto;
  font-size: 11px;
  line-height: 1.5;
  padding: 0 6px;
  border-radius: 999px;
  border: 1px solid var(--line);
  color: var(--ink);
  background: var(--surface-2, var(--surface));
}
.cal-coach__chip--off { color: var(--ink-2); }
/* CALENDAR-CLIENT-CANCEL-CHAT-MAIL-NOTIFY — only an upcoming appointment is
   pressable; the affordance is the pointer + focus ring, no extra button. */
.cal-coach__row.is-tappable { cursor: pointer; }
.cal-coach__row.is-tappable:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cal-coach__row.is-cancelled .cal-coach__time,
.cal-coach__row.is-cancelled .cal-coach__title {
  color: var(--ink-2);
  text-decoration: line-through;
}
/* AFSPRAAK-DE-KLANT-ZIET-ZIJN-SESSIES-UIT-DE-APP-ZELF — time and gym stack in
   one fixed-width column so the gym name can never squeeze the title or push
   the status chip off a 390px screen. */
.cal-coach__when { flex: 0 0 126px; max-width: 45%; display: flex; flex-direction: column; }
.cal-coach__when .cal-coach__time { min-width: 0; }
.cal-coach__place {
  font-size: 11px;
  color: var(--ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cal-coach__row.is-cancelled .cal-coach__place { text-decoration: line-through; }

/* The "Next session" line at the top of the day card. A button, because it
   moves the agenda — but it reads as a line, not as a control. */
/* DAGBOEK-JE-EERSTVOLGENDE-COACHAFSPRAAK-STAAT-ER-WEER — de aankondiging.
   Eén dekkend paneel, hetzelfde als .fz-weekchart erboven: het Dagboek tekent
   op een dagfoto, en losse tekst daarop is niet te lezen. De regel STAPELT
   (label boven, waarde eronder) — naast elkaar knijpt het label op 390px de
   dag en de tijd weg, en dat is precies wat de regel moet zeggen. */
.cal-nextappt {
  margin: var(--space-3) 0 0;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.cal-next-session {
  display: block;
  width: 100%;
  padding: 0;
  margin: 0;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: var(--ink);
}
.cal-next-session:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Een sessie die niet meer te openen is (afgezegd, no-show, geen identiteit)
   blijft dezelfde regel, maar zonder de belofte dat er iets gebeurt als je
   erop drukt. */
.cal-next-session.is-plain { cursor: default; }
.cal-next-session__k {
  display: block;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--ink-2);
}
.cal-next-session__v {
  display: block;
  margin-top: 2px;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.35;
}
/* De tekstknop eronder. Geen tweede accentknop naast de check-in-knop: die
   moet de zwaarste blijven op dit scherm. */
.cal-allappts-btn {
  display: block;
  padding: 6px 0 0;
  margin: 0;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.cal-allappts-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Het blad met alle komende afspraken: per dag een kopregel, daaronder de
   ongewijzigde dagkaart. */
.cal-allappts { max-height: 80vh; overflow-y: auto; }
.cal-allappts__day {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  color: var(--ink-2);
  margin: var(--space-4) 0 var(--space-2);
}
.cal-allappts__day:first-of-type { margin-top: 0; }

/* The small session sheet — rides on the .ui-dlg primitive, adds only rows. */
.cal-sheet { margin: 0 0 var(--space-5); }
.cal-sheet-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  align-items: baseline;
  padding: 6px 0;
  border-bottom: 1px solid var(--line);
  font-size: 14px;
}
.cal-sheet-row:last-child { border-bottom: 0; }
.cal-sheet-row__k { flex: 0 0 auto; color: var(--ink-2); }
.cal-sheet-row__v { flex: 1 1 auto; min-width: 0; text-align: right; color: var(--ink); }
.cal-sheet__rule {
  margin: var(--space-3) 0 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--ink-2);
}
.cal-tracker-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 6px 0;
  border-top: 1px solid var(--line-2);
  font-size: 13px;
}
.cal-tracker-row:first-of-type { border-top: 0; padding-top: 4px; }
.cal-tracker-row__k { color: var(--ink-2); }
.cal-tracker-row__v {
  color: var(--ink);
  font-weight: 500;
  font-feature-settings: 'tnum' 1;
}

/* Tracker Settings tile (v0.9.427) — rows mirror .mcp-apps__row anatomy.
   v0.9.508 SETTINGS-PADDING-AND-PROFILE-KERNING-V2 — list + rows were
   horizontally bleeding to the card's left edge (Oura / Garmin / Apple /
   Google chips touching the edge). Standardised inset to the same 16px
   gutter every other Settings section uses, with min-height 44px per HIG. */
.tracker-list {
  display: flex; flex-direction: column;
  padding-bottom: var(--space-3);
}
.tracker-list__empty {
  color: var(--muted); font-size: 13px;
  padding: 6px var(--space-4);
}
.tracker-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: 44px;
  padding: 11px var(--space-4);
  border-top: 1px solid var(--line-2);
}
.tracker-row:first-child { border-top: 0; }
.tracker-row__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tracker-row__name { font-size: 15px; color: var(--ink); }
.tracker-row__soon {
  font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted);
  border: 1px solid var(--line); border-radius: var(--radius-pill); padding: 2px 7px;
  flex: 0 0 auto;
}
/* Derde koppelstand — de verbinding is verlopen (Google laat de toestemming
   van een tester na 7 dagen vervallen). De regel moet OPVALLEN zonder te
   alarmeren: dezelfde rij, een uitleg eronder, en een knop die exact dezelfde
   koppelroute start als de eerste keer. GOOGLE-HEALTH-DE-KOPPELING-ZEGT-WANNEER-ZE-VERLOPEN-IS */
.tracker-row--expired { flex-wrap: wrap; align-items: flex-start; }
.tracker-row--expired .tracker-row__text { flex: 1 1 100%; }
.tracker-row--expired .tracker-row__name { color: var(--danger); }
.tracker-row--expired .set-mini { margin-top: 10px; margin-left: auto; }
.tracker-row__note { font-size: 12px; line-height: 1.35; color: var(--muted); }
.set-mini--accent {
  border-color: var(--accent); color: var(--accent);
  flex: 0 0 auto; text-decoration: none; white-space: nowrap;
}
.set-mini--accent:hover { border-color: var(--accent); color: var(--accent); }


/* ──────────────────────────────────────────────────────────
   Diary metrics — week-header position (v0.9.455 DIARY-METRICS-ROW-PROMOTE-TO-TOP)
   Per-day metric inputs (weight, sleep, steps, water) toggled per-user in
   Settings → Dagboek metrics. Promoted to live directly under the "This
   week" totals strip — each metric is a tap-target with two states:
   EMPTY (accent CTA pill — "Log today's weight") and FILLED (calm surface
   card — "Weight 78.4 kg ›"). Empty-state colours follow the accent-on-token
   contrast protocol (var(--on-accent), never hardcoded white — lint rule g).
   Solo = single full-width pill; ≥2 = 2-column grid.
   ────────────────────────────────────────────────────────── */
.cal-diary-metrics {
  display: grid;
  gap: var(--space-2);
  margin: var(--space-2) 0;
}
.cal-diary-metrics--solo {
  grid-template-columns: 1fr;
}
.cal-metric {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 12px 14px;
  border-radius: var(--radius-lg);
  border: 1px solid transparent;
  font-size: 14px;
  min-height: 44px;
  box-sizing: border-box;
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--dur-base) var(--ease-standard), border-color var(--dur-base) var(--ease-standard), color var(--dur-base) var(--ease-standard);
}
.cal-metric--empty {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}
.cal-metric--empty .cal-metric__lbl {
  flex: 1 1 auto;
  font-weight: 600;
  font-size: 14px;
  line-height: 1.2;
}
.cal-metric--empty .cal-metric__hint {
  flex: 0 0 auto;
  font-size: 20px;
  font-weight: 600;
  line-height: 1;
  opacity: 0.9;
}
.cal-metric--filled {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--line);
}
.cal-metric--filled .cal-metric__lbl {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 12px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.cal-metric--filled .cal-metric__val {
  flex: 1 1 auto;
  font-weight: 600;
  font-size: 15px;
  font-feature-settings: 'tnum' 1;
}
/* DIARY-EIGEN-METRIEK-TYPE-TEKST — a text answer is clipped VISUALLY here; the
   DOM keeps the whole string (plus a title tooltip), so the cell never becomes
   the source of truth for a value the user typed in full. Tabular numerals are
   switched off because this is prose, not a measurement. */
.cal-metric--filled .cal-metric__val--text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: right;
  font-feature-settings: normal;
}
.cal-metric__input--text {
  font-feature-settings: normal;
}
.cal-metric--filled .cal-metric__hint {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 18px;
  line-height: 1;
}
.cal-metric--editing {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--accent);
}
.cal-metric--editing .cal-metric__lbl {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 12px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.cal-metric__input {
  flex: 1 1 auto;
  min-width: 0;
  background: transparent;
  color: var(--ink);
  border: none;
  padding: 0;
  font-size: 15px;
  font-weight: 600;
  font-feature-settings: 'tnum' 1;
  outline: none;
  -moz-appearance: textfield;
  appearance: textfield;
}
.cal-metric__input::-webkit-outer-spin-button,
.cal-metric__input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.cal-metric__unit {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 12px;
  font-weight: 500;
}
/* CHECKIN-ADAPTIVE-METRIC-PRIORITY-YESTERDAY-GHOST — yesterday's value shown
   as a suggestion the user must ACCEPT. It is an overlay, never input.value:
   a placeholder disappears on iOS the moment the field takes focus, which
   would kill the one-tap accept before the user ever saw it. */
.cal-metric__field {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  display: flex;
}
.cal-metric__field .cal-metric__input { width: 100%; min-width: 0; }
.cal-metric__ghost {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  pointer-events: none;
  color: var(--muted);
  font-size: 15px;
  font-weight: 600;
  font-feature-settings: 'tnum' 1;
}
.cal-metric__ghosthint {
  flex: 1 0 100%;
  margin-top: 6px;
  background: transparent;
  border: 0;
  padding: 0;
  text-align: left;
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
}
.cal-metric--ghosted { flex-wrap: wrap; }
.cal-metric--saved {
  border-color: var(--ok);
}
/* NUTRITION-LOG-WATER-BUTTON-BLUE-COLOR (v0.9.483) — semantic water-blue
   override on the water_ml quick-log cell only. Replaces the generic --accent
   fill with a domain-coded water blue declared in :root (strength.html). The
   .cal-metric--water modifier is added by paintMetricCell when metricKey ===
   'water_ml', so the Cardio steps cell (also a quick-log) stays on --accent. */
.cal-metric--water.cal-metric--empty {
  background: var(--water-blue);
  color: var(--on-water-blue);
  border-color: var(--water-blue);
}

/* v0.9.463 WATER-LOG-VESSEL-PRESETS — popover with 4 vessel chips + manual
   '+ ml' fallback. Anchored to the water metric cell from JS (openWaterVesselSheet).
   Tokens only — both Dag + Nacht themes inherit correctly. */
.fz-water-vessels {
  position: fixed;
  z-index: 130;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 220px;
  max-width: 320px;
  padding: 12px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-3);
  font-family: inherit;
}
.fz-water-vessel {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: 44px;
  width: 100%;
  padding: 8px 16px;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  font-family: inherit;
  cursor: pointer;
  text-align: center;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
}
.fz-water-vessel__lbl {
  font-size: 14px;
  font-weight: 600;
  line-height: 1.2;
}
.fz-water-vessel__sub {
  font-size: 12px;
  font-weight: 500;
  color: var(--muted);
  line-height: 1.2;
}
.fz-water-vessel:active {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}
.fz-water-vessel:active .fz-water-vessel__sub {
  color: var(--on-accent);
  opacity: 0.85;
}
.fz-water-vessels__sep {
  height: 1px;
  width: 100%;
  background: var(--line);
  margin: 2px 0;
}
.fz-water-vessel--manual {
  background: transparent;
  border-style: dashed;
}
/* v0.9.470 WATER-LOG-UNDO-LAST-STEP — Undo chip lives below a second
   separator beneath the manual '+ ml' chip. Same pill chrome as the other
   chips but with a slightly muted surface (it's a destructive-ish action,
   but NOT a destroy/clear button — so no red/destructive token; just a
   softer tone). Tokens only — Dag + Nacht inherit correctly. */
.fz-water-vessels__undo {
  background: var(--bg);
  color: var(--muted);
  border-style: dashed;
}
.fz-water-vessels__undo .fz-water-vessel__lbl {
  font-weight: 500;
}
.fz-water-vessels__undo .fz-water-vessel__sub {
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
  letter-spacing: 0.02em;
}
.fz-water-vessels__undo:active:not(.fz-water-vessels__undo--disabled) {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--ink);
}
.fz-water-vessels__undo:active:not(.fz-water-vessels__undo--disabled) .fz-water-vessel__sub {
  color: var(--ink);
  opacity: 0.7;
}
.fz-water-vessels__undo--disabled {
  opacity: 0.5;
  cursor: default;
  pointer-events: none;
}
/* WATER-TERUGDRAAIEN-OVERAL — "Clear today" chip, the always-open door beneath
   Undo. This one IS a clear, so it carries the danger token where Undo
   deliberately does not. Tokens only — Dag + Nacht inherit correctly. */
.fz-water-vessels__clear {
  background: var(--bg);
  color: var(--danger);
  border-style: dashed;
}
.fz-water-vessels__clear .fz-water-vessel__lbl { font-weight: 500; }
.fz-water-vessels__clear .fz-water-vessel__sub {
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
  letter-spacing: 0.02em;
}
.fz-water-vessels__clear:active {
  background: var(--danger);
  color: var(--on-accent);
  border-color: var(--danger);
}
.fz-water-vessels__clear:active .fz-water-vessel__sub {
  color: var(--on-accent);
  opacity: 0.7;
}
.fz-water-delta-badge {
  position: fixed;
  z-index: 140;
  padding: 6px 10px;
  background: var(--ink);
  color: var(--bg);
  border-radius: var(--radius-pill);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  pointer-events: none;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity var(--dur-base) var(--ease-standard), transform var(--dur-base) var(--ease-standard);
}
.fz-water-delta-badge--show {
  opacity: 1;
  transform: translateY(0);
}

/* ──────────────────────────────────────────────────────────
   Diary entry rows (v0.9.422 DIARY-2 → DIARY-ENTRIES-CARD-MOVE-INTO-
   MOOD-ANALYTICS-PAGE v0.9.509). Entries no longer live on the Diary
   tab — they're rendered inside the `.diary-entries-section` on the
   Mood analytics page (mood-page.js). The row visual chrome below is
   shared via the renderDiaryEntryRow component. All colours reference
   design tokens — no hardcoded hex per the css-hex-lint rule.
   ────────────────────────────────────────────────────────── */
.diary-hashtag {
  color: var(--accent);
  font-weight: 500;
}
.diary-mood-badge {
  display: inline-flex;
  align-items: center;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 11px;
  font-weight: 600;
  border-radius: var(--radius-pill);
  padding: 2px 8px;
  font-feature-settings: 'tnum' 1;
  flex: 0 0 auto;
}
.diary-mood-bar {
  position: relative;
  height: 4px;
  background: var(--line);
  border-radius: var(--radius-sm);
  margin: 6px 0 6px;
  overflow: hidden;
  --mood-pct: 50%;
}
.diary-mood-bar::after {
  content: '';
  position: absolute;
  inset: 0;
  width: var(--mood-pct);
  background: var(--accent);
  border-radius: var(--radius-sm);
}
.diary-entry {
  padding: 8px 0;
  border-top: 1px solid var(--line-2);
}
.diary-entry:first-of-type { border-top: 0; padding-top: 4px; }
.diary-entry-body {
  font-size: 14px;
  line-height: 1.45;
  color: var(--ink);
  white-space: pre-wrap;
  word-break: break-word;
}
.diary-entry__meta {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--muted);
  margin-top: 4px;
}
.diary-entry__ts {
  font-feature-settings: 'tnum' 1;
  margin-left: 4px;
}
.diary-entry__del {
  margin-left: auto;
  background: transparent;
  border: 0;
  padding: 4px;
  color: var(--ink-2);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
}
.diary-entry__del:hover { color: var(--danger); }
.diary-entry__del svg { width: 16px; height: 16px; }
.diary-toggle {
  background: transparent;
  border: 0;
  color: var(--accent);
  font-size: 13px;
  font-weight: 600;
  text-align: left;
  padding: 4px 0;
  cursor: pointer;
  width: 100%;
}
.diary-composer {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.diary-composer__ta-wrap {
  position: relative;
}
.diary-body-input {
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  line-height: 1.45;
  padding: 8px 10px;
  resize: vertical;
  /* CHECKIN-PAGE-REORDER (v0.9.506) — bumped from 64px so the rows='4'
     multi-line placeholder (three reflective prompts) is visible without
     scrolling the textarea. */
  min-height: 96px;
  box-sizing: border-box;
  white-space: pre-wrap;
}
.diary-body-input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}
.diary-body-input.is-err {
  border-color: var(--danger);
  animation: diary-shake var(--dur-base) var(--ease-standard);
}
.diary-ac {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  margin-top: 4px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--elev-2);
  z-index: 5;
  max-height: 220px;
  overflow: auto;
}
.diary-ac[hidden] { display: none; }
.diary-ac-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  background: transparent;
  border: 0;
  padding: 8px 10px;
  font: inherit;
  font-size: 13px;
  color: var(--ink);
  cursor: pointer;
  text-align: left;
}
.diary-ac-item:hover, .diary-ac-item:focus-visible {
  background: var(--surface-muted);
}
.diary-ac-item__tag {
  color: var(--accent);
  font-weight: 600;
}
.diary-ac-item__uses {
  color: var(--muted);
  font-size: 11px;
  font-feature-settings: 'tnum' 1;
}
/* MOOD-HASHTAG-DEFAULT-SUGGESTIONS — horizontal scrollable chip-row that
   sits directly under the diary composer textarea. Sticky-to-input visual:
   no margin-top, padding above the chips. Touch scroll on iOS. */
.fz-mood-chips {
  display: flex;
  flex-wrap: nowrap;
  gap: 8px;
  padding: 8px 0;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.fz-mood-chips::-webkit-scrollbar { display: none; }
.fz-mood-chip {
  flex: 0 0 auto;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  padding: 4px 10px;
  font: inherit;
  font-size: 12px;
  line-height: 1.2;
  white-space: nowrap;
  cursor: pointer;
}
.fz-mood-chip--on {
  background: var(--accent);
  color: var(--on-accent);
  border-color: transparent;
}
.diary-composer__slider {
  display: flex;
  align-items: center;
  gap: 10px;
}
.diary-mood-lbl {
  font-size: 12px;
  color: var(--muted);
  flex: 0 0 auto;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.diary-mood-slider {
  flex: 1 1 auto;
  width: 100%;
  accent-color: var(--accent);
}
.diary-mood-val {
  min-width: 2ch;
  text-align: right;
  font-feature-settings: 'tnum' 1;
  color: var(--accent);
  font-weight: 600;
  font-size: 14px;
}
.cal-diary-footer {
  display: flex;
  gap: 12px;
  align-items: stretch;
  margin-top: 12px;
}
/* DIARY-TRIGGER-INTERVENTION-LOOP-P1 — the "+ Trigger" toggle reuses the old
   temptation-toggle sizing/colour tokens so the composer slot is unchanged. */
.diary-composer__trigger {
  display: flex;
  align-items: center;
  justify-content: flex-start;
}
.diary-trigger-open {
  background: none;
  border: 0;
  padding: 4px 0;
  font-size: 12px;
  color: var(--muted);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  cursor: pointer;
  text-decoration: none;
}

/* ── Trigger intervention-loop sheet (full-screen overlay) ── */
html.trigger-sheet-open { overflow: hidden; }
.trigger-sheet {
  position: fixed;
  inset: 0;
  z-index: 100000;          /* above the bottombar (z 99999) */
  background: var(--surface);
  color: var(--ink);
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.trigger-sheet__head {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--surface);
  display: flex;
  align-items: center;
  min-height: 48px;
  padding: 8px var(--space-4);
}
.trigger-sheet__back {
  background: none;
  border: 0;
  color: var(--ink);
  cursor: pointer;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.trigger-sheet__back svg { width: 22px; height: 22px; }
.trigger-sheet__back-spacer { width: 32px; height: 32px; }
.trigger-affirmation {
  position: sticky;
  top: 48px;
  z-index: 1;
  background: var(--surface);
  margin: 0 auto;
  max-width: 32rem;
  text-align: center;
  color: var(--text);
  line-height: 1.45;
  padding: var(--space-3) var(--space-4);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
}
.trigger-affirmation__quote {
  font-family: Georgia, 'Times New Roman', 'Times', serif;
  font-style: italic;
  font-size: 1.05em;
  line-height: 1.45;
  color: var(--text);
  text-align: center;
  display: block;
}
.trigger-affirmation__quote::before { content: '\201C'; margin-right: 0.15em; }
.trigger-affirmation__quote::after  { content: '\201D'; margin-left: 0.15em; }
.trigger-affirmation__icon {
  display: block;
  width: 90%;
  max-width: 90%;
  height: auto;
  object-fit: contain;
  margin: 0 auto;
}
@media (max-height: 720px) {
  .trigger-affirmation__icon { width: 60%; max-width: 60%; }
}
.trigger-step {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  max-width: 32rem;
  width: 100%;
  margin: 0 auto;
}
.trigger-step__h {
  font-size: 20px;
  font-weight: 700;
  margin: 0;
  line-height: 1.25;
}
.trigger-step__sub {
  font-size: 13px;
  color: var(--muted);
  margin: 0;
}
.trigger-step__err {
  font-size: 13px;
  color: var(--danger);
  margin: 0;
}
.trigger-slider-row {
  display: flex;
  align-items: center;
  gap: 12px;
}
.trigger-intensity-slider {
  flex: 1 1 auto;
  width: 100%;
  accent-color: var(--accent);
}
.trigger-intensity-val {
  min-width: 2ch;
  text-align: right;
  font-feature-settings: 'tnum' 1;
  color: var(--accent);
  font-weight: 700;
  font-size: 18px;
}
.trigger-ta {
  width: 100%;
  box-sizing: border-box;
  resize: vertical;
  font: inherit;
  padding: 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--surface-card);
  color: var(--ink);
}
.trigger-step__foot {
  display: flex;
  margin-top: var(--space-2);
}
.trigger-step__foot--split { gap: 12px; }
.trigger-step__foot .trigger-btn { flex: 1 1 auto; }
.trigger-btn {
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  padding: 12px 16px;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.trigger-btn:active { background: var(--accent-subtle); }
.trigger-btn:disabled { opacity: 0.5; cursor: default; }
.trigger-btn--primary {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}
.trigger-picker-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
}
.trigger-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 110px;
  padding: 16px 12px;
  background: var(--surface-card);
  border: 0;
  border-radius: var(--radius-sm);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
  text-align: center;
}
.trigger-card:active { transform: scale(0.98); }
.trigger-card:disabled { opacity: 0.5; cursor: default; }
.trigger-card__icon {
  display: inline-flex;
  color: var(--accent);
}
.trigger-card__icon svg { width: 28px; height: 28px; }
.trigger-card__label {
  font-size: 13px;
  font-weight: 600;
  line-height: 1.2;
}

/* ── P2: intervention screens + outcome capture ── */
.trigger-intervention {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-4);
  padding: var(--space-4) 0;
  width: 100%;
}
.trigger-intervention .trigger-step__h { text-align: center; }
.trigger-intervention .trigger-step__sub { text-align: center; }
.trigger-intervention .trigger-btn--primary { width: 100%; }
.trigger-intervention .trigger-ta { text-align: left; }
/* DIARY-TRIGGER-MAKE-IT-PUBLIC-SHARE-SHEET — editable template + share/copy/anchor */
.trigger-go-public {
  gap: var(--space-3);
}
.trigger-share-ta {
  width: 100%;
  min-height: 8rem;
  font: inherit;
  font-size: 1em;
  text-align: left;
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  border: 1px solid var(--line);
  color: inherit;
  resize: vertical;
}
.trigger-share-ta:focus {
  outline: none;
  border-color: var(--accent);
}
.trigger-share-actions {
  display: flex;
  gap: var(--space-2);
  width: 100%;
}
.trigger-share-actions .trigger-btn {
  flex: 1 1 0;
  width: auto;
}
.trigger-share-fallback-link {
  align-self: center;
  font-size: 0.9em;
  color: var(--muted);
  text-decoration: underline;
  cursor: pointer;
}
.trigger-timer {
  font-size: 4rem;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.trigger-timer-presets {
  display: flex;
  gap: var(--space-2);
  justify-content: center;
}
.trigger-timer-preset {
  min-width: 48px;
  padding: 8px 12px;
}
.trigger-timer-preset.is-on {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}
.trigger-hold-progress {
  width: 100%;
  height: 4px;
  border-radius: var(--radius-sm);
  background: var(--line);
  overflow: hidden;
}
.trigger-hold-progress span {
  display: block;
  height: 100%;
  width: 0;
  background: var(--accent);
  transition: width var(--dur-base) linear;
}
.trigger-outcome-grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: var(--space-2);
}
.trigger-outcome-btn {
  padding: 14px 8px;
  font-size: 13px;
}
.trigger-outcome-btn.is-on {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}
.trigger-outcome-after {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.trigger-skip-outcome {
  background: none;
  border: 0;
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  text-decoration: underline;
  cursor: pointer;
  align-self: center;
  margin-top: var(--space-2);
}

/* ── P2: Recent triggers section in the Diary composer ── */
.diary-recent-triggers {
  margin-top: var(--space-3);
  border-top: 1px solid var(--line);
  padding-top: var(--space-3);
}
/* DIARY-MOOD-ANALYTICS-MOVE-RECENT-TRIGGERS-FROM-CHECKIN — compact entry-point
   on Check-in (where Recent Triggers used to live) linking to the Mood
   analytics page. Light chrome so it doesn't compete with the Save button. */
.checkin-mood-link {
  margin-top: var(--space-3);
  padding-top: var(--space-2);
}
.checkin-mood-link__btn {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  padding: var(--space-2) 0;
  color: var(--ink);
  font: inherit;
  font-size: 0.95em;
  cursor: pointer;
}
.checkin-mood-link__count { color: var(--muted); }
/* On the Mood analytics page the Recent Triggers list leads the page, so it
   needs no top divider. */
.trigger-patterns-recent {
  margin-top: 0;
  border-top: none;
  padding-top: 0;
  margin-bottom: var(--space-4);
}
.diary-recent-triggers__head {
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--muted);
  margin-bottom: var(--space-2);
}
.diary-recent-triggers__count {
  color: var(--accent);
}
.diary-recent-trigger__group { margin-bottom: var(--space-2); }
.diary-recent-trigger__date {
  font-size: 11px;
  color: var(--muted);
  margin-bottom: 4px;
}
.diary-recent-trigger__row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 0;
  font-size: 13px;
}
.diary-recent-trigger__time {
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  min-width: 3.5ch;
}
.diary-recent-trigger__int {
  font-weight: 700;
  color: var(--accent);
  min-width: 2ch;
}
.diary-recent-trigger__iv {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.diary-recent-trigger__badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  font-size: 11px;
  font-weight: 600;
  padding: 4px 8px;
  border-radius: var(--radius-pill);
  border: 0;
}
.diary-recent-trigger__badge svg { width: 14px; height: 14px; }
.diary-recent-trigger__badge--succeeded { background: var(--accent-subtle); color: var(--ok); }
.diary-recent-trigger__badge--partial   { background: var(--accent-subtle); color: var(--warning, var(--nutrition)); }
.diary-recent-trigger__badge--failed    { background: var(--accent-subtle); color: var(--danger); }
.diary-recent-trigger__badge--untracked {
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  text-decoration: underline;
}
.recent-trigger-hashtag {
  flex: 0 0 auto;
  font-size: 10px;
  font-weight: 600;
  padding: 2px 6px;
  border-radius: var(--radius-pill);
  background: var(--surface-2, var(--surface));
  color: var(--muted);
}

/* ── Hashtag pre-step (DIARY-TRIGGER-HASHTAG-PRE-STEP) ── */
.trigger-hashtag-input {
  width: 100%;
  box-sizing: border-box;
  background: var(--surface-2, var(--surface));
  color: inherit;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  font: inherit;
  margin-top: 8px;
}
.trigger-hashtag-input:focus {
  outline: none;
  border-color: var(--accent);
}
.trigger-hashtag-suggestions {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  padding: 4px 2px;
  margin-top: 8px;
}
.trigger-hashtag-chip {
  flex: 0 0 auto;
  background: var(--surface-2, var(--surface));
  color: inherit;
  border: 0;
  border-radius: var(--radius-pill);
  padding: 4px 12px;
  font: inherit;
  font-size: 0.9em;
  cursor: pointer;
  white-space: nowrap;
}
.trigger-hashtag-chip.is-on {
  background: var(--accent);
  color: var(--on-accent);
}
.trigger-hashtag-preview {
  text-align: center;
  color: var(--muted);
  font-size: 13px;
  min-height: 1.2em;
  margin-top: 6px;
}
.trigger-hashtag-skiplink {
  display: block;
  margin: 6px auto 0;
  background: none;
  border: 0;
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  text-decoration: underline;
  cursor: pointer;
}

/* ── P3: "See patterns" link in the Recent triggers header ── */
.diary-recent-triggers__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.diary-recent-triggers__patterns {
  background: none;
  border: 0;
  color: var(--accent);
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  cursor: pointer;
  padding: 0;
}

/* ── P3: recommended intervention card (Step 4 effectiveness ordering) ── */
.trigger-card--recommended {
  border: 2px solid var(--accent);
}
.trigger-card__hint {
  font-size: 11px;
  font-weight: 600;
  color: var(--accent);
  line-height: 1.1;
}
.trigger-show-all {
  display: block;
  width: 100%;
  margin-top: var(--space-3);
  background: none;
  border: 0;
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  text-decoration: underline;
  cursor: pointer;
}

/* ── P3: Trigger patterns page (/diary/triggers/patterns) ── */
.trigger-patterns-page {
  padding: var(--space-4);
}

/* DIARY-MOOD-ANALYTICS-VAULT-PASSWORD-PROTECTION */
.mood-vault-settings__head {
  display: flex;
  align-items: center;
  margin-bottom: var(--space-2);
}
.mood-vault-settings__title {
  font-size: 1rem;
  font-weight: 600;
  margin: 0;
}
.mood-vault-settings__toggle {
  cursor: pointer;
}
.mood-vault-settings__checkbox {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
}
.mood-vault-settings__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-3);
}
.mood-vault-settings__body form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.mood-vault-gate {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-3);
  padding: var(--space-6) var(--space-4);
  max-width: 360px;
  margin: 0 auto;
}
.mood-vault-gate__icon {
  color: var(--muted);
}
.mood-vault-gate__icon svg {
  width: 36px;
  height: 36px;
}
.mood-vault-gate__title {
  font-size: 1.125rem;
  font-weight: 600;
  margin: 0;
}
.mood-vault-gate__sub {
  color: var(--muted);
  margin: 0;
}
.mood-vault-gate__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  width: 100%;
  margin-top: var(--space-2);
}
.mood-vault-gate__input {
  width: 100%;
}
.mood-vault-gate__err {
  color: var(--danger, var(--muted));
  font-size: 0.875rem;
}
.trigger-patterns-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  margin-top: var(--space-4);
}
.trigger-patterns-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.trigger-patterns-section__h {
  font-size: 14px;
  font-weight: 700;
  margin: 0 0 var(--space-1);
}
.trigger-pattern-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 10px 12px;
  background: var(--surface-card);
  border-radius: var(--radius-sm);
  font-size: 13px;
}
.trigger-pattern-row--high-risk {
  border-left: 4px solid var(--warning, var(--nutrition));
}
.trigger-pattern-row__text {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
}
.trigger-pattern-row__chip {
  flex: 0 0 auto;
  font-size: 11px;
  font-weight: 600;
  color: var(--warning, var(--nutrition));
  background: var(--accent-subtle);
  padding: 2px 8px;
  border-radius: var(--radius-pill);
}
.trigger-pattern-row__count {
  flex: 0 0 auto;
  font-weight: 700;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.trigger-patterns-chart {
  width: 100%;
  max-width: 560px;
  margin: 0 auto;
  padding: var(--space-2) 0;
}
.trigger-patterns-empty {
  color: var(--muted);
  font-size: 14px;
  line-height: 1.5;
  margin-top: var(--space-4);
}
/* CHECKIN-BUTTON-MINIMALIST-DEMOTION (v0.9.553) — demoted from accent-fill
   to outlined so /check-in stops fighting itself for attention. Class only
   renders inside mountMoodEntryCard, which is now mounted exclusively by
   /check-in (mood card moved out of /diary in v0.9.494), so a global
   demotion is scope-safe — no Diary surface still wears this class. */
.cal-diary-foot__save {
  flex: 1 1 auto;
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--accent);
  border-radius: var(--radius-md);
  padding: 12px 16px;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.cal-diary-foot__save:active { background: var(--accent-subtle); }
.cal-diary-foot__save:disabled { opacity: 0.6; cursor: default; }

/* CHECKIN-PAGE-REORDER-PLUS-MOOD-ANALYTICS-ICON-BUTTON (v0.9.506) — square
   analytics icon-button sitting LEFT of Save entry on the mood card footer.
   Border-only chrome so it reads as a secondary affordance against the
   accent-filled Save button. 44×44 minimum tap target both axes. */
.cal-diary-foot__analytics {
  flex: 0 0 44px;
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 0;
  color: var(--muted);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.cal-diary-foot__analytics:hover,
.cal-diary-foot__analytics:active {
  background: var(--surface-muted);
  color: var(--ink);
}
.cal-diary-foot__analytics svg {
  width: 20px;
  height: 20px;
}

@keyframes diary-shake {
  0%   { transform: translateX(0); }
  20%  { transform: translateX(-6px); }
  40%  { transform: translateX(6px); }
  60%  { transform: translateX(-4px); }
  80%  { transform: translateX(4px); }
  100% { transform: translateX(0); }
}
@media (prefers-reduced-motion: reduce) {
  .diary-body-input.is-err { animation: none; }
}


/* ──────────────────────────────────────────────────────────
   Mood analytics (v0.9.426 DIARY-3)
   Mood line chart + hashtag bubble scatter, always visible
   below the week strip in Week mode. Inline SVG — no canvas.
   Bubble fill colour is set inline via fill="rgb(r,g,b)" on
   the SVG <circle> for semantic mood encoding; that's the
   ONLY hex-equivalent that lives outside this file and is
   exempt from css-hex-lint by virtue of not being in CSS.
   ────────────────────────────────────────────────────────── */
.diary-analytics {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin: 16px 0 8px;
  padding: 12px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}
.diary-mood-chart,
.diary-bubble-section,
.mood-history {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* MOOD-ANALYTICS-HISTORY — the conversations under the charts. The turn idiom
   is lifted from the check-in itself: question dimmed to 0.45, answer at full
   weight. A day header is a button because a wall of transcript does not get
   read; only today opens itself. */
.mood-history__day {
  border-top: 1px solid var(--line);
  padding-top: 8px;
}
.mood-history__date {
  background: transparent;
  border: 0;
  padding: 6px 0;
  width: 100%;
  min-height: 44px;
  text-align: left;
  cursor: pointer;
  color: var(--ink);
  font-size: 13px;
  font-weight: 700;
}
.mood-history__body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 2px 0 10px;
}
.mood-history__tod {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-weight: 700;
}
.mood-history__q {
  margin: 8px 0 2px;
  font-size: 13px;
  line-height: 1.4;
  color: var(--ink);
  opacity: 0.45;
}
.mood-history__a {
  margin: 0;
  font-size: 14px;
  line-height: 1.45;
  color: var(--ink);
  white-space: pre-wrap;
}
.mood-history__scores {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.mood-history__score {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 3px solid var(--mh-score-color, var(--line));
  border-radius: var(--radius-md);
  padding: 4px 8px;
}
.diary-chart-head {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-weight: 700;
  margin: 0;
}
.diary-chart-sub {
  font-size: 12px;
  color: var(--muted);
}
.diary-chart-loading,
.diary-chart-empty {
  font-size: 13px;
  color: var(--muted);
  padding: 12px 0;
  text-align: center;
  line-height: 1.4;
}
.diary-mood-svg,
.diary-bubble-svg {
  width: 100%;
  height: auto;
  display: block;
}
.diary-chart-label {
  font-size: 10px;
  fill: var(--muted);
}
.diary-bubble-label {
  font-size: 10px;
  fill: var(--ink);
  font-weight: 600;
  pointer-events: none;
  paint-order: stroke;
  stroke: var(--surface);
  stroke-width: 2px;
  stroke-linejoin: round;
}
.diary-bubble-leader {
  stroke: var(--muted);
  stroke-width: 0.6px;
  opacity: 0.6;
  pointer-events: none;
}
.diary-bubble { cursor: pointer; }
.diary-bubble:active circle { fill-opacity: 1; }

/* ── MOODCHART-DEPTH-IS-RECENCY-AND-BUBBLES-BREATHE ────────────────────────
   Depth means recency: close = used today, far = a year ago.

   PERSPECTIVE: 900px IS A CORRECTNESS GUARANTEE, NOT A LOOK. Against the z
   range of ±140px capped in mood-page.js it limits the apparent-size
   difference between the front and back plane to
       900 / (900 + 140) = 0.865  →  13.5 %
   which is less than one use_count step, so the depth axis can NEVER invert
   the size ordering (= frequency). At perspective 400px the same z range gives
   26 % and the chart starts lying about how often a tag was used. Redo that sum
   before changing this number, or the graph silently misreports its own data.

   Why divs over the SVG instead of preserve-3d inside it: measured. Inside
   <svg> the z translation is flattened outright (ratio 1.000 — no depth at
   all); the div layer measured exactly the 0.865 the geometry predicts. The SVG
   still owns background, axes, leader lines and beside-labels. */
.diary-bubble-space {
  position: relative;
  display: block;
  perspective: 900px;
  /* Lets the bubble text size itself in the SVG's own viewBox units (1 unit =
     0.3333cqw at viewBox width 300), so a label is the same size whether it is
     drawn by the SVG or by a div. */
  container-type: inline-size;
}
.diary-bubble-scene {
  position: relative;
  transform-style: preserve-3d;
  transform: rotateY(var(--fz-mc-ry, 0deg));
  /* Springs back to square on release. A chart left at an angle is unreadable,
     so the resting state is always 0. */
  transition: transform 400ms cubic-bezier(0.22, 1, 0.36, 1);
}
/* During the gesture the tilt must track the finger, not chase it. */
.diary-bubble-stage.is-gesturing .diary-bubble-scene,
.mood-explore__stage.is-gesturing .diary-bubble-scene { transition: none; }
.diary-bubble-layer {
  position: absolute;
  inset: 0;
  transform-origin: 0 0;
  transform-style: preserve-3d;
  /* --fz-mc-bake is 1 during the gesture (cheap GPU stretch of one texture) and
     1/scale at rest, when mood-page.js has grown the LAYOUT by that same factor
     so the compositor re-rasterises at native DPR instead of upscaling the 1x
     texture. --fz-mc-s never changes meaning, so every counter-scale divisor
     that reads it keeps working untouched. */
  transform: translate3d(var(--fz-mc-tx, 0px), var(--fz-mc-ty, 0px), 0) scale(calc(var(--fz-mc-s, 1) * var(--fz-mc-bake, 1)));
  pointer-events: none;
}
.diary-bubble-layer .diary-bubble {
  position: absolute;
  box-sizing: border-box;
  aspect-ratio: 1;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  pointer-events: auto;
  /* z is a 3D translation and scale() is 2D, so the composed depth is constant
     at every zoom level — the 13.5 % ceiling above holds at 1× and at 6×. */
  transform: translate3d(0, 0, var(--fz-z, 0px));
  animation-name: fz-mood-drift;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-play-state: paused;   /* only .diary-bubble--drift starts it */
}
.diary-bubble-layer .diary-bubble--drift { animation-play-state: running; }
.diary-bubble-layer .diary-bubble-inner {
  color: var(--fz-bub-ink, #FFFFFF);
  font-weight: 600;
  pointer-events: none;
  white-space: nowrap;
  /* Same on-screen size at every zoom, same rule as the SVG labels — but off
     --fz-mc-ls, which is only rewritten once the gesture ENDS. These two
     properties are the only per-bubble style that depends on the zoom, and with
     ~400 bubbles recomputing them per frame they cost the entire frame budget
     (measured: 24 fps dragging, 65 with the layer hidden). See
     applyLabelScale() in mood-page.js. */
  font-size: calc(var(--fz-fs, 9) * 0.33333cqw / var(--fz-mc-ls, 1));
}
/* The border is the ONE absolute-px thing inside the layer, so it is also the
   one divisor the bake has to override: baked, the layer is no longer scaled up
   and the border must be its native 1.5px. --fz-mc-bw is only set while baked. */
.diary-bubble-layer .diary-bubble { border-width: calc(1.5px / var(--fz-mc-bw, var(--fz-mc-ls, 1))); }
/* Both halts are deliberate: during a gesture two motion sources at once make
   the gesture imprecise, and a bubble you have tapped must hold still while you
   read its tooltip. */
.is-gesturing .diary-bubble-layer .diary-bubble,
.diary-bubble-layer .diary-bubble.is-frozen,
[data-motion="off"] .diary-bubble-layer .diary-bubble { animation-play-state: paused; }

/* will-change ONLY while a gesture runs, and only on the wrapper. A permanent
   per-bubble will-change costs texture memory around the clock for no gain. */
.is-gesturing .diary-bubble-layer { will-change: transform; }

/* One shared keyframe track; the character of each bubble comes entirely from
   four per-element variables. Primes of 41–67s (doubled by `alternate`) keep the
   cloud perceptually non-repeating for 80–130s — at 14s a human sees the loop
   within half a minute. NOTE: no requestAnimationFrame anywhere. A rAF loop
   writing 400 transforms per frame runs on the main thread, stutters on every
   garbage collect and dies the moment anything else happens; this runs on the
   compositor and costs zero JS per frame. */
@keyframes fz-mood-drift {
  0%   { transform: translate3d(0, 0, var(--fz-z, 0px)); }
  23%  { transform: translate3d(calc(var(--fz-ax, 0px) *  0.9), calc(var(--fz-ay, 0px) * -0.6), var(--fz-z, 0px)); }
  47%  { transform: translate3d(calc(var(--fz-ax, 0px) * -0.4), calc(var(--fz-ay, 0px) *  0.8), var(--fz-z, 0px)); }
  71%  { transform: translate3d(calc(var(--fz-ax, 0px) *  0.6), calc(var(--fz-ay, 0px) *  0.3), var(--fz-z, 0px)); }
  100% { transform: translate3d(0, 0, var(--fz-z, 0px)); }
}

/* WCAG 2.2 SC 2.2.2. All three conditions are met — starts automatically, never
   ends, sits beside other content — so this control is mandatory. */
.diary-bubble-motion-toggle {
  position: absolute;
  bottom: 6px;
  left: 6px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-2);
  cursor: pointer;
}
.diary-bubble-motion-toggle:hover { color: var(--ink); border-color: var(--ink-2); }

/* prefers-reduced-motion is a MEDICAL boundary, not a taste setting: floating
   at different depths with parallax is the exact pattern that triggers nausea
   and disorientation in vestibular disorders, and half a dose is still a dose.
   So: full stop, never a reduced amplitude. Depth, opacity and colour all
   REMAIN, so the third axis stays fully readable and the picture does not feel
   dead — it only holds still. The parallax stops too: the planes no longer move
   relative to each other while dragging. */
@media (prefers-reduced-motion: reduce) {
  .diary-bubble-layer .diary-bubble { animation: none !important; }
  .diary-bubble-scene { transition: none !important; transform: none !important; }
}

/* ── MOODCHART-PINCH-ZOOM-PAN-AND-EXPLORE-OVERLAY ──────────────────────────
   The stage owns the gesture; the SVG only carries a transform.

   touch-action: pan-y is the hostage guarantee and is NOT a style choice. It
   hands a vertical swipe that starts on the chart back to the page, so the
   chart can never trap the scroll — the failure mode there has no error and no
   way out but leaving the tab. It does NOT include pinch-zoom, so two fingers
   still reach our handler. Same reasoning as .mdx__svg further down. Never
   `none` here; `none` is only correct in .mood-explore__stage, which is
   full-screen and has nothing behind it. */
.diary-bubble-stage {
  position: relative;
  overflow: hidden;
  touch-action: pan-y;
  border-radius: var(--radius-md);
}
.diary-bubble-stage .diary-bubble-svg,
.mood-explore__stage .diary-bubble-svg {
  transform-origin: 0 0;
  /* Same bake factor as .diary-bubble-layer — the two must stay locked. */
  transform: translate3d(var(--fz-mc-tx, 0px), var(--fz-mc-ty, 0px), 0) scale(calc(var(--fz-mc-s, 1) * var(--fz-mc-bake, 1)));
}
/* Escape hatch. Not decoration: without it someone who pinches to 6× by
   accident has no obvious way back to the whole picture. */
.diary-bubble-zoomreset {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 2;
  min-width: 34px;
  height: 28px;
  padding: 0 8px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-2);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.diary-bubble-zoomreset[hidden] { display: none; }
/* The Verken affordance sits ON the preview, bottom-right, where the thumb
   already is — not in the section header a screen away from the chart. One
   line, icon + word (fz-lc-strip idiom). */
.diary-bubble-explore-chip {
  position: absolute;
  bottom: 6px;
  right: 6px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-2);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}
.diary-bubble-explore-chip svg { width: 16px; height: 16px; }
.diary-bubble-explore-chip:hover { color: var(--ink); border-color: var(--ink-2); }
.diary-bubble-headrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
/* Progressive reveal. One attribute write on the stage swaps the visible set —
   the alternative is a JS loop over 400 text nodes per zoom step. */
.diary-bubble-label--tier1,
.diary-bubble-label--tier2,
.diary-bubble-label--tier3,
.diary-bubble-label--tier4 { transition: opacity 180ms ease; }
[data-zoom-tier="1"] .diary-bubble-label--tier2,
[data-zoom-tier="1"] .diary-bubble-label--tier3,
[data-zoom-tier="1"] .diary-bubble-label--tier4,
[data-zoom-tier="2"] .diary-bubble-label--tier3,
[data-zoom-tier="2"] .diary-bubble-label--tier4,
[data-zoom-tier="3"] .diary-bubble-label--tier4 { opacity: 0; }
/* Strokes and text must keep their ON-SCREEN size while the geometry grows.
   font-size is compensated inline per element (it varies per bubble); the
   stroke widths are compensated here. At 4× an uncompensated 2px halo reads
   8px and swallows the glyphs. */
.diary-bubble-stage .diary-bubble-label,
.mood-explore__stage .diary-bubble-label { stroke-width: calc(2px / var(--fz-mc-s, 1)); }
.diary-bubble-stage .diary-bubble-inner,
.mood-explore__stage .diary-bubble-inner { stroke-width: calc(1.5px / var(--fz-mc-s, 1)); }
.diary-bubble-stage .diary-bubble-leader,
.mood-explore__stage .diary-bubble-leader { stroke-width: calc(0.6px / var(--fz-mc-s, 1)); }
.diary-bubble-stage .diary-chart-label,
.diary-bubble-stage .diary-chart-axis-hint,
.mood-explore__stage .diary-chart-label,
.mood-explore__stage .diary-chart-axis-hint { font-size: calc(var(--fz-mc-fs, 10px) / var(--fz-mc-s, 1)); }
.diary-bubble-stage .diary-chart-axis-hint,
.mood-explore__stage .diary-chart-axis-hint { --fz-mc-fs: 7.5px; }

/* Verken-modus. Background follows var(--bg) so the Dag preset is genuinely
   light — a hardcoded dark panel would survive the sunset theme flip and look
   broken half the day. */
.mood-explore {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: var(--bg);
  display: flex;
  flex-direction: column;
}
.mood-explore__stage {
  position: relative;
  flex: 1 1 auto;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Correct here and ONLY here: full-screen, nothing behind it to scroll, and
     a permanently visible close button as the way out. */
  touch-action: none;
  overscroll-behavior: contain;
}
.mood-explore__stage .diary-bubble-svg { width: 100%; }
.mood-explore__stage .diary-bubble-space { width: 100%; }
.mood-explore__close {
  position: absolute;
  top: max(12px, env(safe-area-inset-top));
  left: 12px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
/* Compass. The field's own mood gradient IS the map — same stop offsets as
   #moodRainbowBg — so the colour under the viewport box reads as the mood you
   are standing in. Decorative: aria-hidden, no pointer events, no focus stop. */
.mood-explore__minimap {
  position: absolute;
  left: 12px;
  bottom: max(12px, env(safe-area-inset-bottom));
  z-index: 2;
  width: 112px;
  height: 72px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: linear-gradient(
    to right,
    var(--mood-rainbow-1) 0%,
    var(--mood-rainbow-3) 22.2%,
    var(--mood-rainbow-5) 44.4%,
    var(--mood-rainbow-6) 55.5%,
    var(--mood-rainbow-8) 77.7%,
    var(--mood-rainbow-9) 88.8%,
    var(--mood-rainbow-10) 100%
  );
  overflow: hidden;
  pointer-events: none;
  transition: opacity 180ms ease;
}
.mood-explore__minimap[hidden] { display: none; }
.mood-explore__minimap-view {
  position: absolute;
  box-sizing: border-box;
  border: 1.5px solid var(--accent);
  border-radius: 3px;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  transition: left 90ms linear, top 90ms linear, width 90ms linear, height 90ms linear;
}
.mood-explore__minimap-hint {
  position: absolute;
  bottom: 1px;
  font-size: 9px;
  line-height: 1;
  font-weight: 700;
  color: var(--on-accent);
  opacity: 0.75;
}
.mood-explore__minimap-hint--lo { left: 3px; }
.mood-explore__minimap-hint--hi { right: 3px; }
@media (prefers-reduced-motion: reduce) {
  .mood-explore__minimap,
  .mood-explore__minimap-view { transition: none; }
}
.diary-chart-axis-hint {
  font-size: 7.5px;
  fill: var(--muted);
  letter-spacing: 0.04em;
  pointer-events: none;
}
.diary-bubble-inner {
  fill: var(--fz-bub-ink, #FFFFFF); /* lint-hex-ok */
  font-weight: 600;
  paint-order: stroke;
  stroke: var(--fz-bub-halo, #101014); /* lint-hex-ok */
  stroke-width: 1.5px;
  stroke-linejoin: round;
  pointer-events: none;
}

/* Mood line chart nav — 4-tab toggle + period navigator (v0.9.579) */
.mood-line-nav {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  margin: 8px 0 12px;
}
.mood-line-toggle {
  display: flex;
  background: var(--surface-muted);
  border-radius: var(--radius-pill);
  padding: 2px;
}
.mood-line-toggle__btn {
  border: none;
  background: transparent;
  color: var(--ink-2);
  font-size: 12px;
  font-weight: 500;
  padding: 5px 11px;
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
  white-space: nowrap;
}
.mood-line-toggle__btn.is-active {
  background: var(--surface-card);
  color: var(--ink);
  box-shadow: var(--elev-1); /* lint-hex-ok */
}
.mood-line-period-nav {
  display: flex;
  align-items: center;
  gap: 4px;
}
.mood-line-period-label {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
  min-width: 140px;
  text-align: center;
}
.mood-line-nav__arrow {
  border: none;
  background: transparent;
  color: var(--ink-2);
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard);
  padding: 0;
}
.mood-line-nav__arrow svg { width: 16px; height: 16px; display: block; }
.mood-line-nav__arrow:hover:not(:disabled) { background: var(--surface-muted); }
.mood-line-nav__arrow--disabled,
.mood-line-nav__arrow:disabled { opacity: 0.3; cursor: default; pointer-events: none; }

/* MOOD-ANALYTICS-RAINBOW-AND-POLISH (v0.9.474) — line chart polish */
.diary-mood-line {
  stroke: var(--accent);
  stroke-width: 2.2;
}
/* CHECKIN-DUAL-GRAPH — legend + verleiding (temptation) line */
.diary-chart-legend { display:flex; gap:1rem; align-items:center; margin-bottom:.5rem; }
.diary-chart-legend__item { display:flex; align-items:center; gap:.3rem; font-size:.75rem; color:var(--muted); }
.diary-chart-legend__dot { width:8px; height:8px; border-radius: 50%; flex-shrink:0; }
.diary-chart-legend__dot--mood { background:var(--accent); }
.diary-chart-legend__dot--temptation { background:var(--temptation-line); }
.diary-tempt-line { stroke:var(--temptation-line); stroke-width:1.8; stroke-dasharray:5 3; fill:none; }
.diary-tempt-dot { fill:var(--temptation-line); }
.diary-mood-fill-stop--top { stop-color: var(--accent); stop-opacity: 0.42; }
.diary-mood-fill-stop--bot { stop-color: var(--accent); stop-opacity: 0; }
.diary-chart-dot { fill: var(--accent); }
.diary-chart-dot--today { fill: var(--accent); stroke: var(--surface); stroke-width: 1.2; }
.diary-chart-dot--empty {
  fill: none;
  stroke: var(--ink-2);
  stroke-width: 1;
  stroke-dasharray: 1.6 1.6;
  opacity: 0.45;
}
.diary-chart-refline { stroke: var(--line); stroke-width: 1; opacity: 0.8; }
.diary-chart-reflabel { fill: var(--muted); font-size: 9px; }

/* MOOD-ANALYTICS-RAINBOW-AND-POLISH (v0.9.474) — scatter rainbow stops.
   stop-color references the :root tokens so the lint stays clean. */
.diary-scatter-bg { fill-opacity: var(--mood-rainbow-bg-opacity, 0.28); }
.mood-rainbow-stop--1  { stop-color: var(--mood-rainbow-1); }
.mood-rainbow-stop--3  { stop-color: var(--mood-rainbow-3); }
.mood-rainbow-stop--5  { stop-color: var(--mood-rainbow-5); }
.mood-rainbow-stop--6  { stop-color: var(--mood-rainbow-6); }
.mood-rainbow-stop--8  { stop-color: var(--mood-rainbow-8); }
.mood-rainbow-stop--9  { stop-color: var(--mood-rainbow-9); }
.mood-rainbow-stop--10 { stop-color: var(--mood-rainbow-10); }


.diary-range-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
.diary-date-in {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  color: var(--ink);
  font: inherit;
  font-size: 12px;
  padding: 6px 8px;
  flex: 1 1 0;
  min-width: 0;
}
.diary-date-in:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}
.diary-range-btn {
  background: var(--accent);
  color: var(--on-accent);
  border: 0;
  border-radius: var(--radius-md);
  padding: 6px 14px;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  flex: 0 0 auto;
}
.diary-range-btn:active { opacity: 0.85; }
.diary-range-clear {
  background: transparent;
  color: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 5px 10px;
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.diary-range-clear:hover { color: var(--ink); border-color: var(--ink-2); }

.diary-bubble-tip {
  position: fixed;
  z-index: 200;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 8px 10px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--ink);
  box-shadow: var(--elev-2);
  pointer-events: auto;
  max-width: 240px;
}
.diary-bubble-tip__tag {
  font-weight: 600;
  color: var(--accent);
  margin-bottom: 2px;
}
.diary-bubble-tip__row { color: var(--ink-2); }
.diary-bubble-tip__row strong { color: var(--ink); }

/* MOOD-TOPIC-TREND-CHART — merging one topic into another, from the tip. The
   chart geometry is untouched; this is only the detail panel. */
.diary-bubble-tip__note {
  color: var(--ink-2);
  font-size: 11px;
  margin-top: 4px;
}
.diary-bubble-tip__merge {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 8px;
}
.diary-bubble-tip__merge-btn {
  min-height: 36px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: var(--on-accent);
  font: inherit;
  font-weight: 600;
  padding: 6px 10px;
  cursor: pointer;
}
.diary-bubble-tip__merge-btn:disabled { opacity: 0.55; cursor: default; }
.diary-bubble-tip__merge-sel {
  min-height: 36px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  padding: 6px 8px;
  max-width: 100%;
}

/* DIARY-MOOD-PAGE — dedicated /calendar/mood page chrome. The trigger
   moved into the Diary card footer (see .cal-diary-foot__analytics).
   The charts themselves reuse .diary-mood-chart / .diary-bubble-section
   above. MOOD-ANALYTICS-RAINBOW-AND-POLISH (v0.9.474) tightened the
   header into a single row with an eyebrow + title stack.
   MOOD-TRACKER-PADDING-AND-CSS-POLISH (v0.9.489) ships proper
   outer-edge padding so the page no longer bleeds to the viewport
   edges (overrides the earlier padding:0 that erased .cal-mount's
   built-in inset). 16px horizontal + 12px top + 16px bottom; the
   parent .app-main already adds nav-h + safe-bottom clearance so the
   trailing section never sits under the bottom nav. Inter-section
   gap bumped from 18px to 24px (felt-distance separator). */
.mood-page {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-3) var(--space-4) var(--space-4);
}
.mood-page__head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 0 12px;
  border-bottom: 1px solid var(--line);
}
.mood-page__back {
  background: transparent;
  border: 0;
  color: var(--accent);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  padding: 6px 8px 6px 0;
  cursor: pointer;
  flex: 0 0 auto;
}
.mood-page__back:active { opacity: 0.75; }
.mood-page__title-wrap {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.mood-page__eyebrow {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--muted);
  line-height: 1;
}
.mood-page__title {
  font-size: 18px;
  font-weight: 700;
  color: var(--ink);
  margin: 0;
  line-height: 1.2;
  letter-spacing: -0.01em;
}


/* ──────────────────────────────────────────────────────────
   Add-Exercise modal (bottom sheet)
   ────────────────────────────────────────────────────────── */
.mo-overlay {
  position: fixed;
  inset: 0;
  background: var(--scrim);
  z-index: 200;
  display: flex;
  align-items: flex-end;
  opacity: 0;
  /* NUTRITION-M3-WAVE-3 — asymmetric M3 emphasized easing. The transition on the
     BASE rule governs the EXIT (mo--open removed): ~200ms out, matching the
     220ms JS teardown in closeSheet() so the fade completes before removal. */
  transition: opacity var(--dur-base) var(--ease-standard);
}
/* The transition on the OPEN rule governs the ENTER: ~300ms in. */
.mo-overlay.mo--open { opacity: 1; transition: opacity var(--dur-base) var(--ease-standard); }

.mo-sheet {
  background: var(--bg);
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
  max-height: 85vh;
  border-top-left-radius: var(--radius-lg);
  border-top-right-radius: var(--radius-lg);
  /* NUTRITION-M3-WAVE-3 — M3 elevation-3, theme-aware, cast upward. */
  box-shadow: var(--elev-up);
  display: flex;
  flex-direction: column;
  transform: translateY(100%);
  /* EXIT motion (see .mo-overlay note): ~200ms out, inside the 220ms teardown. */
  transition: transform var(--dur-base) var(--ease-standard);
  padding-bottom: var(--safe-bottom);
}
/* ENTER motion: ~300ms M3 emphasized slide-up. */
.mo-sheet.mo--open { transform: translateY(0); transition: transform var(--dur-base) var(--ease-standard); }

/* VOEDING-DE-ZOEKBALK-BLIJFT-STAAN-ALS-ER-NIETS-GEVONDEN-IS — the food-search
   sheet is the ONE sheet whose height must not follow its content. iOS Safari
   does not shrink the layout viewport for the on-screen keyboard, so the bottom
   of .mo-overlay stays UNDER it. With a full result list the sheet is tall
   enough to stick out above the keys; with an empty list it is only
   "title + field + two lines" (~220px) against a ~300px keyboard and disappears
   behind it entirely. Pinning the height it already has when results are there
   keeps the search field on the same y before, during and after typing — no
   measurement, and no jump the moment the keyboard opens (which is what a
   min-height tied to body.kb-open would give). .mo-body already scrolls
   (flex:1 + overflow-y:auto) so a long list still fits inside the sheet.
   Deliberately NOT applied to every sheet: the portion picker, the confirm
   dialog and the cook sheet are rightly as tall as their content. */
.mo-sheet--search { height: 85vh; }

.mo-grip {
  /* M3 drag handle: 32×4. Quiet by default, more prominent while the sheet is
     touched/hovered (opacity verloop) — signals the sheet is grab-to-dismiss. */
  width: 32px;
  height: 4px;
  background: var(--muted);
  opacity: 0.55;
  border-radius: var(--radius-sm);
  margin: var(--space-2) auto;
  flex: 0 0 auto;
  transition: opacity var(--dur-base) var(--ease-standard);
}
.mo-sheet:hover .mo-grip,
.mo-sheet:active .mo-grip { opacity: 0.85; }

/* NUTRITION-M3-WAVE-3 — reduced-motion: fade-only. The overlay's opacity fade
   still carries the appearance; the sheet itself does not slide (no transform),
   so there is no large translate motion. box-shadow stays (it is static). */
@media (prefers-reduced-motion: reduce) {
  .mo-sheet,
  .mo-sheet.mo--open { transform: none; transition: none; }
  .mo-grip { transition: none; }
}

.mo-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--space-5) var(--space-3);
  border-bottom: 1px solid var(--line);
}
.mo-title {
  font-size: var(--text-title);
  line-height: var(--lh-title);
  font-weight: 500;
  letter-spacing: 0.04em;
  flex: 1 1 auto;
  min-width: 0;
}

/* NUTRITION-LOG-FLOW-SINGLE-SHEET-NO-STACKING — the log flow is one sheet whose
   body swaps per stage. Two signifiers say where you are and how to leave:
   a back-chevron (one step back) and a three-dot stage indicator. */
.mo-back {
  background: none;
  border: 0;
  color: var(--muted);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  margin: calc(-1 * var(--space-2)) 0 calc(-1 * var(--space-2)) calc(-1 * var(--space-2));
}
.mo-back:hover { color: var(--ink); }
.mo-back[hidden] { display: none; }

.mo-steps {
  display: flex;
  gap: 6px;
  justify-content: center;
  padding: var(--space-3) 0 0;
  flex: 0 0 auto;
}
.mo-steps__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--line);
}
.mo-steps__dot.is-on {
  width: 18px;
  border-radius: 3px;
  background: var(--accent);
}
/* A stage the user stepped back from is kept in the DOM so its query, results
   and scroll position survive the return trip — [hidden] alone loses to the
   display rules on .mo-body and its variants. */
.mo-body[hidden] { display: none !important; }
.mo-close {
  background: none;
  border: 0;
  font-size: 24px;
  color: var(--muted);
  cursor: pointer;
  padding: 4px 8px;
  line-height: 1;
  /* WAVE-2 touch target: ≥44×44pt hitbox; negative margins keep the header
     height + right edge unchanged (HIG negative-margin pattern). */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  margin: calc(-1 * var(--space-2)) calc(-1 * var(--space-2)) calc(-1 * var(--space-2)) 0;
}
.mo-close:hover { color: var(--ink); }

.mo-error {
  margin: var(--space-3) var(--space-5) 0;
  padding: var(--space-2) var(--space-3);
  border-left: 2px solid var(--danger);
  color: var(--danger);
  font-size: 13px;
}

.mo-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: var(--space-4) var(--space-5) var(--space-6);
}

.mo-section-lbl {
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin: var(--space-4) 0 var(--space-2);
}
.mo-section-lbl:first-child { margin-top: 0; }

.mo-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-2);
}
.mo-tm {
  position: relative;
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-family: inherit;
}
.mo-tm:hover { border-color: var(--ink-2); }
.mo-tm__lbl {
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
}
.mo-tm__blurb {
  font-size: 11px;
  color: var(--muted);
  line-height: 1.4;
}
.mo-tm__pin {
  position: absolute;
  top: 6px;
  right: 8px;
  color: var(--muted);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}
.mo-tm__pin.on { color: var(--accent); }

.mo-config {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.mo-back {
  align-self: flex-start;
  background: transparent;
  border: 0;
  padding: var(--space-2) 0;
  font-size: 12px;
  color: var(--muted);
  cursor: pointer;
}
.mo-back:hover { color: var(--ink); }

.mo-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.mo-lbl {
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.mo-input {
  width: 100%;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--line);
  padding: 10px 0;
  font-size: 16px;
  color: var(--ink);
  outline: none;
  border-radius: 0;
  font-family: inherit;
}
.mo-input:focus { border-bottom-color: var(--ink); }
.mo-input--num { font-feature-settings: 'tnum' 1; }

.mo-lib {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 4px;
}
.mo-lib-chip {
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  padding: 4px 10px;
  font-size: 12px;
  color: var(--muted);
  cursor: pointer;
  font-family: inherit;
}
.mo-lib-chip:hover { color: var(--ink); border-color: var(--ink-2); }

.mo-config__box {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-3) var(--space-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
}

.mo-actions {
  display: flex;
  gap: var(--space-3);
  margin-top: var(--space-4);
}
.mo-actions .btn { flex: 1 1 0; }

/* ──────────────────────────────────────────────────────────
   Hero timer
   ────────────────────────────────────────────────────────── */
.hero-timer {
  position: fixed;
  top: 0; left: 0; right: 0;
  background: var(--ink);
  color: var(--bg);
  padding: var(--space-3) var(--space-5);
  z-index: 60;
  display: none;
  align-items: center;
  gap: var(--space-3);
  border-bottom: 1px solid var(--ink-2);
  font-feature-settings: 'tnum' 1;
}
.hero-timer.active { display: flex; }
.hero-timer.warn   { background: var(--accent); }
.hero-timer.alarm  { background: var(--ok); }
.hero-timer__phase {
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.7);
  flex: 0 0 auto;
}
.hero-timer__time {
  font-size: 28px;
  font-weight: 300;
  letter-spacing: -0.02em;
  margin-left: auto;
}
.hero-timer__round {
  font-size: 12px;
  color: rgba(255,255,255,0.7);
  margin-left: var(--space-3);
}
.hero-timer__btns {
  display: flex;
  gap: var(--space-2);
}
.hero-timer button {
  background: rgba(255,255,255,0.15);
  color: var(--bg);
  border: 0;
  width: 30px; height: 30px;
  border-radius: 50%;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
}
.hero-timer button:hover { background: rgba(255,255,255,0.3); }
body.hero-active .app-main { padding-top: 64px; }

/* ──────────────────────────────────────────────────────────
   Per-set rest countdown + ex play
   ────────────────────────────────────────────────────────── */
.sr-rest {
  margin-left: 4px;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  padding: 2px 8px;
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  font-family: inherit;
}
.sr-rest.on { background: var(--ink); color: var(--bg); border-color: var(--ink); }
.sr-rest:hover { color: var(--ink); }
.sr-rest.on:hover { color: var(--bg); }

.ex-play {
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  padding: 4px 10px;
  margin-left: var(--space-2);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  font-family: inherit;
}
.ex-play:hover { color: var(--ink); border-color: var(--ink-2); }
.ex-play.on    { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }

/* ──────── Update-available banner ──────── */
.update-banner {
  position: fixed;
  left: 50%;
  /* v0.9.339 — bump gap from space-3 → space-4 so the pill clearly floats
     above the tabbar (previously sat ~12px above and visually crowded the
     BRICKS/SETTINGS labels on a phone screenshot review). */
  bottom: calc(var(--tabbar-h) + var(--safe-bottom) + var(--space-4));
  transform: translate(-50%, calc(100% + var(--space-5)));
  background: var(--ink);
  color: var(--bg);
  /* v0.9.339 — symmetric padding. The bottom anchor above already clears the
     tabbar AND the home-indicator safe-area; doubling safe-bottom into the
     padding (v0.9.320) was inflating the pill height and looked off-balance. */
  padding: var(--space-2) var(--space-3) var(--space-2) var(--space-4);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  border-radius: var(--radius-pill);
  box-shadow: var(--elev-2);
  z-index: 200;
  font-size: 13px;
  letter-spacing: 0.02em;
  transition: transform var(--dur-base) var(--ease-standard);
  max-width: calc(100vw - var(--space-5) * 2);
}
.update-banner--show { transform: translate(-50%, 0); }
.update-banner__msg {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.update-banner__skip,
.update-banner__cta {
  background: transparent;
  border: 0;
  font: inherit;
  color: inherit;
  padding: 6px 10px;
  cursor: pointer;
  border-radius: var(--radius-pill);
}
/* v0.9.339 — opacity, not hardcoded white. The pill's text is var(--bg), which
   inverts with the theme (dark text in dark mode where the pill is white,
   light text in light mode where the pill is dark). The previous
   rgba(255,255,255,0.6) was invisible in dark mode (white-on-white). */
.update-banner__skip { opacity: 0.55; }
.update-banner__skip:hover { opacity: 0.9; }
.update-banner__cta {
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 500;
  padding: 6px 14px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 11px;
}
.update-banner__cta:hover { background: var(--accent-2); }
.update-banner__cta:disabled { opacity: 0.6; cursor: default; }
body.tab-strength .update-banner { /* keep visible above iframe + tab bar */
  bottom: calc(var(--tabbar-h) + var(--safe-bottom) + var(--space-4));
}

/* ───── Offline pill (non-blocking) ───── */
/* SYNC-MELDING-IS-EEN-PIL-OVERAL — moved to assets/css/offline-pill.css,
   @import'ed at the top of this file. The coach app links that same file
   directly (it never loads app.css), so there is exactly one copy. */

/* ───────── Toast ───────── */
.toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--tabbar-h) + var(--safe-bottom) + var(--space-4));
  transform: translateX(-50%);
  background: var(--ink);
  color: var(--bg);
  padding: var(--space-3) var(--space-5);
  font-size: 13px;
  letter-spacing: 0.04em;
  z-index: 100;
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-standard);
  pointer-events: none;
  max-width: 90vw;
}
.toast--show { opacity: 1; }
.toast--err { background: var(--danger); }

/* ───────── Utility ───────── */
.spinner {
  display: inline-block;
  width: 14px; height: 14px;
  border: 2px solid var(--line);
  border-top-color: var(--ink);
  border-radius: 50%;
  animation: spin var(--dur-loop) linear infinite;
  vertical-align: middle;
  margin-right: 8px;
}
.btn .spinner { border-color: rgba(255,255,255,0.3); border-top-color: var(--bg); }
@keyframes spin { to { transform: rotate(360deg); } }

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}

/* v0.6.0 #2a — Calendar progress bars (month grid + day sheet).
   Mirrors classes used in the strength iframe so visuals stay consistent.   */
.fz-cal-prog { height: 3px; background: var(--line); border-radius: var(--radius-sm); width: 80%; margin: 2px auto 0; overflow: hidden; }
.fz-cal-prog__fill { height: 100%; background: var(--muted); border-radius: var(--radius-sm); transition: width var(--dur-base) var(--ease-standard); }
.cal-cell.fz-cal-today .fz-cal-prog { background: rgba(255,255,255,0.25); }
.cal-cell.fz-cal-today .fz-cal-prog__fill { background: var(--bg); }
.fz-sheet-prog { height: 4px; background: var(--line); border-radius: var(--radius-sm); margin-top: 6px; overflow: hidden; width: 100%; }
.fz-sheet-prog__fill { height: 100%; background: var(--accent); border-radius: var(--radius-sm); transition: width var(--dur-base) var(--ease-standard); }
.fz-sheet-prog__lbl { font-size: 10px; color: var(--muted); font-weight: 600; margin-top: 4px; }
/* Resume action button (replaces Repeat for in_progress sessions). */
.fz-resume-btn { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.fz-resume-btn:hover { background: var(--accent-2); border-color: var(--accent-2); }

/* ──────────────────────────────────────────────────────────
   Nutrition tab (v0.9.174, Ship 1 — the logging loop).
   Native SPA tab; reuses the .mo-* bottom-sheet + .ui-dlg-* dialog CSS.
   ────────────────────────────────────────────────────────── */
.nut-mount { padding: var(--space-4) var(--space-4) calc(var(--tabbar-h) + var(--safe-bottom) + var(--space-5)); }

/* Date header */
.nut-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-4); }
/* VOEDING-NA-MIDDERNACHT-EET-JE-NOG-STEEDS-GISTEREN — de uitlegregel boven de
   datumstrip tussen 00:00 en 05:00. Secundaire ink, geen kader, geen kleurcode:
   dit is een mededeling, geen waarschuwing. */
.nut-dayturn {
  font-size: 12px;
  line-height: 1.4;
  color: var(--ink-2);
  text-align: center;
  margin-bottom: var(--space-2);
}
/* VOEDING-EEN-VERGETEN-DAG-IS-EEN-TIK-WEG — 36px was under the 44px touch
   guideline and these two sit on the extreme edges of a 390px screen. The
   glyph is unchanged; only the tappable area grows. */
.nut-nav {
  background: none; border: 1px solid var(--line); color: var(--ink-2);
  width: 44px; height: 44px; border-radius: 50%; font-size: 20px; line-height: 1;
  cursor: pointer; flex: 0 0 auto;
}
.nut-nav:hover { border-color: var(--ink-2); color: var(--ink); }
/* The one-tap way back, present ONLY when you are not on today. */
.nut-today {
  background: none; border: 1px solid var(--line); color: var(--ink);
  height: 44px; padding: 0 var(--space-3); border-radius: 999px;
  font-size: var(--text-body-sm); cursor: pointer; flex: 0 0 auto;
  margin-left: var(--space-2);
}
.nut-today:hover { border-color: var(--ink-2); }
.nut-date {
  background: none; border: 0; color: var(--ink); font-size: 17px; font-weight: 500;
  cursor: pointer; letter-spacing: 0.01em; padding: 6px 10px; border-radius: var(--radius-sm);
}
.nut-date.is-today { color: var(--accent); }
/* You must be able to SEE that you are not on today. Outline, not colour — a
   back-dated day is a catch-up, never an error, so no red and no alarm. */
.nut-date.is-otherday { border: 1px solid var(--line); color: var(--ink); }
.nut-date__caret { color: var(--muted); font-size: 12px; }
/* v0.9.179 — transparent native date input overlaying the label; a direct tap
   opens the iOS picker (jump to any day). opacity:0 still receives pointer
   events, unlike display:none/visibility:hidden. */
.nut-datewrap { position: relative; display: inline-flex; }
.nut-dateinput {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; border: 0; margin: 0; padding: 0; cursor: pointer;
  -webkit-appearance: none; appearance: none; background: none;
}

.nut-loading { text-align: center; color: var(--muted); padding: var(--space-7) 0; }

/* SASSU-DESIGN-EMPTY-STATES-ACTIONABLE — the load-failure box states the recovery
   AND offers it. The button is the only accent element in an otherwise muted
   dashed placeholder, so the eye lands on the one thing worth pressing. */
.nut-loaderr__txt { margin: 0 0 var(--space-4); }
.nut-loaderr__retry {
  display: inline-block; min-height: 44px; padding: 0 var(--space-6);
  background: var(--accent); color: var(--on-accent);
  border: 0; border-radius: var(--radius-md);
  font-size: 15px; font-weight: 600; cursor: pointer;
}
.nut-loaderr__retry:hover { background: var(--accent-2); }
.nut-loaderr__retry:disabled { opacity: .5; cursor: default; }

/* DIARY-LOG-BUTTONS-RELOCATE-WATER-NUTRITION-STEPS-CARDIO (v0.9.473) — Water
   quick-log cell relocated from Diary onto Nutrition. The cell chrome is the
   .cal-metric--empty/--filled pair reused from the Diary surface; this wrap
   only owns the surrounding margin so the cell sits between the date header
   and the North Star without crowding either. */
/* NUTRITION-HYDRATION-CARD-AND-INTAKE-WATER-GOAL — dedicated water-tracking
   card under the macros North Star. Card chrome mirrors .nut-ns but carries the
   semantic blue --hydration accent (water ≠ macros amber). */
/* NUTRITION-M3-WAVE-4 — day-view cards share one outlined-card style:
   radius var(--radius-lg), padding var(--space-4)/var(--space-5), 1px --line. */
.hydration-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5); margin-bottom: var(--space-4);
  display: flex; flex-direction: column; gap: var(--space-3);
}
.hydration-card > header {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3);
}
.hydration-card h3 { margin: 0; font-size: var(--text-title); line-height: var(--lh-title); font-weight: 600; color: var(--ink); }
.hydration-amount { font-size: var(--text-body-sm); font-weight: 600; color: var(--hydration); white-space: nowrap; }
.hydration-bar {
  width: 100%; height: 8px; background: var(--surface-muted);
  border-radius: var(--radius-pill); overflow: hidden;
}
.hydration-bar__fill {
  height: 100%; width: 0; background: var(--hydration);
  border-radius: var(--radius-pill); transition: width var(--dur-base) var(--ease-standard);
}
.hydration-actions { display: flex; gap: var(--space-2); }
.hydration-btn {
  flex: 1; padding: var(--space-2) var(--space-3);
  background: var(--surface-muted); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  font-size: 0.85rem; font-weight: 600; cursor: pointer;
}
.hydration-btn:hover { border-color: var(--hydration); }
/* WATER-TERUGDRAAIEN-OVERAL — the three quick-add buttons became one button
   that opens the shared vessel sheet; alone in the row it takes the full width
   instead of sitting as a lonely stub on the left. */
.hydration-btn--log { flex: 1; }
/* Intake water-goal preset chips reuse .onboarding__radio; this just keeps the
   row wrapping cleanly when the 5 chips don't fit one line. */
.water-goal-presets { flex-wrap: wrap; }

/* VOEDING-TREDE-VINKJES-PER-VOEDSELGROEP — "Added today" card. Same shell as
   .hydration-card so the two read as siblings on the day. A ticked row fills
   with --accent and takes --on-accent for its text: never a hardcoded white,
   because the Nacht preset's accent is light enough to need dark ink. */
.fg-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5); margin-bottom: var(--space-4);
  display: flex; flex-direction: column; gap: var(--space-3);
}
.fg-card > header {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3);
}
.fg-card h3 { margin: 0; font-size: var(--text-title); line-height: var(--lh-title); font-weight: 600; color: var(--ink); }
.fg-count { font-size: var(--text-body-sm); font-weight: 600; color: var(--accent); white-space: nowrap; }
.fg-sub { margin: 0; font-size: var(--text-body-sm); line-height: 1.4; color: var(--ink-2); }
.fg-rows { display: flex; flex-direction: column; gap: var(--space-2); }
.fg-row {
  display: flex; align-items: center; gap: var(--space-3); width: 100%;
  padding: var(--space-3); text-align: left; cursor: pointer;
  background: var(--surface-muted); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  font-size: 0.95rem; font-weight: 600;
  transition: background var(--dur-base) var(--ease-standard), border-color var(--dur-base) var(--ease-standard);
}
.fg-row:hover { border-color: var(--accent); }
.fg-box {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; flex: none;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); color: transparent;
}
.fg-box .nut-ic svg { width: 16px; height: 16px; display: block; }
.fg-row.is-on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.fg-row.is-on .fg-box { background: transparent; border-color: var(--on-accent); color: var(--on-accent); }
.fg-label { flex: 1; }

/* VOEDING-ZACHTE-START-VEERTIEN-DAGEN — inline, never an overlay: the message
   is deliberately not urgent, and a scrim would say the opposite. */
.nut-softstart {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5); margin-bottom: var(--space-4);
  display: flex; flex-direction: column; gap: var(--space-3);
}
.nut-softstart__lead {
  margin: 0; font-size: var(--text-title); line-height: var(--lh-title);
  font-weight: 700; color: var(--ink);
}
.nut-softstart__sub { margin: 0; font-size: var(--text-body-sm); line-height: 1.4; color: var(--ink-2); }
.nut-softstart__q { margin: var(--space-2) 0 0; font-size: 0.95rem; font-weight: 600; color: var(--ink); }
/* VOEDING-DE-ZACHTE-START-VRAAGT-IETS-DAT-JE-SNAPT — de hulpregel hoort bij de
   vraag erboven, dus hij krijgt de kaart-gap niet: negatieve marge trekt hem
   tegen de vraag aan, zodat het één blok leest en niet twee losse zinnen. */
.nut-softstart__hint {
  margin: calc(var(--space-3) * -1 + 2px) 0 0;
  font-size: var(--text-body-sm); line-height: 1.4; color: var(--ink-2);
}
.nut-softstart__where { margin: calc(var(--space-3) * -1 + 2px) 0 0; font-size: var(--text-body-sm); color: var(--ink-2); }
.nut-softstart__chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.nut-softstart__chip {
  padding: var(--space-2) var(--space-3); cursor: pointer;
  background: var(--surface-muted); color: var(--ink);
  border: 1px solid var(--line); border-radius: 999px;
  font-size: var(--text-body-sm); font-weight: 600;
  transition: background var(--dur-base) var(--ease-standard), border-color var(--dur-base) var(--ease-standard);
}
.nut-softstart__chip:hover { border-color: var(--accent); }
.nut-softstart__chip.is-on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.nut-softstart__free {
  width: 100%; padding: var(--space-3);
  background: var(--surface-muted); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  font-size: 0.95rem;
}
.nut-softstart__free::placeholder { color: var(--ink-2); opacity: 1; }
.nut-softstart__ok {
  align-self: flex-start; padding: var(--space-3) var(--space-5); cursor: pointer;
  background: var(--accent); color: var(--on-accent);
  border: 1px solid var(--accent); border-radius: var(--radius-md);
  font-size: 0.95rem; font-weight: 600;
}
.nut-softstart__ok[disabled] { opacity: 0.6; cursor: default; }
.nut-softstart__note { margin: 0; font-size: var(--text-body-sm); color: var(--ink-2); }

/* North Star */
.nut-ns {
  display: block; width: 100%; text-align: left; cursor: pointer;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5); margin-bottom: var(--space-4);
  /* SEQUENCER-V1E (v0.9.501) — positioning ancestor for the active-plan
     indicator that anchors to the card's top-right corner. */
  position: relative;
}
.nut-ns:hover { border-color: var(--ink-2); }
.nut-ns--empty { text-align: center; padding: var(--space-5); }
.nut-ns__set { color: var(--accent); font-size: 15px; font-weight: 500; }
.nut-ns__hint { color: var(--muted); font-size: var(--text-body-sm); margin-top: 4px; }
/* Coach-set goals (v0.9.189): "Set by your coach" badge + coach-only Lock toggle. */
.nut-ns__by { color: var(--accent); font-size: 12px; font-weight: 600; margin-bottom: var(--space-3); }
/* VOEDINGSDOELEN-KOMEN-UIT-HET-WEEKRASTER — herkomst van het dagdoel: één
   rustige regel, geen badge en geen zwevend element. De eigen-doel-staart is
   nog stiller dan de rest, want hij vertelt alleen wat er overschreven is. */
.nut-ns__plan {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px;
  color: var(--muted); font-size: 12px; margin-bottom: var(--space-3);
}
.nut-ns__plan-wk { font-weight: 600; color: var(--ink); }
.nut-ns__plan-own { opacity: 0.75; }
.nut-ns__plan-own::before { content: '·'; margin-right: 6px; }
.nut-lockrow { display: flex; align-items: center; gap: 8px; margin: var(--space-3) 0; cursor: pointer; }
.nut-lockrow__chk { accent-color: var(--accent); width: 18px; height: 18px; flex: 0 0 auto; }
.nut-lockrow__lbl { font-size: 13px; color: var(--ink); }
.nut-ns__cal { margin-bottom: var(--space-3); }
.nut-ns__cal:last-child { margin-bottom: 0; }
.nut-ns__calrow { display: flex; align-items: baseline; gap: 6px; }
/* NUTRITION-M3-WAVE-4 — type roles: kcal main number = headline; goal/remainder
   detail = body-sm. line-height:1 kept on the number for a tight numeric cap. */
.nut-ns__calnum { font-size: var(--text-headline); font-weight: 600; color: var(--ink); line-height: 1; }
.nut-ns__calgoal { font-size: var(--text-body-sm); color: var(--muted); }
.nut-ns__rem { font-size: var(--text-body-sm); color: var(--muted); margin-top: 4px; }
/* Ship A / rec #2 — over-target is calm + neutral, NEVER red (ED-safety + Feliz
   no-shame policy). The old `.is-over { color: var(--danger) }` shame state and
   the red bar fills below were removed wholesale. */
.nut-ns__rem--met { color: var(--ink-2); }
/* Gentle low-intake floor hint — supportive, accent-toned, never alarming. */
.nut-ns__floor { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--accent); margin-top: 6px; }
.nut-ns__floor .nut-ic { width: 14px; height: 14px; flex: 0 0 auto; }
.nut-ns__macros { display: flex; flex-direction: column; gap: var(--space-3); margin-top: var(--space-2); }

/* NUTRITION-DAG-TOONT-DE-BALANS — in / out / net under the North Star. Three
   equal cells so the numbers line up as a comparison, not a sentence. The net
   cell is never coloured by sign: a deficit is a fact, not an alarm. */
.nut-bal {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: var(--space-2);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: var(--space-3) var(--space-4); margin-bottom: var(--space-4);
}
.nut-bal__cell { min-width: 0; text-align: left; }
.nut-bal__lbl { font-size: var(--text-label); color: var(--muted); }
.nut-bal__val { display: flex; align-items: baseline; gap: 3px; margin-top: 2px; }
.nut-bal__num { font-size: var(--text-title); font-weight: 600; color: var(--ink); line-height: 1; font-variant-numeric: tabular-nums; }
.nut-bal__unit { font-size: var(--text-body-sm); color: var(--muted); }
.nut-bal__src { font-size: 11px; color: var(--muted); margin-top: 2px; min-height: 13px; }
/* VOEDING-JE-GEEFT-JE-ACTIEVE-VERBRANDING-ZELF-DOOR — de invoer-lade achter de
   Verbranding-cel. De cel zelf krijgt GEEN eigen opmaak: hij moet er precies zo
   uitzien als de twee andere cellen, alleen met role=button erop. */
.nut-burn__helprow { display: flex; justify-content: flex-end; margin-bottom: var(--space-2); }
.nut-burn__help {
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--bg); border: 1px solid var(--line); color: var(--muted);
  font-size: 14px; font-weight: 700; line-height: 1; cursor: pointer;
}
.nut-burn__helptext, .nut-burn__note {
  font-size: var(--text-body-sm); color: var(--muted); line-height: 1.5;
  margin: 0 0 var(--space-3);
}
/* VOEDING-JE-RUSTVERBRANDING-MAG-JE-ZELF-INVULLEN — de lade draagt nu TWEE
   invoervelden, dus ze hebben allebei een naam nodig; zonder kop zouden twee
   identieke kcal-vakjes boven elkaar staan en is niet te zien welke welke is.
   De hint onder het rustveld is stiller dan het label maar niet stiller dan de
   somregel: hij vertelt dat dit getal ALLE dagen raakt, en dat mag niemand
   missen. */
.nut-burn__label {
  display: block; font-size: 11px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--section-label); margin: var(--space-3) 0 4px;
}
.nut-burn__resthint {
  font-size: var(--text-body-sm); color: var(--muted); line-height: 1.5;
  margin: 4px 0 0;
}
/* VOEDING-HET-VERBRANDINGSBLAD-ZEGT-WAAR-HET-GETAL-VANDAAN-KOMT — welk SOORT
   dag dit is, één regel boven de velden. Donkerder dan de hint eronder en
   lichter dan een kop: het is de aanleiding van de somregel, geen titel van de
   lade. Alleen tokens, dus licht en donker komen er gratis bij. */
.nut-burn__daytype {
  font-size: var(--text-body-sm); color: var(--ink-2); line-height: 1.5;
  font-weight: 600; margin: 0 0 var(--space-2);
}
.nut-burn__sum {
  margin-top: var(--space-3); font-size: var(--text-body-sm); color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* CARDIO-TELT-MEE-IN-JE-DAG — de sessies onder de somregel. Geen kaart en geen
   nieuw onderdeel: een kale rij per sessie in dezelfde stille kleur als de som,
   zodat de lade één blok blijft in plaats van twee. */
.nut-burn__sessions { list-style: none; margin: var(--space-2) 0 0; padding: 0; }
.nut-burn__session {
  display: flex; align-items: baseline; gap: var(--space-2);
  padding: 6px 0; border-top: 1px solid var(--line);
  font-size: var(--text-body-sm); color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.nut-burn__session-name { flex: 1 1 auto; color: var(--ink); }
.nut-burn__session-dur { flex: 0 0 auto; }
.nut-burn__session-kcal { flex: 0 0 auto; min-width: 68px; text-align: right; }
.nut-burn__done { width: 100%; margin-top: var(--space-4); }
.nut-burn__undo {
  display: block; width: 100%; margin-top: var(--space-2);
  background: none; border: 0; color: var(--muted);
  font-size: var(--text-body-sm); padding: var(--space-2); cursor: pointer;
}
/* Desktop hero: the ring zone is a fixed 180x180 positioning box, so the
   balance hangs below it out of flow rather than stretching that box and
   dragging the absolutely-centred kcal-left number off the ring's centre. */
.nut-bal--compact {
  position: absolute; top: 100%; left: -12px; right: -12px;
  margin-top: var(--space-3); margin-bottom: 0;
  padding: var(--space-2) var(--space-3);
}

.nut-mac__top { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 4px; }
.nut-mac__lbl { font-size: var(--text-body); color: var(--ink-2); }
/* COLOR-3 — the gram readouts sitting directly above the macro bars were the
   one metric display on the Nutrition overview still on proportional figures,
   so the three values jittered against each other as the day's numbers moved. */
.nut-mac__val { font-size: var(--text-body-sm); color: var(--muted); font-variant-numeric: tabular-nums; }

/* NUTRITION-M3-WAVE-4 — macro progress bars thickened 6→8px with full pill
   rounding for glanceability; track stays var(--line), fill colour per macro
   token unchanged. */
.nut-bar { height: 8px; background: var(--line); border-radius: var(--radius-pill); overflow: hidden; }
.nut-bar__fill { height: 100%; background: var(--accent); border-radius: var(--radius-pill); transition: width var(--dur-base) var(--ease-standard); }
/* COLOR-3 — protein moved off the generic --ok onto the macro triad's own
   green. --ok is the app-wide success colour at C 0.21; sitting it next to
   --mac-c and --mac-f made the three bars read as two quiet ones and an alarm. */
.nut-mac--p .nut-bar__fill { background: var(--mac-p); }
.nut-mac--c .nut-bar__fill { background: var(--mac-c); }
.nut-mac--f .nut-bar__fill { background: var(--mac-f); }

/* Ship B (rec #3) — North Star reframe. Protein HERO leads (the one celebrated
   metric); fiber a calm secondary indicator; calories/carbs/fat a quiet band
   below a divider. No red anywhere — Ship A's no-shame invariant carried through.
   Protein green + fiber teal are fixed (like the macro fills above) with a
   slightly brighter dark-theme variant for contrast. */
.nut-ns__hero { margin-bottom: var(--space-4); }
.nut-ns__hero-top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
/* NUTRITION-PROTEIN-BAR-MINIMALIST-REDESIGN-V2 — 6px time-axis bar; pills float
   above as lightweight neutral rectangles; colour belongs to the accent fill, not
   the data points. Token note: the brief names var(--text)/var(--surface-card); this
   theme defines neither, so --text→var(--ink) and --surface-2→var(--surface-2,
   var(--line)) (the established fallback). */
.nut-ns__hero-label { font-size: 11px; font-family: 'Geist Sans', sans-serif; text-transform: uppercase; color: var(--ink); opacity: 0.5; letter-spacing: 0.3px; }
.nut-ns__hero-num { display: flex; align-items: baseline; gap: 6px; }
/* COLOR-3 — the 32px protein total is the single largest metric on the
   Nutrition overview and was still proportional, so it visibly reflowed on
   every gram logged; at this size the shift is a wobble, not a nudge. */
.nut-ns__hero-total { font-size: 32px; font-family: 'Geist Sans', sans-serif; color: var(--accent); line-height: 1; font-variant-numeric: tabular-nums; }
.nut-ns__hero-goal { font-size: 14px; font-family: 'Geist Sans', sans-serif; color: var(--ink); opacity: 0.4; }
/* Bar: the 32px top margin reserves the floating pill zone (18px pill + 6px gap)
   plus the brief's 8px gap below the title row. */
/* NUTRITION-M3-WAVE-4 — protein time-axis bar thickened 6→8px, pill-rounded;
   the now-line height term tracks the new 8px bar so it still reaches bar-bottom. */
/* VOEDING-KIES-WAT-BOVENAAN-STAAT — the calorie lead's bar carries the nutrition
   token (not --accent), so a card led by calories still reads as the Voeding
   surface. Token only; the night finish follows automatically. */
.nut-ns__calbar { margin-top: 12px; }
.nut-ns__calbar .nut-bar__fill { background: var(--nutrition); }
/* Protein demoted into the band keeps its timeline; the pills float ~30px above
   the track, so the row needs headroom of its own or they collide with the
   calorie hero sitting above it. */
.nut-ns__bandpro { margin-top: 38px; }
.nut-ns__hero-bar { position: relative; height: 8px; margin-top: 32px; }
.nut-ns__hero-bar__track { position: absolute; left: 0; top: 0; right: 0; bottom: 0; background: var(--surface-2, var(--line)); opacity: 0.6; border-radius: var(--radius-pill); }
.nut-ns__hero-bar__fill { position: absolute; left: 0; top: 0; bottom: 0; background: var(--accent); border-radius: var(--radius-pill); pointer-events: none; }
.nut-ns__hero-bar--complete .nut-ns__hero-bar__track { opacity: 1; }
/* NOW hairline — 1px accent @30%, spans pill-top→bar-bottom, behind the pills. */
.nut-ns__now-line { position: absolute; width: 1px; top: calc(-18px - 6px); height: calc(18px + 6px + 8px);
  background: var(--accent); opacity: 0.3; transform: translateX(-50%); pointer-events: none; z-index: 1; }
/* Pills — 28×18px neutral rectangles floating 6px above the bar. min-width keeps
   the nominal 28px terminal; content sizing lets the single-line label fit. */
.nut-ns__pill { position: absolute; min-width: 28px; height: 18px; box-sizing: border-box; padding: 0 5px;
  bottom: calc(100% + 6px); left: 0; transform: translateX(-50%);
  display: flex; align-items: center; justify-content: center;
  background: var(--bg); border: 1px solid var(--surface-2, var(--line)); border-radius: var(--radius-sm);
  cursor: pointer; z-index: 2; }
.nut-ns__pill::before { content: ''; position: absolute; inset: -12px; }
.nut-ns__pill--past { border-color: var(--accent); }
.nut-ns__pill--tap { border-color: var(--accent); }
.nut-ns__pill-label { font-family: 'Geist Sans', sans-serif; font-size: 11px; letter-spacing: 0.3px; color: var(--ink); white-space: nowrap; }
.nut-ns__pill-time { opacity: 0.9; }
.nut-ns__pill-grams { opacity: 1; }
.nut-ns__pill-super { position: absolute; top: -6px; right: -4px; font-size: 9px; color: var(--accent); font-family: 'Geist Sans', sans-serif; line-height: 1; }
/* Microcopy — 8px below the bar. */
.nut-ns__hero-micro { margin-top: 8px; font-size: 11px; font-family: 'Geist Sans', sans-serif; color: var(--ink); opacity: 0.45; display: flex; align-items: center; gap: 4px; }
.nut-ns__hero-micro--complete { color: var(--accent); opacity: 0.7; }
.nut-ns__hero-micro .nut-ic { width: 12px; height: 12px; flex: 0 0 auto; }
/* cluster sheet — opened by tapping a '+N' badge on a collapsed cluster */
.nut-clus-row { display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 12px 4px; background: transparent; border: 0; border-bottom: 1px solid var(--line);
  color: var(--ink); font-size: 14px; text-align: left; cursor: pointer; }
.nut-clus-row__t { font-variant-numeric: tabular-nums; color: var(--muted); min-width: 46px; }
.nut-clus-row__n { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nut-clus-row__g { font-weight: 600; color: var(--accent); }

.nut-ns__fiber { display: flex; align-items: center; gap: 10px; margin-bottom: var(--space-4); }
.nut-ns__fiber-main { flex: 1; min-width: 0; }
.nut-ns__fiber-top { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 4px; }
.nut-ns__fiber-lbl { font-size: var(--text-body); color: var(--ink-2); }
.nut-ns__fiber-val { font-size: var(--text-body-sm); color: var(--muted); }
.nut-ns__fiber-bar { height: 8px; background: var(--line); border-radius: var(--radius-pill); overflow: hidden; }
.nut-ns__fiber-bar > i { display: block; height: 100%; border-radius: var(--radius-pill); background: var(--fiber); transition: width var(--dur-base) var(--ease-standard); }

.nut-ns__band { border-top: 1px solid var(--line); padding-top: var(--space-3); margin-top: 2px;
                display: flex; flex-direction: column; gap: 10px; }
.nut-ns__band-cal { display: flex; align-items: baseline; justify-content: space-between; }
.nut-ns__band-cal-l { font-size: var(--text-body-sm); color: var(--ink-2); }
.nut-ns__band-cal-r { font-size: var(--text-body-sm); color: var(--muted); }
.nut-ns__band-cal-r b { color: var(--ink); font-weight: 600; }

/* "Your usual" — recents + favorites one-tap tiles above search (Ship A / rec #1). */
.nut-usual { margin-bottom: var(--space-2); }
.nut-usual__hd { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: var(--space-2); }
.nut-usual__lbl { font-size: 12px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-2); }
.nut-usual__hint { font-size: 11px; color: var(--muted); }
.nut-usual__list { display: flex; flex-direction: column; gap: var(--space-2); }
.nut-utile {
  display: flex; align-items: center; gap: var(--space-3); width: 100%; text-align: left;
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 10px var(--space-3); cursor: pointer;
}
.nut-utile:active { transform: scale(.99); }
.nut-utile__main { flex: 1; min-width: 0; }
.nut-utile__name { display: flex; align-items: center; gap: 6px; font-size: 14px; font-weight: 500; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nut-utile__name > span { overflow: hidden; text-overflow: ellipsis; }
.nut-utile__star { color: var(--accent); width: 14px; height: 14px; flex: 0 0 auto; }
.nut-utile__sub { font-size: 11px; color: var(--muted); margin-top: 2px; }
/* Ship G (rec #10) — time-of-day legibility: a faint accent tag on tiles that
   matched the current slot, and an accent header hint ("for your mornings"). */
.nut-utile__slot { color: var(--accent); font-weight: 600; }
.nut-usual__hint--slot { color: var(--accent); }
/* v0.9.257 — persoon-icoon next to user-uploaded foods. Muted, never accent —
   it sits beside fav stars and product thumbs and must not compete with them. */
.nut-user { display: inline-flex; align-items: center; color: var(--muted); margin-left: 6px; flex: 0 0 auto; }
.nut-user .nut-ic { width: 12px; height: 12px; }
.nut-user .nut-ic svg { width: 12px; height: 12px; }
.nut-utile__add {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%;
  background: var(--accent); color: var(--on-accent);
  display: flex; align-items: center; justify-content: center;
}
.nut-utile__add .nut-ic { width: 16px; height: 16px; }
/* NUTRITION-PIN-AND-LAST-USED-TOP — corner "pin to top" toggle. Sits in the
   tile's top-right corner (align-self, so it never overlaps the add circle) and
   above the tile's ::after ripple, which would otherwise eat the tap. Subtle by
   default; the pinned state fills and takes the accent — tokens only. */
.nut-utile__pin {
  position: relative; z-index: 1; flex: 0 0 auto; align-self: flex-start;
  width: 26px; height: 26px; margin: -4px -2px 0 0;
  display: flex; align-items: center; justify-content: center;
  color: var(--muted); opacity: .55; cursor: pointer; border-radius: 50%;
}
.nut-utile__pin .nut-ic { width: 14px; height: 14px; }
.nut-utile__pin .nut-ic svg { width: 14px; height: 14px; }
.nut-utile__pin.is-on { color: var(--accent); opacity: 1; }
.nut-utile__pin:active { transform: scale(.92); }
.nut-usual__or { display: flex; align-items: center; gap: 10px; margin: var(--space-4) 0 var(--space-3); }
.nut-usual__or::before, .nut-usual__or::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.nut-usual__or-lbl { font-size: 11px; color: var(--muted); }

/* Forgiving consistency band (Ship E, rec #7) — "logged X of the last 7 days",
   an ADDITIVE count a single gap can't zero. NOT a streak; never red. */
/* VOEDING-DE-STIPPEN-WORDEN-DAGEN-DIE-JE-KUNT-SWIPEN — the seven dots became
   seven day numbers in a goal ring, and the row swipes back a week at a time.
   Same card, same place, same tap target: .nut-consist__hit keeps being the
   measure (>=44px, edge-to-edge so neighbours can never overlap). */
.nut-consist {
  display: flex; flex-direction: column; align-items: stretch; gap: var(--space-2);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: var(--space-3) var(--space-5); margin-bottom: var(--space-4);
}
/* LAADTIJD-JE-LANDT-WAAR-JE-GEBLEVEN-WAS — the band's data now lands one render
   AFTER the day itself, so renderConsistencySkeleton() holds its exact box open
   in the meantime. visibility (not display/opacity) is the point: the box keeps
   its full height so nothing below it moves, while the placeholder is never
   briefly readable as a real "you logged nothing" band. */
.nut-consist--pending { visibility: hidden; }
/* The swipe surface. Native scroll + scroll-snap, so the row follows the finger
   with the platform's own momentum and rubber-banding; no touch handler exists
   to fight pull-to-refresh or the per-row action tray. */
.nut-consist__pages {
  display: flex; overflow-x: auto; overflow-y: hidden;
  /* auto, NOT smooth: the only programmatic scroll is the jump to the viewed
     day's page on first paint, and a card that scrolls itself while you are
     reading it is a card that moved. The swipe itself is the browser's. */
  scroll-snap-type: x mandatory; scroll-behavior: auto;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}
.nut-consist__pages::-webkit-scrollbar { display: none; }
.nut-consist__page {
  flex: 0 0 100%; scroll-snap-align: start;
  display: flex; align-items: flex-end; justify-content: space-between; gap: 0;
}
/* The load-older sentinel: zero visual weight, but it must have a box for the
   IntersectionObserver to ever see it. */
.nut-consist__more { flex: 0 0 1px; align-self: stretch; }
/* flex 1 1 0 with a 40px floor: 44.3px each at 390px, and on a 360px screen it
   shrinks to 40px rather than overflowing. Areas are edge-to-edge by
   construction, so they can never overlap. */
.nut-consist__hit {
  flex: 1 1 0; min-width: 40px; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
}
span.nut-consist__hit { cursor: default; }
.nut-consist__day {
  display: inline-flex; flex-direction: column; align-items: center; gap: 2px;
  position: relative;
}
.nut-consist__wd {
  font-size: var(--text-label); line-height: 1; color: var(--muted);
  text-transform: lowercase;
}
.nut-consist__ring {
  position: relative; width: 30px; height: 30px;
  display: inline-flex; align-items: center; justify-content: center;
}
/* Explicit pixels, not 100%: a percentage-sized inline SVG in a nested span
   inherits an unconstrained intrinsic size (lint rule m). */
.nut-consist__svg { position: absolute; top: 0; left: 0; width: 30px; height: 30px; }
.nut-consist__track { stroke: var(--line); }
/* Fill is ALWAYS the accent — never red, never green. Eating too little is not
   a mistake, and a colour that judges turns a diary into a report card. */
.nut-consist__arc { stroke: var(--accent); }
.nut-consist__num {
  position: relative; font-size: var(--text-body-sm); line-height: 1;
  font-weight: 600; color: var(--ink-2);
}
.nut-consist__day.is-on .nut-consist__num { color: var(--ink); }
/* The day you are reading: the number sits ON a disc, so it reads as "here"
   from across the card without a second colour entering the row. */
.nut-consist__day.is-current .nut-consist__num {
  width: 24px; height: 24px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--ink); color: var(--bg);
}
/* You are on another day — today keeps a mini-marker so the week stays legible. */
.nut-consist__now {
  width: 4px; height: 4px; border-radius: 50%; background: var(--ink-2);
  margin-top: 1px;
}
/* DE-WEEKSTRIP-ZEGT-WELKE-WEEK-JE-ZIET — one row of two: the sentence left, the
   date range of the visible page right. `wrap` is what keeps the promise on a
   narrow screen: the RANGE drops to a second line, the sentence is never
   squeezed (it is the half that carries the meaning). */
.nut-consist__txt {
  font-size: var(--text-body-sm); color: var(--ink-2); line-height: 1.3;
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: 2px 12px;
}
.nut-consist__sent { min-width: 0; }
.nut-consist__range { margin-left: auto; color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
.nut-consist__txt b { color: var(--ink); font-weight: 700; }
.nut-consist--zero .nut-consist__txt { color: var(--muted); }
.nut-consist--zero .nut-consist__txt b { color: var(--ink-2); }
/* Reduced motion changes nothing here — there is no smooth scroll to switch off
   — but the snap must survive it explicitly: without snapping the row would rest
   half-way between two weeks, which is worse than any animation. */
@media (prefers-reduced-motion: reduce) {
  .nut-consist__pages { scroll-behavior: auto; scroll-snap-type: x mandatory; }
}

/* Warm re-entry (Ship E, rec #7) — empty TODAY after a gap. Gentle, never guilt. */
.nut-reentry {
  display: flex; gap: 10px; align-items: flex-start; text-align: left;
  background: var(--accent-light); border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: 13px 14px; margin-bottom: var(--space-4);
}
.nut-reentry__ic { flex: 0 0 auto; color: var(--accent); margin-top: 1px; }
.nut-reentry__ic svg { width: 20px; height: 20px; display: block; }
.nut-reentry__txt { font-size: 13.5px; color: var(--ink); line-height: 1.45; }
.nut-reentry__txt b { font-weight: 700; }
.nut-reentry__txt span { color: var(--ink-2); }

/* Sub-second reward tick (Ship E, rec #7) — fires on a successful log. No emoji
   (the `check` glyph). Pops + fades centered; static fade under reduced-motion. */
.nut-tick {
  position: fixed; inset: 0; z-index: 60; display: flex; align-items: center;
  justify-content: center; pointer-events: none;
}
.nut-tick__badge {
  width: 64px; height: 64px; border-radius: 50%; background: var(--ok);
  display: flex; align-items: center; justify-content: center;
  box-shadow: var(--elev-2);
  animation: nutTickPop var(--dur-slow) var(--ease-standard) both;
}
.nut-tick__badge svg { width: 34px; height: 34px; color: var(--on-accent); }
@keyframes nutTickPop {
  0%   { transform: scale(0.4); opacity: 0; }
  28%  { transform: scale(1);   opacity: 1; }
  72%  { transform: scale(1);   opacity: 1; }
  100% { transform: scale(0.96); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  /* 0.6s stays: this is an opacity-only dwell so the tick is readable without
     motion. Only the curve joins the app signature. */
  .nut-tick__badge { animation: nutTickFade 0.6s var(--ease-standard) both; }
  @keyframes nutTickFade { 0% { opacity: 0; } 20% { opacity: 1; } 80% { opacity: 1; } 100% { opacity: 0; } }
}

/* Meals */
.nut-empty { text-align: center; color: var(--muted); padding: var(--space-5) 0; }
/* Desktop-only since NUTRITION-LIQUID-GLASS-PLATE — the phone tree replaced this
   hero with the pinned plate wallpaper (.nut-plate-wrap, end of file). */
.nut-empty__hero { display: flex; justify-content: center; margin: var(--space-5) 0 var(--space-4); }
/* LAADTIJD-VOEDING-OPENT-ZONDER-OMWEG — het bord zit sinds deze fase in een
   <picture> (AVIF/WebP met de PNG als vangnet). `display: contents` laat die
   wikkel uit de lay-out verdwijnen, zodat de <img> precies het flex-kind blijft
   dat hij hiervoor was: zonder deze regel wordt de PICTURE het flex-kind en
   rekenen `max-width: 95%` en `max-height: 200px` tegen de verkeerde doos. */
.nut-empty__hero > picture { display: contents; }
.nut-empty__hero-img { max-width: 95%; max-height: 200px; width: auto; height: auto; opacity: 0.95; border-radius: var(--radius-md); }
.nut-empty__title { font-size: var(--text-title); line-height: var(--lh-title); font-weight: 600; color: var(--ink); margin: 0 0 var(--space-1); }
.nut-empty__txt { font-size: var(--text-body-sm); line-height: var(--lh-body-sm); color: var(--muted); }
/* Copy-a-previous-day (Ship 2, v0.9.178) — prominent in the empty state, a quiet
   link when the day already has meals. */
.nut-copyday {
  margin-top: var(--space-4); background: none; border: 1px solid var(--line);
  border-radius: var(--radius-md); color: var(--accent); font-size: 14px; cursor: pointer;
  padding: var(--space-3) var(--space-4);
}
.nut-copyday:hover { border-color: var(--accent); background: var(--accent-light); }
.nut-copy__hint { font-size: 13px; color: var(--muted); margin-bottom: var(--space-3); }

/* Repeat-a-day HERO (Ship C, rec #4) — empty-day one-tap replay of the most
   recent logged day. Promotes the old buried copy link to the primary action. */
.nut-repeat {
  text-align: left; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: var(--space-4) var(--space-4) var(--space-3);
}
.nut-repeat__eyebrow {
  display: flex; align-items: center; gap: var(--space-1); color: var(--accent);
  font-size: 11px; letter-spacing: .06em; text-transform: uppercase; font-weight: 700;
  margin-bottom: var(--space-2);
}
.nut-repeat__eyebrow .nut-ic svg { width: 14px; height: 14px; }
.nut-repeat__q { font-size: 20px; font-weight: 700; color: var(--ink); line-height: 1.2; margin-bottom: var(--space-1); }
.nut-repeat__when { color: var(--accent); }
.nut-repeat__prev { font-size: 13px; color: var(--ink-2); margin: 0 0 var(--space-4); }
.nut-repeat__prev b { color: var(--ink); font-weight: 600; }
.nut-repeat__go {
  display: block; width: 100%; background: var(--accent); color: var(--on-accent);
  border: 0; border-radius: var(--radius-md); padding: var(--space-3); font-size: 15px; font-weight: 600; cursor: pointer;
}
.nut-repeat__go:hover { background: var(--accent-2); }
.nut-repeat__alt {
  display: block; margin: var(--space-2) auto 0; background: none; border: 0;
  color: var(--muted); font-size: 13px; cursor: pointer; padding: var(--space-1);
}
.nut-repeat__alt:hover { color: var(--accent); }

/* Undo banner after a repeat-a-day copy (Ship C, rec #4) — a persistent, tappable
   in-tab bar (the global toast auto-hides in 3s, too fast to catch a mistake). */
.nut-undobar {
  display: flex; align-items: center; gap: var(--space-3); margin-top: var(--space-4);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
}
.nut-undobar__txt { flex: 1 1 auto; font-size: 13px; color: var(--ink-2); }
.nut-undobar__undo { flex: 0 0 auto; background: none; border: 0; color: var(--accent); font-weight: 700; font-size: 14px; cursor: pointer; padding: var(--space-1) var(--space-2); }

/* Combo picker behind the "+" tile (NUTRITION-PLUS-TILE-COMBO-PICKER-SAVED-ONLY).
   One compact header line: label · "?". The explanation lives behind the "?" and
   reuses the SAME key as the Combos library empty state, so there is exactly one
   explanation of what a combo is, never two side by side. */
.nut-combopick__hd { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-2); }
.nut-combopick__lbl { flex: 1 1 auto; font-size: 12px; font-weight: 600; color: var(--muted); }
.nut-combopick__help {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0; background: none; border: 0; cursor: pointer; color: var(--muted);
}
.nut-combopick__help.is-on { color: var(--accent); }
.nut-combopick__help svg { width: 16px; height: 16px; fill: none; stroke: currentColor; }
.nut-combopick__note { display: none; font-size: 12px; line-height: 1.45; color: var(--muted); margin-bottom: var(--space-3); }
.nut-combopick__note.is-open { display: block; }
/* VOEDING-JE-KIEST-JE-COMBO-METEEN-EN-WEET-WAT-HET-IS — the same header idiom one
   step earlier, in the sheets that ASK for a combo. The button carries its word
   next to the glyph, so the tap target is the whole phrase and clears 44px. */
.nut-combohelp__hd { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-2); }
.nut-combohelp__lbl { flex: 1 1 auto; min-width: 0; font-size: 12px; font-weight: 600; color: var(--muted); }
.nut-combohelp__btn {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 44px; padding: 0 var(--space-2); background: none; border: 0; cursor: pointer;
  color: var(--muted); font-size: 12px; font-weight: 600; text-align: left;
}
.nut-combohelp__btn.is-on { color: var(--accent); }
.nut-combohelp__btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; }
/* VOEDING-HET-COMBOSCHERM-LEGT-ZICHZELF-UIT — the note becomes its own tinted
   plate, so the explanation reads as a note rather than as loose body copy under
   a heading. The example card KEEPS --surface: a real-looking card lying ON a
   tinted note is itself the message that a combo is a thing you can point at. */
.nut-combohelp__note { display: none; margin-bottom: var(--space-3); }
.nut-combohelp__note.is-open {
  display: block;
  background: var(--accent-subtle); border-radius: var(--radius-lg);
  padding: var(--space-3);
}
/* Measured, not eyeballed: --accent-subtle is a 20% tint on Nacht and 12% on Dag,
   both over --bg (the sheet). --muted lands at 2.8:1 (Nacht) and 3.7:1 (Dag) on
   that plate — under 4.5 in BOTH — so the lines carry --ink-2 (6.5:1 / 5.9:1).
   Never lower the tint to rescue a text colour. Gate:
   tests/visual/nutrition-combo-picker-opens-expanded.spec.js. */
.nut-combohelp__line { margin: 0 0 var(--space-2); font-size: 12px; line-height: 1.45; color: var(--ink-2); }
.nut-combohelp__line:last-of-type { margin-bottom: var(--space-3); }
/* The mini picture of a combo: a meal card at ~0.85×, same radius + hairline. */
.nut-comboex { position: relative; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg); overflow: hidden; }
/* The SAME corner ribbon a real combo card wears (.nut-meal__ribbon), scaled from
   its 96px corner to 64px: that repeated mark is what links the picture to the
   cards in the Log without a word of copy. Decorative, never a hit target. */
.nut-comboex__ribbon { position: absolute; top: 0; right: 0; width: 64px; height: 64px; overflow: hidden; pointer-events: none; }
.nut-comboex__ribbon span { position: absolute; display: block; width: 80px; text-align: center; transform: rotate(45deg); right: -26px; top: 9px; background: var(--accent-subtle); color: var(--accent); font-size: 7px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; padding: 2px 0; line-height: 1; }
.nut-comboex__hd { display: flex; align-items: center; gap: var(--space-2); padding: 8px var(--space-3); }
/* Name + kcal cluster LEFT in one stretchy slot — the same trick .nut-meal__title
   plays — so the ribbon's corner never lands on top of the kcal. */
.nut-comboex__ttl { flex: 1 1 auto; min-width: 0; display: flex; align-items: baseline; gap: var(--space-2); }
.nut-comboex__hd .nut-ic svg { width: 14px; height: 14px; fill: none; stroke: currentColor; }
.nut-comboex__hd .nut-ic { color: var(--muted); display: inline-flex; flex: 0 0 auto; }
.nut-comboex__name { min-width: 0; font-size: 13px; font-weight: 500; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nut-comboex__kcal { flex: 0 0 auto; font-size: 11px; color: var(--muted); }
.nut-comboex__row { display: flex; align-items: center; gap: var(--space-2); padding: 7px var(--space-3); border-top: 1px solid var(--line-2); }
.nut-comboex__itm { flex: 1 1 auto; min-width: 0; font-size: 12px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nut-combopick__list { display: flex; flex-direction: column; gap: var(--space-2); }
.nut-savedrow {
  display: flex; align-items: center; gap: var(--space-3); width: 100%; text-align: left;
  background: var(--accent-light); border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4); cursor: pointer;
}
.nut-savedrow:hover { border-color: var(--accent); }
.nut-savedrow .nut-ic { color: var(--accent); flex: 0 0 auto; }
/* Line glyph (clock), not the old filled star — a fill would turn it into a blob. */
.nut-savedrow .nut-ic svg { width: 18px; height: 18px; fill: none; stroke: currentColor; }
.nut-savedrow__main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.nut-savedrow__name { font-size: 15px; font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* One line, clipped — a six-item combo must not wrap the row open; the count
   line underneath tells the truth about what is hidden behind the ellipsis. */
.nut-savedrow__sub { font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nut-savedrow__add { flex: 0 0 auto; display: inline-flex; align-items: center; color: var(--accent); }

/* NUTRITION-REMOVE-QUICK-LOG-CARDS-AND-DRAG-REORDER-MEALS — the .nut-qstart /
   .nut-qbtn block (Ship F's two empty-day quick-start tiles) lived here and was
   removed together with its markup on 2026-07-28. Both tiles duplicated the
   ready-made meal-slot rows above them and the global "+" FAB below. */

/* Rough-log sheet (rec #8) — one optional calories field, slot chips, a name. */
.nut-rl__hint { font-size: 13px; color: var(--ink-2); line-height: 1.45; margin: 0 0 var(--space-4); }
.nut-rl__cal {
  display: flex; align-items: baseline; gap: var(--space-2);
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: var(--space-3) var(--space-4);
}
.nut-rl__num { flex: 0 0 auto; min-width: 64px; width: 96px; background: none; border: 0; color: var(--ink); font-size: 30px; font-weight: 800; padding: 0; }
.nut-rl__num::placeholder { color: var(--muted); }
.nut-rl__num:focus { outline: none; }
.nut-rl__unit { flex: 1 1 auto; font-size: 14px; color: var(--muted); }
.nut-rl__opt { font-size: 11px; color: var(--muted); }
.nut-rl__lbl { font-size: 12px; font-weight: 600; color: var(--muted); margin: var(--space-4) 0 var(--space-2); text-transform: uppercase; letter-spacing: .05em; }
.nut-rl__slots { display: flex; gap: var(--space-2); }
.nut-rl__slot {
  flex: 1 1 0; text-align: center; background: var(--bg); border: 1px solid var(--line);
  border-radius: var(--radius-md); padding: 9px 4px; font-size: 13px; color: var(--ink-2); cursor: pointer;
}
.nut-rl__slot.is-on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 600; }
.nut-rl__name {
  width: 100%; background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 11px var(--space-4); font-size: 14px; color: var(--ink); margin-top: var(--space-2);
}
.nut-rl__name:focus { outline: none; border-color: var(--accent); }
.nut-rl__go { width: 100%; margin-top: var(--space-4); }
.nut-rl__nodetail { display: block; width: 100%; background: none; border: 0; color: var(--accent); font-size: 13.5px; cursor: pointer; padding: var(--space-3); margin-top: 2px; }
.nut-rl__nodetail:hover { color: var(--accent-2); }

.nut-meals { display: flex; flex-direction: column; gap: var(--space-3); }
.nut-meal { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg); overflow: hidden; position: relative; }
.nut-meal__ribbon { position: absolute; top: 0; right: 0; width: 96px; height: 96px; overflow: hidden; pointer-events: none; }
.nut-meal__ribbon span { position: absolute; display: block; width: 110px; text-align: center; transform: rotate(45deg); right: -38px; top: 7px; background: var(--accent-subtle); color: var(--accent); font-size: 7.5px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; padding: 2.5px 0; line-height: 1; }
.nut-meal__head { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-3) var(--space-4); cursor: pointer; }
.nut-meal__caret { background: none; border: 0; color: var(--muted); font-size: 13px; cursor: pointer; width: 18px; flex: 0 0 auto; }
.nut-meal__title { flex: 1 1 auto; display: flex; align-items: baseline; gap: var(--space-3); min-width: 0; }
.nut-meal__name { font-size: var(--text-title); line-height: var(--lh-title); font-weight: 500; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nut-meal__kcal { font-size: var(--text-body-sm); color: var(--muted); flex: 0 0 auto; }
/* VOEDING-KOPIEREN-VULT-HET-VAK-AAN — "236 of 336 kcal · 4 still to tick off"
   is far longer than the bare "336 kcal" it replaces, so it gets its OWN
   full-width row under the head instead of squeezing the meal name into an
   ellipsis. Only rendered while something is un-ticked; a settled card keeps
   its original single-line head untouched. */
.nut-meal__pending {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-2) var(--space-4) var(--space-3);
  border-top: 1px solid var(--line-2);
}
.nut-meal__pendtxt { flex: 1 1 auto; min-width: 0; font-size: var(--text-body-sm); color: var(--accent); }
.nut-meal__tickall {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 4px;
  background: var(--accent-light); border: 0; border-radius: var(--radius-pill);
  color: var(--accent); font-size: var(--text-body-sm); font-weight: 500;
  padding: 6px 12px; cursor: pointer; white-space: nowrap; min-height: 32px;
}
.nut-meal__tickall:hover { background: var(--accent-subtle); }
.nut-meal__menu { background: none; border: 0; color: var(--muted); font-size: 20px; line-height: 1; cursor: pointer; padding: 2px 6px; flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; margin: calc(-1 * var(--space-2)) 0; }
.nut-meal__menu:hover { color: var(--ink); }
/* VOEDING-EEN-FOTO-ZONDER-BEREIK-WACHT-BIJ-DE-MAALTIJD — a photo whose estimate
   could not be sent. Deliberately QUIET: it is not an error and not a log entry,
   it is a thing that is on its way. Same top border as the pending line so it
   reads as another row of the meal card it hangs under; the orphan block wraps
   its own cards in a card, because there is no meal card above them. */
.nut-qphoto {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  border-top: 1px solid var(--line-2);
}
.nut-qphoto__main {
  flex: 1 1 auto; display: flex; align-items: center; gap: var(--space-3);
  min-width: 0; text-align: left; background: none; border: 0; padding: 0;
  font-family: inherit; color: inherit;
}
button.nut-qphoto__main { cursor: pointer; min-height: 44px; }
.nut-qphoto__thumb {
  flex: 0 0 auto; width: 40px; height: 40px; border-radius: var(--radius-sm);
  background-color: var(--line-2); background-size: cover; background-position: center;
}
.nut-qphoto__txt { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.nut-qphoto__state { font-size: var(--text-body-sm); color: var(--muted); }
.nut-qphoto__sub {
  font-size: var(--text-label); color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Ready is the only state with something to do, so it is the only one that is
   allowed to pull the eye. */
.nut-qphoto[data-state="ready"] .nut-qphoto__state { color: var(--accent); font-weight: 500; }
.nut-qphoto[data-state="failed"] .nut-qphoto__state { color: var(--danger); }
.nut-qphoto__acts { flex: 0 0 auto; display: flex; align-items: center; gap: var(--space-2); }
.nut-qphoto__btn {
  background: none; border: 0; color: var(--accent); cursor: pointer;
  font-family: inherit; font-size: var(--text-body-sm);
  padding: 6px 8px; min-height: 32px; white-space: nowrap;
}
.nut-qphoto__btn--del { color: var(--muted); }
.nut-qphoto__btn--del:hover { color: var(--danger); }
.nut-qphotos {
  margin-top: var(--space-3);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); overflow: hidden;
}
.nut-qphotos .nut-qphoto:first-child { border-top: 0; }

.nut-meal__body { border-top: 1px solid var(--line-2); }
.nut-meal__add {
  display: block; width: 100%; text-align: left; background: none; border: 0;
  color: var(--accent); font-size: 14px; padding: var(--space-3) var(--space-4); cursor: pointer;
}
.nut-meal__add:hover { background: var(--accent-light); }

/* NUTRITION-DEFAULT-MEALS-KEEP-EMPTY-HERO — a standard meal with nothing in it
   yet is ONE line, never an opened empty section (that reads as a blank form).
   Same card chrome as a real meal so the list keeps one rhythm; softer border
   and a muted trailing hint so it still reads as waiting, not as logged. */
.nut-mealslot {
  display: flex; align-items: center; gap: var(--space-2); width: 100%;
  min-height: 48px; padding: var(--space-3) var(--space-4); text-align: left;
  background: var(--surface); border: 1px solid var(--line-2);
  border-radius: var(--radius-lg); cursor: pointer; font-family: inherit;
  -webkit-tap-highlight-color: transparent;
}
.nut-mealslot:hover { border-color: var(--line); }
.nut-mealslot__cat { flex: 0 0 auto; display: inline-flex; color: var(--muted); }
.nut-mealslot__main { flex: 1 1 auto; display: flex; align-items: baseline; gap: var(--space-2); min-width: 0; }
.nut-mealslot__name {
  flex: 0 0 auto; font-size: var(--text-title); line-height: var(--lh-title);
  font-weight: 500; color: var(--ink);
}
.nut-mealslot__hint {
  font-size: var(--text-body-sm); color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nut-mealslot__hint::before { content: '·'; margin-right: var(--space-2); }
.nut-mealslot__chev { flex: 0 0 auto; display: inline-flex; color: var(--muted); }
/* HERHAAL-JE-ONTBIJT-VAN-GISTEREN — the repeat offer under an empty slot.
   VOEDING-DE-HERHAALREGEL-IS-WEER-TE-LEZEN made it one panel with the slot row
   above it (.nut-slotgrp) and turned the line itself into the button, in the
   shape of a meal card's "+ Add food" row: leading plus, full width, left
   aligned. The panel chrome (tint, edge, radius) lives in the glass block. */
.nut-slotgrp { border-radius: var(--radius-lg); overflow: hidden; }
.nut-slotgrp .nut-mealslot { border-radius: 0; }
.nut-slotsug {
  position: relative;
  display: flex; align-items: flex-start; gap: var(--space-3); width: 100%;
  padding: var(--space-3) var(--space-4);
  background: none; border: 0; text-align: left; font-family: inherit;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
/* The hairline between the rows of the panel. */
.nut-slotgrp .nut-slotsug::before,
.nut-slotgrp .nut-slotadd::before {
  content: ''; position: absolute; left: var(--space-4); right: var(--space-4);
  top: 0; height: 1px; background: var(--line-2);
}
.nut-slotsug__ico { flex: 0 0 auto; display: inline-flex; color: var(--accent); margin-top: 2px; }
.nut-slotsug__main { flex: 1 1 auto; min-width: 0; }
.nut-slotsug__ttl { display: block; font-size: var(--text-body-sm); line-height: var(--lh-body-sm); color: var(--accent); }
.nut-slotsug__names {
  display: block;
  font-size: var(--text-label); line-height: var(--lh-label); color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nut-slotsug:disabled { opacity: .5; cursor: default; }

/* VOEDING-EEN-LEEG-VAK-BIEDT-OOK-VERS-BEGINNEN — the "start fresh" door, in the
   same shape as the repeat line above it so the two choices read as a pair of
   named doors rather than one big button with an invisible one over it. No
   second line: there are no item names to show. Panel chrome comes from
   .nut-slotgrp, so this row carries no glass of its own. */
.nut-slotadd {
  position: relative;
  display: flex; align-items: flex-start; gap: var(--space-3); width: 100%;
  padding: var(--space-3) var(--space-4);
  background: none; border: 0; text-align: left; font-family: inherit;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.nut-slotadd__ico { flex: 0 0 auto; display: inline-flex; color: var(--accent); margin-top: 2px; }
.nut-slotadd__main { flex: 1 1 auto; min-width: 0; }
.nut-slotadd__ttl { display: block; font-size: var(--text-body-sm); line-height: var(--lh-body-sm); color: var(--accent); }

/* On the empty day the slots sit between the hero copy and the quick-start
   group, so they carry their own vertical rhythm. */
.nut-meals--slots { margin: var(--space-4) 0; }

/* Entry rows — v0.9.209 (Phase 4): each row swipes left to reveal an action tray.
   The wrap clips the tray; the row is the solid-background foreground that slides. */
/* NUTRITION-M3-WAVE-4 — entry rows within a meal card are separated by an
   INSET var(--line) hairline (HIG: divider begins at the row content, not
   edge-to-edge). Drawn as a ::before so it insets from the card edges; z-index
   lifts it above the row's opaque surface background. */
.nut-entry-wrap { position: relative; overflow: hidden; }
.nut-entry-wrap:not(:first-child)::before {
  content: ''; position: absolute; top: 0; left: var(--space-4); right: var(--space-4);
  height: 1px; background: var(--line); z-index: 1; pointer-events: none;
}
.nut-entry-actions { position: absolute; inset: 0 0 0 auto; display: flex; align-items: stretch; }
.nut-entry-act {
  width: 64px; min-height: 44px; flex: 0 0 auto; border: 0; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  font-size: 11px; font-family: inherit; line-height: 1; -webkit-tap-highlight-color: transparent;
}
.nut-entry-act .nut-ic { width: 18px; height: 18px; }
.nut-entry-act--edit { background: var(--line-2); color: var(--ink); }
.nut-entry-act--dup  { background: var(--line); color: var(--ink); }
.nut-entry-act--del  { background: var(--danger); color: var(--on-danger); }

.nut-entry {
  position: relative; display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-4); background: var(--surface);
  touch-action: pan-y; will-change: transform; transition: transform var(--dur-base) var(--ease-standard);
}
/* Dim the CONTENT, not the row — the row must stay opaque so the swipe tray
   behind it never bleeds through a semi-transparent foreground. */
.nut-entry.is-uneaten > * { opacity: 0.55; }
/* NUTRITION-RECIPE-LOG-AS-SINGLE-AGGREGATE-ROW — a recipe logged as one
   aggregate row gets a subtle accent rail so it reads as "a whole recipe",
   distinct from the loose single-ingredient rows around it. */
.nut-entry--recipe { border-left: 3px solid var(--accent); padding-left: calc(var(--space-4) - 3px); }
.nut-check {
  flex: 0 0 auto; width: 24px; height: 24px; border-radius: 50%;
  border: 1.5px solid var(--line); background: none; color: var(--on-accent);
  font-size: 14px; line-height: 1; cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.nut-check.on { background: var(--accent); border-color: var(--accent); }
.nut-entry__mid { flex: 1 1 auto; min-width: 0; cursor: pointer; }
.nut-entry__name { font-size: var(--text-body); line-height: var(--lh-body); color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nut-entry__meta { font-size: var(--text-label); color: var(--muted); margin-top: 2px; }
.nut-entry__kcal { flex: 0 0 auto; font-size: var(--text-body); color: var(--ink-2); font-variant-numeric: tabular-nums; }

/* Drag handle — v0.9.210 (Phase 4 Ship B). The handle alone gets touch-action:none
   so the drag never fights the page's vertical scroll (which the rest of the row
   leaves to the browser via pan-y). Press the grip → drag up/down to reorder within
   a meal, or across into another meal. */
.nut-entry__grip {
  flex: 0 0 auto; width: 28px; height: 28px; padding: 0; margin-left: -4px;
  display: flex; align-items: center; justify-content: center;
  background: none; border: 0; cursor: grab; color: var(--muted);
  touch-action: none; -webkit-tap-highlight-color: transparent;
}
.nut-entry__grip:active { cursor: grabbing; }
.nut-entry__grip svg { width: 18px; height: 18px; display: block; }

/* The lifted, finger-following copy of the row during a drag. */
.nut-entry--ghost {
  position: fixed; z-index: 300; pointer-events: none; box-sizing: border-box;
  border-radius: var(--radius-md); background: var(--surface);
  box-shadow: var(--elev-2); transform: scale(1.02);
  transition: none; opacity: .96;
}
/* SNELHEID-4 — a row painted before the server confirmed it. Readable, not
   greyed out: the item IS there, only its handles aren't live yet. */
.nut-entry-wrap.is-pending .nut-check { pointer-events: none; }
/* The vacated slot left behind, shown as a dashed drop target that travels with
   the pointer (it IS the drop indicator — single, unambiguous). */
.nut-entry-wrap.is-dragging { background: var(--accent-light); outline: 2px dashed var(--accent); outline-offset: -2px; }
.nut-entry-wrap.is-dragging .nut-entry,
.nut-entry-wrap.is-dragging .nut-entry-actions { opacity: 0; }
/* VOEDING-SLEEP-EEN-PRODUCT-NAAR-EEN-LEGE-MAALTIJD — the two drop targets the
   travelling placeholder cannot enter: an empty default slot (a <button>, so a
   div may not move inside it) and a COLLAPSED meal card (its body isn't
   rendered). They light up in exactly the placeholder's language — accent tint,
   dashed accent edge — so "this is where it lands" reads the same everywhere.
   The .nut-slotgrp variant is not a duplicate: the glass block later in this
   file strips a slot's own background at (0,2,0), and the marking has to win. */
.nut-mealslot.is-drop-target,
.nut-slotgrp .nut-mealslot.is-drop-target,
.nut-meal.is-drop-target {
  background: var(--accent-light);
  outline: 2px dashed var(--accent);
  outline-offset: -2px;
}

/* NUTRITION-REMOVE-QUICK-LOG-CARDS-AND-DRAG-REORDER-MEALS — the same grip one
   level up, on the meal head. Quieter than the entry grip (it sits on a header
   that already carries a caret and a ⋯ menu) but the identical hit size, so the
   gesture stays reachable on a 390px phone. */
.nut-meal__grip {
  flex: 0 0 auto; width: 28px; height: 28px; padding: 0; margin-left: 2px;
  display: flex; align-items: center; justify-content: center;
  background: none; border: 0; cursor: grab; color: var(--muted); opacity: .55;
  touch-action: none; -webkit-tap-highlight-color: transparent;
}
.nut-meal__grip:hover { opacity: 1; }
.nut-meal__grip:active { cursor: grabbing; opacity: 1; }
.nut-meal__grip svg { width: 18px; height: 18px; display: block; }

.nut-meal--ghost {
  position: fixed; z-index: 300; pointer-events: none; box-sizing: border-box;
  border-radius: var(--radius-lg); background: var(--surface);
  box-shadow: var(--elev-2); transform: scale(1.02);
  transition: none; opacity: .96;
}
/* The vacated card: a dashed drop target that travels to whatever slot the
   pointer reaches, collapsed to the head's height so the list closes up. */
.nut-meal.is-dragging {
  background: var(--accent-light); outline: 2px dashed var(--accent); outline-offset: -2px;
}
.nut-meal.is-dragging > * { opacity: 0; }
.nut-meal.is-dragging .nut-meal__body { display: none; }

.nut-addmeal {
  display: block; width: 100%; margin-top: var(--space-4); padding: var(--space-3);
  background: none; border: 1px dashed var(--line); border-radius: var(--radius-md);
  color: var(--accent); font-size: 14px; cursor: pointer;
}
.nut-addmeal:hover { border-color: var(--accent); background: var(--accent-light); }

/* Add-food sheet body */
.nut-sheet-body { padding: var(--space-4) var(--space-5) var(--space-6); overflow-y: auto; }
/* NUTRITION-MEAL-KEBAB-BULK-CHANGE-TIME — bulk time-picker sheet */
.bulk-time__hint { margin-bottom: var(--space-3); color: var(--muted); }
.bulk-time__input {
  width: 100%;
  padding: var(--space-3);
  font-size: 1.5em;
  text-align: center;
  background: var(--surface-2, var(--line));
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  margin-bottom: var(--space-3);
}
.bulk-time__save { width: 100%; margin-bottom: var(--space-2); }

/* NUTRITION-MEAL-RECURRING-DELETE-INSTANCE-OR-SERIES — 3-option delete sheet */
.recurring-delete__hint { margin-bottom: var(--space-3); }
.recurring-delete__btn {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  width: 100%;
  padding: var(--space-3);
  margin-bottom: var(--space-2);
  text-align: left;
}
.recurring-delete__btn-sub {
  font-size: 0.85em;
  color: var(--muted);
  margin-top: var(--space-1);
}
.recurring-delete__cancel { margin-top: var(--space-3); }
.nut-search {
  width: 100%; min-height: 44px; padding: var(--space-3); font-size: 16px; color: var(--ink);
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-md); box-sizing: border-box;
  transition: border-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard);
}
.nut-search:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring-focus); }
/* NUTRITION-ADD-FOOD-ADAPTIVE-TOP3-MEER — scan-barcode affordance parked inside
   the search field; the input reserves the trailing space so text never runs
   under it. Absent (not disabled) on camera-less devices. */
.nut-search-wrap { position: relative; }
.nut-search-wrap .nut-search { padding-right: 48px; }
.nut-search__scan {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  display: flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; padding: 0; cursor: pointer;
  background: transparent; border: 0; border-radius: var(--radius-md); color: var(--accent);
}
.nut-search__scan:hover { background: var(--line-2); }
.nut-search__scan .nut-ic svg { width: 22px; height: 22px; }

/* NUTRITION-LOG-INLINE-QUICK-LOG-WITH-MEAL-PICKER — inline entry-point on the
   Log page between the consistency band and the water cell. Reuses the add-food
   sheet's search bar + alt-row buttons; visually identical to flow A. */
.food-detail__meal-picker {
  display: flex; flex-direction: column; gap: var(--space-2); margin: var(--space-3) 0;
}
.food-detail__meal-picker label { font-size: 13px; color: var(--muted); }
.food-detail__meal-select {
  width: 100%; padding: var(--space-3); font-size: 16px; color: var(--ink);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm); box-sizing: border-box;
}
.food-detail__meal-select:focus { outline: none; border-color: var(--accent); }
/* NUTRITION-MY-RECIPES-LIST-IN-LOG-FOOD-SHEET — recipe list above meal-picker */
.log-food__recipes { display: flex; flex-direction: column; gap: var(--space-2); margin: var(--space-3) 0; max-height: 40vh; overflow-y: auto; }
.log-food__section-label { font-size: 13px; color: var(--muted); }
.log-food__recipe-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.log-food__recipe {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--surface); border: 2px solid var(--line); border-radius: var(--radius-sm);
  cursor: pointer; box-sizing: border-box;
}
.log-food__recipe:active { opacity: .7; }
.log-food__recipe[aria-checked="true"] { border-color: var(--accent); }
.log-food__recipe-thumb { flex: 0 0 auto; width: 48px; height: 48px; border-radius: var(--radius-sm); overflow: hidden; }
.log-food__recipe-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.log-food__recipe-thumb--empty { width: 100%; height: 100%; background: var(--line-2); }
.log-food__recipe-body { flex: 1 1 auto; min-width: 0; }
.log-food__recipe-name { font-size: 14px; font-weight: 600; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.log-food__recipe-macros { font-size: 12px; color: var(--muted); }
.log-food__recipe-check {
  flex: 0 0 auto; width: 24px; height: 24px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: transparent; color: transparent;
}
.log-food__recipe[aria-checked="true"] .log-food__recipe-check { background: var(--accent); color: var(--on-accent); }
.log-food__recipe-check .nut-ic svg { width: 16px; height: 16px; }
.log-food__recipes--empty { align-items: center; text-align: center; padding: var(--space-4) 0; }
.log-food__empty-text { color: var(--ink); font-style: italic; margin: 0 0 var(--space-2); }
.log-food__empty-help { color: var(--muted); font-size: 13px; margin: 0; }
/* Inline new-meal slot — input + check/× micro-icons (progressive disclosure). */
.food-detail__new-meal {
  display: flex; align-items: center; gap: var(--space-2);
}
.food-detail__new-meal-input {
  flex: 1 1 auto; min-width: 0; padding: var(--space-3); font-size: 16px; color: var(--ink);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm); box-sizing: border-box;
}
.food-detail__new-meal-input:focus { outline: none; border-color: var(--accent); }
.food-detail__meal-icon-btn {
  flex: 0 0 auto; width: 36px; height: 36px; border: 0; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-2, var(--line-2)); cursor: pointer; padding: 0;
}
.food-detail__meal-icon-btn .nut-ic svg { width: 18px; height: 18px; }
.food-detail__meal-icon-btn--confirm { background: var(--accent); color: var(--on-accent); }
.food-detail__meal-icon-btn--cancel { color: var(--muted); }
.food-detail__meal-icon-btn:active { opacity: .7; }
.food-detail__meal-icon-btn:disabled { opacity: .5; cursor: default; }
/* Helper line under the disabled ADD: explains why the primary CTA waits. */
.food-detail__meal-hint {
  font-size: 12px; color: var(--muted); margin: var(--space-1) 0 0; text-align: center;
}
.food-detail__meal-hint:empty, .food-detail__meal-hint[hidden] { display: none; }
/* NUTRITION-MEAL-PICKER-PROGRESSIVE-DISCLOSURE — collapsed until the primary
   CTA is tapped; expanding reveals the rows + chips (select equals save). */
.nut-subpick.is-collapsed { display: none; }
/* NUTRITION-MEAL-PICKER-PRESETS-AND-SUBFLOW-CREATE-REMOVE — sub-flow picker:
   existing-meal rows + 4 preset chips, no select / no free-form / no create. */
.nut-subpick__meals { display: flex; flex-direction: column; gap: var(--space-2); }
.nut-subpick__meal {
  width: 100%; text-align: left; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); color: var(--ink); font-size: 15px; font-weight: 500;
  padding: 12px 14px; cursor: pointer; font-family: inherit;
}
.nut-subpick__meal:hover { border-color: var(--accent); color: var(--accent); }
.nut-subpick__meal.is-on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
/* VOEDING-HET-COMBOSCHERM-LEGT-ZICHZELF-UIT — row line 2 shows WHAT is in the
   combo, in the same shape .nut-slotsug__names already uses on the repeat row:
   one clipped line, never a second row of height. The selected state paints the
   whole button in --accent, where --muted would vanish — so the names follow
   currentColor there instead of keeping their own grey. */
.nut-subpick__ttl { display: block; }
.nut-subpick__names {
  display: block; margin-top: 2px; font-weight: 400;
  font-size: var(--text-label); line-height: var(--lh-label); color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nut-subpick__meal:hover .nut-subpick__names,
.nut-subpick__meal.is-on .nut-subpick__names { color: inherit; }
.nut-subpick__or { text-align: center; color: var(--muted); font-size: 12px; margin: var(--space-1) 0; }
.nut-subpick__presets { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.nut-subpick__chip {
  flex: 1 1 0; min-width: 0; text-align: center; background: var(--bg);
  border: 1px solid var(--line); border-radius: var(--radius-pill); color: var(--ink);
  font-size: 14px; font-weight: 500; padding: 10px 12px; cursor: pointer; font-family: inherit;
}
.nut-subpick__chip:hover { border-color: var(--accent); color: var(--accent); }
.nut-subpick__chip.is-on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.nut-subpick__chip:disabled { opacity: 0.6; cursor: default; }
.nut-manualbtn { margin-top: var(--space-3); background: none; border: 0; color: var(--accent); font-size: 14px; cursor: pointer; padding: 4px 0; }
.nut-srch-status { color: var(--muted); font-size: 13px; padding: var(--space-3) 0; }
.nut-srch-empty { padding: var(--space-4) 0; }
.nut-srch-empty__hd { color: var(--ink); font-size: var(--text-title); line-height: var(--lh-title); font-weight: 600; }
.nut-srch-empty__sub { color: var(--muted); font-size: var(--text-body-sm); line-height: var(--lh-body-sm); margin-top: var(--space-1); }
/* VOEDING-DE-ZOEKBALK-BLIJFT-STAAN-ALS-ER-NIETS-GEVONDEN-IS — the one real next
   step in the "nothing found" state: carry the typed word into the own-product
   sheet. Sits between the heading and the quiet scan line, hence its own top
   margin and the wider gap it hands to the line below it. */
.nut-srch-empty__own { margin-top: var(--space-3); }
.nut-srch-empty__own + .nut-srch-empty__sub { margin-top: var(--space-3); }
/* VOEDING-ZOEKEN-GESCHIEDENIS-MET-PORTIES — section heading above the block of
   previously-logged matches inside the typing search results. */
.nut-srch-hist__hd {
  color: var(--muted); font-size: var(--text-body-sm); line-height: var(--lh-body-sm);
  font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em;
  padding: var(--space-3) 0 var(--space-2);
}
/* v0.9.191 — inline search spinner (reuses the existing @keyframes spin). */
.nut-spin {
  display: inline-block; width: 12px; height: 12px; vertical-align: -1px;
  border: 2px solid var(--line); border-top-color: var(--accent);
  border-radius: 50%; animation: spin var(--dur-loop) linear infinite;
}
.nut-results { display: flex; flex-direction: column; }
.nut-result { text-align: left; background: none; border: 0; border-top: 1px solid var(--line-2); padding: var(--space-3) 0; cursor: pointer; }
.nut-result:hover { background: var(--accent-light); }
.nut-result__name { font-size: 14px; color: var(--ink); }
.nut-result__sub { font-size: 12px; color: var(--muted); margin-top: 2px; }

/* Product image thumbnail in search rows (v0.9.231). The --img modifier turns the
   row into a flex line (thumb · text) — scoped to search results so other reusers
   of .nut-result (favorites / copy-from / destination rows) stay block-stacked. */
.nut-result--img { display: flex; align-items: center; gap: var(--space-3); }
.nut-result__body { flex: 1 1 auto; min-width: 0; }
.nut-result--img .nut-result__name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nut-result--img .nut-result__sub  { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nut-thumb {
  flex: 0 0 auto; width: 44px; height: 44px; border-radius: var(--radius-md); overflow: hidden;
  background: var(--bg); border: 1px solid var(--line-2);
  display: flex; align-items: center; justify-content: center; box-sizing: border-box;
}
.nut-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.nut-thumb--ph { background: var(--line-2); }
.nut-thumb--ph .nut-ic { width: 20px; height: 20px; color: var(--muted); }

/* Serving scaler + manual/goal fields */
/* Product hero image in the detail/scaler view (v0.9.231). */
.nut-hero {
  width: 200px; height: 200px; max-width: 100%; border-radius: var(--radius-lg);
  overflow: hidden; background: var(--surface-2, var(--bg)); border: 1px solid var(--line-2);
  margin: 0 auto var(--space-3); display: flex; align-items: center; justify-content: center;
  box-sizing: border-box;
}
.nut-hero img { width: 100%; height: 100%; object-fit: contain; display: block; }
@media (max-width: 360px) { .nut-hero { width: 70vw; height: 70vw; } }
/* NUTRITION-EDIT-SHEET-PRODUCT-AND-USER-PHOTO — compact photo row atop the Edit
   sheet: product photo + the user's own captured photo, same height, side by
   side. One present = that one alone; none = the block isn't rendered at all. */
.nut-photorow { display: flex; gap: var(--space-3); margin: 0 0 var(--space-3); }
.nut-photorow__tile {
  position: relative;
  flex: 0 1 130px; height: 130px; min-width: 0;
  border-radius: var(--radius-lg); overflow: hidden;
  background: var(--surface-2, var(--bg)); border: 1px solid var(--line-2);
  display: flex; align-items: center; justify-content: center; box-sizing: border-box;
}
.nut-photorow__tile img { width: 100%; height: 100%; display: block; }
.nut-photorow__tile--product img { object-fit: contain; }
.nut-photorow__tile--user img { object-fit: cover; }
/* SHARE-MEAL-PHOTO-BRANDED-EXPORT — the share affordance sits ON the user photo
   so it reads as "share THIS", and exists only where a photo does. The dark
   scrim is not an accent surface, so its foreground is a literal white by
   design (the --on-accent rule governs accent backgrounds only). */
.nut-photorow__share {
  position: absolute; right: 6px; bottom: 6px;
  width: 34px; height: 34px; border: 0; border-radius: 50%;
  background: rgba(0, 0, 0, 0.55); color: #fff; padding: 0; cursor: pointer; /* allow-hex: the scrim is theme-independent — it sits on the user's photo, not on an app surface */
  display: flex; align-items: center; justify-content: center;
}
.nut-photorow__share .nut-ic svg { width: 18px; height: 18px; display: block; }
.nut-sharecard__prev {
  border-radius: var(--radius-lg); overflow: hidden; margin: 0 0 var(--space-3);
  background: var(--surface-2, var(--bg)); border: 1px solid var(--line-2);
}
.nut-sharecard__prev img { width: 100%; display: block; }
.nut-sharecard__btnic { display: inline-flex; margin-right: var(--space-2); vertical-align: middle; }
.nut-sharecard__btnic svg { width: 17px; height: 17px; display: block; }
.nut-scale__brand { color: var(--muted); font-size: 13px; margin-bottom: var(--space-3); }
/* VOEDING-NA-HET-ETEN-WEEG-JE-WAT-ER-OVER-IS — the two ways to the same gram
   figure (plate on the scale · what is left in the pan). The segment labels are
   sentences, not the one-word tabs `.nut-seg__btn`'s `padding: … 0` was written
   for, so they get side padding and a line-height that survives wrapping.
   `.nut-left__pane` deliberately has NO display rule: the panes are toggled with
   [hidden], and any author `display` here would beat the UA's [hidden] rule. */
.nut-seg--wrap .nut-seg__btn { padding-left: var(--space-2); padding-right: var(--space-2); line-height: 1.3; }
.nut-weigh__seg { margin-bottom: var(--space-3); }
.nut-left__out { min-height: 18px; font-size: 13px; font-weight: 600; color: var(--ink); margin: 0 0 var(--space-3); }
.nut-left__note { font-size: 12.5px; line-height: 1.45; color: var(--muted); margin: 0 0 var(--space-2); }
.nut-scale__row { display: flex; gap: var(--space-3); }
.nut-scale__row > * { flex: 1 1 0; min-width: 0; }
.nut-scale__save { width: 100%; margin-top: var(--space-4); }
/* NUTRITION-PORTION-AI-SUGGESTED-MEASURES-AND-ASKAI-CHIP — the bottom action row is
   ADD only again (full width); Ask AI moved up into the measure-chip row as its last
   chip, so it reads as "AI helps with PORTIONS" rather than a second way to add. */
.nut-scale__actions { display: flex; align-items: stretch; gap: var(--space-3); margin-top: var(--space-4); }
.nut-scale__actions .nut-scale__save { flex: 1 1 auto; width: auto; margin-top: 0; }
.nut-field { margin-bottom: var(--space-3); }
.nut-field__lbl { display: block; font-size: var(--text-body-sm); line-height: var(--lh-body-sm); color: var(--ink-2); margin-bottom: var(--space-1); }
.nut-field__in {
  width: 100%; min-height: 44px; padding: var(--space-3); font-size: 16px; color: var(--ink);
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-md); box-sizing: border-box;
  transition: border-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard);
}
/* Focus via box-shadow ring (no border-width change) so the field never shifts
   layout — matters for the sticky scaler thumb-zone. */
.nut-field__in:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring-focus); }
/* GOAL-SHEET-KCAL-FROM-MACROS-CALC-BUTTON — calorie field + a small ghost helper
   button that computes kcal from the macro fields (Atwater 4/4/9). */
.nut-goal-calcrow { display: flex; align-items: flex-end; gap: 8px; margin-bottom: var(--space-3); }
.nut-goal-calcrow .nut-field { flex: 1; min-width: 0; margin-bottom: 0; }
.nut-goal-calcbtn {
  flex: 0 0 auto; min-width: 44px; min-height: 44px;
  display: flex; align-items: center; justify-content: center;
  background: transparent; color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  cursor: pointer; font-family: inherit;
}
.nut-goal-calcbtn:hover { border-color: var(--accent); color: var(--accent); }
.nut-goal-calcbtn[disabled] { opacity: 0.5; cursor: default; }
.nut-field--flash { animation: nutFieldFlash var(--dur-slow) var(--ease-standard); }
@keyframes nutFieldFlash {
  0% { box-shadow: 0 0 0 0 var(--accent); border-color: var(--accent); }
  40% { box-shadow: var(--ring-focus); border-color: var(--accent); }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* Add-a-meal dialog (v0.9.181) — small centered .ui-dlg card: a linear list of
   standard meal names (day rhythm) + a compact custom field. */
.nut-mealopts { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-4); }
.nut-mealopt {
  width: 100%; text-align: left; background: var(--bg); border: 1px solid var(--line);
  border-radius: var(--radius-sm); color: var(--ink); font-size: 15px; font-weight: 500;
  padding: 12px 14px; cursor: pointer; font-family: inherit;
}
.nut-mealopt:hover { border-color: var(--accent); color: var(--accent); }
/* Select-then-add: a tapped preset stays visibly on until Add commits it. */
.nut-mealopt.is-on {
  border-color: var(--accent); background: var(--accent); color: var(--on-accent);
}
.nut-mealopt.is-on:hover { color: var(--on-accent); }
.nut-mealor { text-align: center; color: var(--muted); font-size: 12px; margin: 0 0 var(--space-3); }
.nut-mealcustom { align-items: stretch; }
.nut-mealcustom__in { flex: 1 1 auto; margin: 0; }
.nut-mealcustom__add { flex: 0 0 auto; padding-left: var(--space-5); padding-right: var(--space-5); }

/* Copy-from-yesterday suggestions inside the add-combo dialog: same row idiom as
   the presets, with a quiet second line naming what is in the combo. */
.nut-mealsug { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-4); }
.nut-mealsug .nut-mealor { margin: 0 0 var(--space-1); }
.nut-mealsug__row { display: flex; flex-direction: column; gap: 2px; }
.nut-mealsug__name { font-weight: 600; }
.nut-mealsug__sub {
  color: var(--muted); font-size: 12px; font-weight: 400;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nut-mealsug__row:hover .nut-mealsug__sub { color: var(--muted); }
.nut-mealsug__all { text-align: center; font-weight: 600; }
.nut-mealsug__row:disabled, .nut-mealsug__all:disabled { opacity: 0.55; cursor: default; }

/* In-flight create UX: all create controls disable together; the tapped one
   shows a subtle spinner; a slow-network hint appears (non-blocking) after ~4s. */
.nut-mealopt:disabled, .nut-savedrow:disabled, .nut-mealcustom__add:disabled,
.nut-mealcustom__in:disabled { opacity: 0.55; cursor: default; }
.nut-mealopt.is-loading, .nut-savedrow.is-loading, .nut-mealcustom__add.is-loading {
  position: relative; color: transparent;
}
.nut-mealopt.is-loading::after, .nut-savedrow.is-loading::after,
.nut-mealcustom__add.is-loading::after {
  content: ''; position: absolute; top: 50%; left: 50%;
  width: 16px; height: 16px; margin: -8px 0 0 -8px;
  border: 2px solid var(--line); border-top-color: var(--accent);
  border-radius: 50%; animation: nut-mealspin var(--dur-loop) linear infinite;
}
.nut-mealcustom__add.is-loading::after { border-top-color: var(--on-accent); }
@keyframes nut-mealspin { to { transform: rotate(360deg); } }
.nut-mealhint {
  min-height: 16px; margin: 0 0 var(--space-3); text-align: center;
  font-size: 12px; color: var(--muted); opacity: 0;
  transition: opacity var(--dur-base) var(--ease-standard);
}
.nut-mealhint.is-on { opacity: 1; }

.nut-chip {
  display: inline-block; margin-bottom: var(--space-3); padding: 6px 12px;
  background: var(--accent-light); border: 1px solid var(--line); border-radius: var(--radius-pill);
  color: var(--accent); font-size: 13px; cursor: pointer;
}
.nut-chip:hover { border-color: var(--accent); }
.nut-copyday { width: 100%; margin-top: var(--space-3); }
.nut-del { width: 100%; margin-top: var(--space-3); color: var(--danger); }

.nut-mpre { display: flex; gap: var(--space-2); margin: var(--space-2) 0; }
.nut-mpre__cell { flex: 1 1 0; text-align: center; background: var(--bg); border-radius: var(--radius-sm); padding: var(--space-2) 0; }
.nut-mpre__num { display: block; font-size: 16px; font-weight: 600; color: var(--ink); }
.nut-mpre__lbl { display: block; font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; margin-top: 2px; }

/* Ship D (rec #6) — portion picker: big readout + −/+ stepper + tap-presets in
   place of the gram/servings keyboard; the number pad is one tap deeper, and
   optional hand portions for by-sight logging. */
.nut-port { margin-bottom: var(--space-3); }
.nut-port__stepper { display: flex; align-items: stretch; gap: var(--space-3); margin-bottom: var(--space-2); }
.nut-port__btn {
  position: relative; /* anchors the velocity badge during hold-to-accelerate */
  flex: 0 0 64px; min-width: 64px; min-height: 64px; height: 64px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink); font-size: 26px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center; font-family: inherit; touch-action: none;
  -webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent;
}
/* CHANGE 2 — hold-to-accelerate velocity badge */
.nut-port__velbadge {
  position: absolute; top: -20px; left: 50%; transform: translateX(-50%);
  font-size: 12px; color: var(--text-muted, var(--muted)); white-space: nowrap;
  opacity: 0; transition: opacity var(--dur-fast) var(--ease-standard); pointer-events: none;
}
.nut-port__velbadge.is-on { opacity: 1; }
/* CHANGE 1 — tap-to-edit gram field + non-editable unit suffix */
.nut-port__display--edit { cursor: text; }
.nut-port__gram {
  width: 100%; min-width: 0; max-width: 5ch; text-align: right; border: 0; background: none; padding: 0;
  font-size: 24px; font-weight: 700; color: var(--ink); font-family: inherit; line-height: 1.1;
  -moz-appearance: textfield;
}
.nut-port__gram::-webkit-outer-spin-button, .nut-port__gram::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.nut-port__gram:focus { outline: none; }
.nut-port__unit { font-size: 18px; font-weight: 600; color: var(--muted); }
/* CHANGE 3 — scrub affordance hint + CHANGE 6 history chip */
.nut-port__hintrow { display: flex; align-items: center; justify-content: center; gap: var(--space-3); min-height: 26px; margin-bottom: var(--space-2); }
.nut-port__scrubhint { font-size: 14px; letter-spacing: 0.3em; color: var(--text-muted, var(--muted)); transition: opacity var(--dur-slow) var(--ease-standard); }
.nut-port__scrubhint.is-fade { opacity: 0; }
.nut-port__histchip {
  background: none; border: 1px solid var(--line); border-radius: var(--radius-pill); padding: 4px 12px;
  color: var(--text-muted, var(--muted)); font-size: 13px; cursor: pointer; font-family: inherit;
}
.nut-port__histchip:hover { border-color: var(--accent); color: var(--accent); }
/* CHANGE 4 — sticky bottom thumb-zone */
.nut-scale--sticky.mo-body { display: flex; flex-direction: column; }
.nut-scale__sticky {
  position: sticky; bottom: 0; z-index: 1; margin-top: auto;
  background: var(--bg); border-top: 1px solid var(--line);
  padding: var(--space-3) 0 0; margin-left: calc(-1 * var(--space-5)); margin-right: calc(-1 * var(--space-5));
  padding-left: var(--space-5); padding-right: var(--space-5);
}
.nut-port__btn:hover { border-color: var(--accent); color: var(--accent); }
.nut-port__btn:disabled { opacity: 0.35; cursor: default; }
.nut-port__btn:disabled:hover { border-color: var(--line); color: var(--ink); }
.nut-port__display {
  flex: 1 1 auto; min-width: 0; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); display: flex; flex-direction: column; align-items: center;
  justify-content: center; padding: 8px 4px;
  cursor: pointer; transition: border-color var(--dur-fast) var(--ease-standard); -webkit-tap-highlight-color: transparent;
}
.nut-port__display:hover, .nut-port__display:focus-visible { border-color: var(--accent); outline: none; }
.nut-port__numrow { display: flex; align-items: center; gap: 6px; }
/* `display: flex` outranks the hidden attribute — unit mode needs it gone. */
.nut-port__numrow[hidden] { display: none; }
.nut-port__unitcount { font-size: 24px; font-weight: 700; color: var(--ink); line-height: 1.1; cursor: pointer; }
.nut-port__num { font-size: 24px; font-weight: 700; color: var(--ink); line-height: 1.1; }
.nut-port__edithint { display: inline-flex; color: var(--muted); opacity: 0.7; }
.nut-port__edithint svg { width: 13px; height: 13px; }
.nut-port__sub { font-size: 12px; color: var(--muted); margin-top: 3px; }
.nut-port__presets { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: var(--space-3); }
.nut-port__chip {
  flex: 1 1 64px; min-width: 0; min-height: 46px; padding: 9px 4px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink-2); font-size: 14px; cursor: pointer; font-family: inherit; text-align: center;
  overflow: hidden;
}
.nut-port__chip:hover { border-color: var(--accent); }
.nut-port__chip.is-active { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.nut-port__chip.is-active .nut-port__chipsub { color: var(--on-accent); opacity: 0.85; }
.nut-port__chip:active { transform: scale(0.95); transition: transform var(--dur-fast) var(--ease-standard); }
/* INTELLIGENCE-POLISH CHANGE 1/2 — two-line preset chip (multiplier + computed grams) */
.nut-port__chiptop { display: block; font-size: 16px; font-weight: 600; line-height: 1.1; overflow-wrap: anywhere; word-break: break-word; }
/* NUTRITION-PORTION-SHEET-ASKAI-LAYOUT-AND-REPEAT — long single-word measure keys get a
   wider basis so the preset row wraps to a clean 2×2 (whole word, one line) instead of
   overflowing a cramped 1×4; short multiplier/gram chips keep the 64px basis (1×4). */
.nut-port__chip--wide { flex-basis: 140px; }
/* NUTRITION-PORTION-AI-SUGGESTED-MEASURES-AND-ASKAI-CHIP — Ask AI rides in the same
   chip row as the last item: accent outline so it reads as an offer rather than a
   value, filling on hover with --on-accent text (contrast protocol, lint rule g). */
.nut-port__chip--ai {
  flex: 1 1 120px; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  background: transparent; border-color: var(--accent); color: var(--accent);
  transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.nut-port__chip--ai .nut-port__chiptop { font-size: 14px; }
.nut-port__chip--ai .nut-ic svg { width: 16px; height: 16px; }
.nut-port__chip--ai:hover { background: var(--accent); color: var(--on-accent); }
.nut-port__chipsub { display: block; font-size: 11px; color: var(--text-muted, var(--muted)); margin-top: 2px; line-height: 1.1; }
/* CHANGE 3 — one-tap shortcut accent flash (preset chip) */
.nut-port__chip.is-flash {
  background: var(--accent); color: var(--on-accent); border-color: var(--accent);
  transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
/* CHANGE 9 — portion-definition sub-label under the gram readout */
.nut-port__portiedef { display: block; font-size: 11px; color: var(--text-muted, var(--muted)); margin-top: 2px; }
/* CHANGE 8 — reset-to-default pill */
.nut-port__reset {
  display: inline-flex; align-items: center; gap: 5px; background: none;
  border: 1px solid var(--line); border-radius: var(--radius-pill); padding: 4px 10px;
  color: var(--text-muted, var(--muted)); font-size: 13px; cursor: pointer; font-family: inherit;
}
.nut-port__reset:hover { border-color: var(--accent); color: var(--accent); }
/* Same trap as .nut-port__numrow[hidden]: the author display:inline-flex above
   beats the UA [hidden] rule, so resetPill.hidden=true left the pill on screen. */
.nut-port__reset[hidden] { display: none; }
.nut-port__reset .nut-ic svg { width: 14px; height: 14px; }
/* CHANGE 5 — live daily kcal goal micro-bar */
.nut-port__goal { display: flex; align-items: center; gap: var(--space-2); margin: 0 0 var(--space-3); }
.nut-port__goal-lbl { font-size: 11px; color: var(--text-muted, var(--muted)); white-space: nowrap; }
.nut-port__goal-track { flex: 1 1 auto; height: 4px; border-radius: var(--radius-sm); background: var(--line); overflow: hidden; }
.nut-port__goal-fill { height: 100%; background: var(--accent); border-radius: var(--radius-sm); transition: width var(--dur-fast) var(--ease-standard); }
.nut-port__goal-num { font-size: 11px; color: var(--text-muted, var(--muted)); white-space: nowrap; font-variant-numeric: tabular-nums; }
/* CHANGE 4 — macro values fade-through on each gram change (perceptible, calm) */
@keyframes nutMpreFade { 0% { opacity: 0.25; } 100% { opacity: 1; } }
.nut-scale__preview .nut-mpre__num { animation: nutMpreFade var(--dur-fast) var(--ease-standard); }
/* CHANGE 7 — brief confirm pulse on TOEVOEGEN */
@keyframes nutSavePulse { 0% { transform: scale(1); } 50% { transform: scale(1.04); } 100% { transform: scale(1); } }
.nut-scale__save.is-pulse { animation: nutSavePulse var(--dur-fast) var(--ease-standard); }
/* CHANGE 6 — vitamines disclosure accordion */
.nut-vitacc { border: 1px solid var(--line); border-radius: var(--radius-sm); margin-bottom: var(--space-3); overflow: hidden; }
.nut-vitacc__head {
  display: flex; align-items: center; justify-content: space-between; width: 100%;
  min-height: 48px; padding: 0 var(--space-3); background: var(--surface); border: 0;
  color: var(--ink); font-size: 14px; font-family: inherit; cursor: pointer;
}
.nut-vitacc__lbl { font-weight: 600; }
.nut-vitacc__chev { color: var(--muted); transition: transform var(--dur-fast) var(--ease-standard); }
.nut-vitacc__chev svg { width: 18px; height: 18px; }
.nut-vitacc.is-open .nut-vitacc__chev { transform: rotate(180deg); }
.nut-vitacc__body { padding: 0 var(--space-3) var(--space-3); }
.nut-port__more { display: flex; justify-content: space-between; align-items: center; gap: var(--space-3); }
.nut-port__more--solo { justify-content: flex-end; }
.nut-port__link {
  background: none; border: 0; color: var(--accent); font-size: 13px; cursor: pointer; padding: 6px 0;
  font-family: inherit; display: inline-flex; align-items: center; gap: 5px;
}
.nut-port__link .nut-ic svg { width: 15px; height: 15px; }
.nut-port__exact { margin-top: var(--space-3); }
.nut-port__askprompt { margin: 0 0 var(--space-2); font-size: 13px; font-weight: 600; color: var(--ink); text-align: center; }

/* Goal mode segmented control */
.nut-seg { display: flex; gap: 0; border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden; margin-bottom: var(--space-4); }
.nut-seg__btn { flex: 1 1 0; background: none; border: 0; border-left: 1px solid var(--line); color: var(--ink-2); font-size: 14px; padding: var(--space-3) 0; cursor: pointer; }
.nut-seg__btn:first-child { border-left: 0; }
.nut-seg__btn.on { background: var(--accent); color: var(--on-accent); box-shadow: var(--glow-soft); }

/* Action sheet rows (meal menu) */
.nut-act { display: block; width: 100%; text-align: left; background: none; border: 0; border-top: 1px solid var(--line-2); color: var(--ink); font-size: 15px; padding: var(--space-4) 0; cursor: pointer; }
.nut-act:first-child { border-top: 0; }
.nut-act--danger { color: var(--danger); }

/* NUTRITION-PARTNER-MEAL-PROPOSE-SENDER-FLOW — sender surface. The marker is a
   glyph inside the meal head, never a row of its own; the picker reuses .nut-act
   so a friend row is the same tap target as every other sheet action. */
.nut-meal__prop { display: inline-flex; align-items: center; flex: 0 0 auto; margin-right: 2px; }
.nut-meal__prop .nut-ic,
.nut-meal__prop .nut-ic svg { width: 14px; height: 14px; }
.nut-meal__prop--pending  { color: var(--accent); }
.nut-meal__prop--accepted { color: var(--ok); }
.nut-meal__prop--declined { color: var(--danger); }
.nut-meal__prop--expired  { color: var(--muted); }
.nut-prop__summary { margin: 0 0 var(--space-2); color: var(--ink); font-size: 15px; line-height: 1.4; }
.nut-prop__consent { margin: 0 0 var(--space-4); color: var(--muted); font-size: 13px; line-height: 1.4; }
.nut-prop__status  { margin: 0; color: var(--muted); font-size: 14px; }
.nut-prop__err     { margin: 0 0 var(--space-3); color: var(--danger); font-size: 14px; line-height: 1.4; }
.nut-prop__friend  { min-height: 48px; }

/* NUTRITION-PARTNER-MEAL-ACCEPT-RECEIVER-FLOW — the recipient side.
   The banner is an invitation, not an alert: accent-outlined rather than
   filled, so a waiting proposal reads as something offered and never competes
   with the day's own numbers for attention. */
.nut-mp-banner {
  display: flex; flex-direction: column; gap: var(--space-2);
  margin: 0 0 var(--space-3);
}
.nut-mp-banner__row {
  display: flex; align-items: stretch; gap: 0;
  background: var(--surface);
  border: 1px solid var(--accent);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.nut-mp-banner__open {
  flex: 1 1 auto; min-height: 56px;
  display: flex; align-items: center; gap: var(--space-2);
  padding: 10px 12px; text-align: left;
  background: none; border: 0; cursor: pointer;
  color: var(--ink); font: inherit;
}
.nut-mp-banner__open .nut-ic,
.nut-mp-banner__open .nut-ic svg { width: 20px; height: 20px; flex: 0 0 auto; }
.nut-mp-banner__open .nut-ic { color: var(--accent); }
.nut-mp-banner__txt  { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.nut-mp-banner__lead {
  font-size: 15px; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nut-mp-banner__meta { font-size: 13px; color: var(--muted); }
.nut-mp-banner__hide {
  flex: 0 0 auto; width: 48px; min-height: 48px;
  display: flex; align-items: center; justify-content: center;
  background: none; border: 0; border-left: 1px solid var(--line);
  color: var(--muted); cursor: pointer;
}
.nut-mp-banner__hide .nut-ic,
.nut-mp-banner__hide .nut-ic svg { width: 16px; height: 16px; }

/* The accept sheet. */
.nut-mp__from { margin: 0 0 var(--space-3); color: var(--ink); font-size: 15px; line-height: 1.4; }

/* One-line portion strip (fz-lc-strip idiom): label + text chips, never icons —
   a measure is a number, and drawing it makes it read as a different kind of
   thing than the quantity on the row below it. */
.nut-mp__strip {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0 0 var(--space-3); padding: 8px 10px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.nut-mp__striplbl {
  font-size: 11px; font-weight: 700; color: var(--muted);
  text-transform: uppercase; letter-spacing: .06em; margin-right: 2px;
}
.nut-mp__chip {
  min-width: 44px; min-height: 36px; padding: 0 12px;
  background: transparent; color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  font: inherit; font-weight: 700; font-feature-settings: "tnum";
  cursor: pointer;
}
.nut-mp__chip.is-on {
  background: var(--accent); color: var(--on-accent); border-color: var(--accent);
}

/* NUTRITION-PORTION-THUMBS-FEEDBACK-PERSONAL — one strip under the measure chips.
   The thumbs are CONTROLS and live below the row; the chips themselves stay
   text-only. Modelled on .nut-mp__strip above so the sheet keeps one idiom. */
.nut-port__fb {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0 0 var(--space-3); padding: 8px 10px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
/* Same trap as .nut-port__numrow[hidden]: this author `display: flex` beats the
   UA [hidden] rule, so the thumbs strip stood under the bare multiplier chips
   even though the JS had never revealed it — the strip must only appear once
   AI-authored portions are actually on screen. */
.nut-port__fb[hidden] { display: none; }
.nut-port__fblbl {
  font-size: 11px; font-weight: 700; color: var(--muted);
  text-transform: uppercase; letter-spacing: .06em; margin-right: 2px;
}
.nut-port__fbbtn {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 36px; padding: 0 12px;
  background: transparent; color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  font: inherit; cursor: pointer;
}
.nut-port__fbbtn:disabled { opacity: .45; cursor: default; }
.nut-port__fbpick {
  flex: 1 0 100%; display: flex; flex-wrap: wrap; gap: 8px;
}
.nut-port__fbpick[hidden] { display: none; }
.nut-port__fbrm {
  min-height: 36px; padding: 0 12px;
  background: transparent; color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  font: inherit; cursor: pointer;
}
.nut-port__fbrm.is-on {
  background: var(--accent); color: var(--on-accent); border-color: var(--accent);
}
.nut-port__fbnote {
  flex: 1 0 100%; min-height: 60px; padding: 8px 10px; resize: vertical;
  background: var(--bg); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  font: inherit;
}
.nut-port__fbsend {
  min-width: 44px; min-height: 36px; padding: 0 14px;
  background: var(--accent); color: var(--on-accent);
  border: 1px solid var(--accent); border-radius: var(--radius-sm);
  font: inherit; font-weight: 700; cursor: pointer;
}
.nut-port__fbsend:disabled { opacity: .45; cursor: default; }
/* NUTRITION-RAGE-CLICK-FRICTION — the in-flight guards existed but were invisible, so
   a slow AI/server response read as a dead button and the user hammered it. Same
   spinner idiom as the meal-create controls (.nut-mealopt.is-loading). */
.nut-pest__send:disabled { opacity: .55; cursor: default; }
.nut-port__fbsend.is-loading, .nut-pest__send.is-loading {
  position: relative; color: transparent;
}
.nut-port__fbsend.is-loading::after, .nut-pest__send.is-loading::after {
  content: ''; position: absolute; top: 50%; left: 50%;
  width: 16px; height: 16px; margin: -8px 0 0 -8px;
  border: 2px solid var(--line); border-top-color: var(--on-accent);
  border-radius: 50%; animation: nut-mealspin var(--dur-loop) linear infinite;
}
/* NUTRITION-PORTION-REGENERATE-FROM-CORRECTION — reads as a text link, not a third
   button: it REPLACES the "Thanks" label in this strip rather than adding a control. */
.nut-port__fbregen {
  min-height: 36px; padding: 0 2px;
  background: transparent; color: var(--accent);
  border: 0; border-radius: var(--radius-sm);
  font: inherit; font-weight: 700; text-decoration: underline;
  cursor: pointer;
}
.nut-port__fbregen:disabled { opacity: .55; cursor: default; text-decoration: none; }

/* VOEDING-JIJ-TYPT-DE-PORTIE-EN-DIE-KRIJG-JE-PRECIES-ZO — the word field takes the
   full second line so a typed measure never gets squeezed into a two-character box;
   the gram field sits beside the button because it is optional. */
.nut-port__fbfix, .nut-port__fbfixg {
  min-height: 36px; padding: 0 10px;
  background: var(--bg); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  font: inherit;
}
.nut-port__fbfix  { flex: 1 0 100%; }
.nut-port__fbfixg { width: 84px; flex: 0 0 auto; }
.nut-port__fbfixsend {
  min-width: 44px; min-height: 36px; padding: 0 14px;
  background: var(--accent); color: var(--on-accent);
  border: 1px solid var(--accent); border-radius: var(--radius-sm);
  font: inherit; font-weight: 700; cursor: pointer;
}
.nut-port__fbfixsend:disabled { opacity: .55; cursor: default; }

/* Ghost lines — dimmed BECAUSE they are not in the log yet. The dimming is the
   message (same idiom as the workout player's prefill), so it stays a step
   quieter than ordinary secondary text and the dashed rule says "outline". */
.nut-mp__lines {
  border: 1px dashed var(--line); border-radius: var(--radius-sm);
  padding: 4px 10px; margin: 0 0 var(--space-2);
}
.nut-mp__line {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 0; border-bottom: 1px solid var(--line-2);
}
.nut-mp__line:last-child { border-bottom: 0; }
.nut-mp__line--ghost { opacity: .62; }
.nut-mp__lname { flex: 1 1 auto; min-width: 0; color: var(--ink); font-size: 14px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nut-mp__lqty  { flex: 0 0 auto; color: var(--muted); font-size: 13px; font-feature-settings: "tnum"; }
.nut-mp__lkcal { flex: 0 0 auto; color: var(--muted); font-size: 13px; font-feature-settings: "tnum"; }
.nut-mp__total { margin: 0 0 var(--space-3); color: var(--ink); font-size: 14px; font-weight: 600; }
.nut-mp__hint  { margin: 0 0 var(--space-3); color: var(--muted); font-size: 13px; line-height: 1.4; }
.nut-mp__err   { margin: 0; color: var(--danger); font-size: 14px; line-height: 1.4; }
.nut-mp__err:empty { display: none; }
.nut-mp__actions { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-3); }
.nut-mp__actions .btn { min-height: 48px; }

/* Provenance (§3.5) — one quiet line at the top of the meal body, so reading a
   log back weeks later still answers "did I measure this, or did someone else?" */
.nut-meal__via {
  display: flex; align-items: center; gap: 6px;
  padding: 4px 0 8px; color: var(--muted); font-size: 12px;
}
.nut-meal__via .nut-ic,
.nut-meal__via .nut-ic svg { width: 14px; height: 14px; }

/* Favorites list rows (Ship 2, v0.9.178) — reuse .nut-result for the tap target,
   add a trailing delete affordance. */
.nut-favrow { display: flex; align-items: stretch; border-top: 1px solid var(--line-2); }
.nut-favrow:first-child { border-top: 0; }
.nut-favrow .nut-result { border-top: 0; flex: 1 1 auto; }
.nut-favrow__del {
  flex: 0 0 auto; background: none; border: 0; color: var(--muted);
  font-size: 22px; line-height: 1; cursor: pointer; padding: 0 var(--space-3);
}
.nut-favrow__del:hover { color: var(--danger); }

/* Custom reusable foods (v0.9.182). Search results that are the user's own
   saved foods get a ★ tag + a trailing delete; the tap target reuses
   .nut-result inside a flex row (same shape as .nut-favrow). */
.nut-result__tag {
  margin-left: var(--space-2); font-size: 10px; font-weight: 600;
  color: var(--accent); text-transform: uppercase; letter-spacing: 0.04em;
  vertical-align: 1px;
}
.nut-result-row { display: flex; align-items: stretch; border-top: 1px solid var(--line-2); }
.nut-result-row:first-child { border-top: 0; }
.nut-result--inrow { border-top: 0; flex: 1 1 auto; }
.nut-result__del {
  flex: 0 0 auto; background: none; border: 0; color: var(--muted);
  font-size: 18px; line-height: 1; cursor: pointer; padding: 0 var(--space-3);
}
.nut-result__del:hover { color: var(--danger); }

/* NUTRITION-SERVING-SCALER-ALWAYS-IN-FLOW — inline per-row quick-add "+". A
   round accent-filled affordance on the right of a search result. Now rendered
   in the recipe/meal builder collect flow (meal.collect) and on the History rows
   of the typing search, where the portion is the user's OWN remembered one; it
   stays removed from generic results, where it instant-logged the default serving
   and let users skip the amount step. There, row-tap → serving scaler. */
.nut-result__quickadd {
  flex: 0 0 auto; align-self: center; margin-right: var(--space-2);
  width: 36px; height: 36px; border-radius: 50%; border: 0; cursor: pointer;
  background: var(--accent); color: var(--on-accent);
  display: inline-flex; align-items: center; justify-content: center;
}
.nut-result__quickadd:hover { background: var(--accent-2); }

/* Global "+" FAB on the Log tab — fixed bottom-right, above the tabbar, hidden
   while any nutrition sheet is open. Glyph uses --on-accent (contrast policy). */
.nut-fab {
  position: fixed; right: var(--space-4);
  bottom: calc(var(--tabbar-h) + var(--safe-bottom) + var(--space-4));
  width: 56px; height: 56px; border-radius: 50%; border: 0; cursor: pointer;
  background: var(--accent); color: var(--on-accent);
  display: inline-flex; align-items: center; justify-content: center;
  box-shadow: var(--elev-2);  /* allow-hex — neutral drop shadow, not a themed surface colour */
  z-index: 40;
}
body.fz-nutrition-sheet-open .nut-fab { display: none; }

/* Shared add-flow submenu — a 3-column labelled-icon grid in the bottom sheet. */
.nut-addmenu { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-3); margin-top: var(--space-3); }
.nut-addmenu__tile {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-2);
  padding: var(--space-4) var(--space-2); cursor: pointer;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg);
  color: var(--accent); text-align: center;
}
.nut-addmenu__tile:hover { border-color: var(--accent); }
.nut-addmenu__tile--disabled { opacity: 0.5; }
.nut-addmenu__tile .nut-ic svg { width: 24px; height: 24px; }

/* NUTRITION-ADD-FOOD-ADAPTIVE-TOP3-MEER — the user's own top-3 routes render as
   larger primary tiles in a two-column band (the #1 route spans the row); every
   demoted route keeps its original 3-column grid behind the "Meer" row. Modifier
   classes only — same .nut-addmenu__tile component throughout. */
.nut-addmenu--primary { grid-template-columns: repeat(2, 1fr); }
.nut-addmenu__tile--primary { padding: var(--space-5, 20px) var(--space-3); border-color: var(--accent); }
.nut-addmenu__tile--wide { grid-column: 1 / -1; }
.nut-addmenu__tile--primary .nut-ic svg,
.nut-addmenu__tile--primary .fz-icon { width: 30px; height: 30px; }
.nut-addmenu__tile--primary .nut-addmenu__lbl { font-size: 14px; font-weight: 600; color: var(--ink); }
.nut-addmenu__more {
  display: flex; align-items: center; justify-content: center; gap: var(--space-2);
  width: 100%; min-height: 44px; margin-top: var(--space-3); padding: var(--space-3);
  background: transparent; border: 1px solid var(--line); border-radius: var(--radius-pill);
  color: var(--ink-2); font-size: 13px; font-weight: 600; cursor: pointer;
}
.nut-addmenu__more:hover { border-color: var(--accent); }
.nut-addmenu__more-chev { transition: transform var(--dur-fast) var(--ease-standard); }
.nut-addmenu__more--open .nut-addmenu__more-chev { transform: rotate(180deg); }
.nut-addmenu--rest[hidden] { display: none; }

.nut-addmenu__lbl { font-size: 12px; line-height: 1.2; color: var(--ink-2); }
.nut-addmenu__bound {
  display: inline-block; align-self: flex-start;
  font-size: 12px; font-weight: 600; color: var(--ink);
  background: var(--line-2); border-radius: var(--radius-pill);
  padding: var(--space-1) var(--space-3); margin-bottom: var(--space-3);
}

/* VOEDING-DE-PLUS-KAN-OOK-EEN-DAG-TERUG — the day row at the top of the "+"
   sheet. Own block, classes included — never .nut-addmenu__bound (the per-meal
   chip on the SAME sheet) and deliberately NOT .nut-field__in either: the
   log-flow keeps a finished stage in the DOM hidden-but-alive, so borrowing the
   shared field class puts a hidden element first in every .nut-field__in sweep
   on this screen. The field styling is therefore restated here, matching
   .nut-field__in value for value including the 44px thumb-safe floor. */
.nut-logdate { display: flex; flex-direction: column; gap: var(--space-1); margin-bottom: var(--space-3); }
.nut-logdate__lbl { font-size: 12px; font-weight: 600; color: var(--ink-2); }
.nut-logdate__in {
  width: 100%; min-height: 44px; padding: var(--space-3); font-size: 16px; color: var(--ink);
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-md); box-sizing: border-box;
  transition: border-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard);
}
.nut-logdate__in:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring-focus); }
.nut-logdate__note { font-size: 12px; color: var(--ink-2); display: none; }
.nut-logdate__note.is-on { display: block; }

/* "Save to my foods" opt-in checkbox in the manual-entry dialog. */
.nut-reuse { display: flex; align-items: center; gap: var(--space-2); margin-top: var(--space-3); cursor: pointer; }
.nut-reuse__box { width: 18px; height: 18px; accent-color: var(--accent); flex: 0 0 auto; }
.nut-reuse__lbl { font-size: 13px; color: var(--ink-2); }

/* NUTRITION-MANUAL-PACK-WEIGHT (v0.9.327): pack-weight hint + two-button row. */
.nut-manual__hint { font-size: 12px; font-style: italic; color: var(--muted); margin-top: calc(-1 * var(--space-2)); margin-bottom: var(--space-3); }
/* VOEDING-NA-HET-ETEN-WEEG-JE-WAT-ER-OVER-IS — this row grew from two buttons to
   three (Back · Skip · Save). `.btn` is uppercase with 0.18em tracking, so each
   label's own minimum width is large; without wrapping the row overflowed 390px
   and pushed the PRIMARY button half off screen. Wrap instead of shrinking: the
   font size stays, `width: auto` lets flex-basis come from the label (a short
   pair still shares one line) and min-width keeps a wrapped button tappable. */
.nut-manual__btnrow { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-4); }
.nut-manual__btnrow > .nut-scale__save { flex: 1 1 auto; width: auto; min-width: 8.5rem; margin-top: 0; }

/* Add-food alternatives row (Scan / Manual) + barcode scanner (Ship 2, v0.9.175) */
.nut-altrow { display: flex; gap: var(--space-3); margin-top: var(--space-3); }
.nut-altbtn {
  flex: 1 1 0; min-width: 0; background: none; border: 1px solid var(--line);
  border-radius: var(--radius-md); color: var(--accent); font-size: 13px; cursor: pointer;
  padding: var(--space-3) var(--space-2); text-align: center;
  /* v0.9.195 — vertical icon-button: icon/glyph on its own row, label below.
     Was an inline row, which made the icon float beside 2-line-wrapped labels
     like an emoji typed into the text (Jayy 2026-05-26). */
  display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px;
}
.nut-altbtn:hover { border-color: var(--accent); }
.nut-altbtn__glyph { font-size: 19px; line-height: 1; display: block; }
.nut-altbtn__lbl { line-height: 1.2; }
.nut-altbtn .nut-ic svg { width: 21px; height: 21px; }

/* v0.9.192 — inline SVG icons (no emoji). Sized to the surrounding text. */
.nut-ic { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; }
.nut-ic svg { width: 1.05em; height: 1.05em; display: block; }
/* nut-ns__by stays block so its longer lock-note sentence wraps; the icon aligns
   inline with the first line. */
.nut-ns__by .nut-ic { vertical-align: -0.12em; margin-right: 4px; }
.nut-ns__by .nut-ic svg { width: 13px; height: 13px; }
.nut-result__del .nut-ic svg { width: 18px; height: 18px; }
.cal-nut__icon svg { width: 1em; height: 1em; vertical-align: -0.15em; }
.nut-manualbtn { margin-top: 0; } /* override the standalone-link spacing when in the row */

.nut-scan {
  position: relative; width: 100%; aspect-ratio: 4 / 3; background: #000;  /* allow-hex — barcode viewfinder needs absolute black behind the live video frame */
  border-radius: var(--radius-md); overflow: hidden; margin-bottom: var(--space-3);
}
.nut-scan__video { width: 100%; height: 100%; object-fit: cover; display: block; }
.nut-scan__frame {
  position: absolute; left: 12%; right: 12%; top: 50%; transform: translateY(-50%);
  height: 52%; border: 2px solid var(--accent); border-radius: var(--radius-md);
  box-shadow: 0 0 0 100vmax var(--scrim); pointer-events: none;
}
/* Torch toggle — only rendered visible when the camera track reports `torch`. */
.nut-scan__torch {
  position: absolute; top: 8px; right: 8px; z-index: 2;
  width: 36px; height: 36px; display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 999px; padding: 0; cursor: pointer;
  background: rgba(0,0,0,.55); color: #fff;  /* allow-hex — sits on the live camera frame, not on a themed surface */
}
.nut-scan__torch[hidden] { display: none; }
.nut-scan__torch[aria-pressed="true"] { background: var(--accent); color: var(--on-accent); }
.nut-scan__torch-ic svg { width: 18px; height: 18px; display: block; }
.nut-scan__video { cursor: pointer; } /* tap-to-focus target */
/* SCAN-GEBRUIK-DE-INGEBOUWDE-LEZER — tap-to-focus was invisible; one line says so. */
.nut-scan__hint { color: var(--muted); font-size: 12px; text-align: center; margin: 0; padding: 0 0 var(--space-1); }
.nut-scan__status { color: var(--muted); font-size: 13px; text-align: center; padding: var(--space-2) 0; }
.nut-scan__status.is-err { color: var(--danger); }
.nut-scan__samsung { color: var(--muted); font-size: 13px; line-height: 1.45; text-align: center; padding: var(--space-2) var(--space-3); margin: 0; }

/* SCAN-LEEST-WEER — the two ways out that appear once the scanner has read
   nothing for 12s. Hidden until then: an escape hatch offered up front reads as
   "this probably will not work". */
.nut-scan__escape { border-top: 1px solid var(--line); margin-top: var(--space-3); padding-top: var(--space-3); }
.nut-scan__escape-hd { color: var(--muted); font-size: 13px; text-align: center; margin: 0 0 var(--space-2); }
.nut-scan__escape-row { display: flex; gap: var(--space-2); align-items: center; }
.nut-scan__escape-input { flex: 1 1 auto; min-width: 0; padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); color: var(--ink); font-size: 15px; }
.nut-scan__escape-input.is-err { border-color: var(--danger); }
.nut-scan__escape-go { flex: 0 0 auto; margin-top: 0; }
.nut-scan__escape-name { display: block; width: 100%; text-align: center; }

/* v0.9.315 — Scanner permission-denied recovery surface (ANDROID-CAMERA-PERMISSION-RECOVERY).
   Replaces the dead-end red `.is-err` string with a calm, platform-detected
   guide. Headline is NOT red — red stays reserved for transient errors. */
.nut-camperm { display: flex; flex-direction: column; gap: var(--space-3); }
.nut-camperm__hd { font-size: 14px; font-weight: 600; color: var(--ink); line-height: 1.35; margin: 0; }
.nut-camperm__steps { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 10px; }
.nut-camperm__steps li { display: flex; gap: 10px; align-items: flex-start; }
.nut-camperm__num {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%;
  background: var(--bg); border: 1px solid var(--line); color: var(--ink-2);
  font-size: 12px; font-weight: 600; display: inline-flex;
  align-items: center; justify-content: center; margin-top: 1px;
}
.nut-camperm__txt {
  flex: 1 1 auto; min-width: 0; font-size: 13px; color: var(--ink); line-height: 1.4;
}
.nut-camperm__txt b { font-weight: 600; color: var(--ink); }
.nut-camperm__glyph {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border: 1px solid var(--line); border-radius: var(--radius-md);
  color: var(--ink-2); background: var(--bg);
  vertical-align: -0.35em; margin: 0 2px;
}
.nut-camperm__glyph svg { width: 14px; height: 14px; display: block; }
.nut-camperm__retry { gap: 8px; margin-top: 2px; }
.nut-camperm__cta-ic { display: inline-flex; align-items: center; justify-content: center; }
.nut-camperm__cta-ic svg { width: 16px; height: 16px; display: block; }

/* v0.9.315 — primary variant of .nut-altbtn (accent fill, --on-accent text).
   Used by the scanner Try-again CTA and any other surface where one of the
   alternative-row buttons is the headline action. */
.nut-altbtn.is-primary {
  background: var(--accent); border-color: var(--accent); color: var(--on-accent);
  flex-direction: row;
}
.nut-altbtn.is-primary:hover { background: var(--accent-2); border-color: var(--accent-2); }

/* NUTRITION-ADD-TO-DAY-FLOW-BUTTON-STYLE-HARMONIZE — the New-meal button swaps
   between the filled step-1 idiom (empty day: it is the only way forward) and
   the quiet alt-button (meal rows present). Only the filled state needs a row
   layout; the alt-button keeps its stacked glyph/label. */

/* v0.9.1255 — NUTRITION-M3-WAVE-5: recurrence + photo/voice/AI-estimator chrome
   moved off inline style.cssText onto tokenised classes so the Dag/Nacht presets
   reach them. DOM + behaviour unchanged; radius/type/spacing snap to wave-1 tokens
   (marginal tightening only); grey fallbacks become theme-aware --line/--line-2. */
.nut-rec__src { font-weight: 600; margin-bottom: var(--space-4); }
.nut-rec__chiprow { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-4); }

.nut-field__lblrow { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); }
textarea.nut-field__in { resize: vertical; overflow: auto; }

.nut-pest__thumb {
  border-radius: var(--radius-lg);
  background-size: cover; background-position: center;
  margin-bottom: var(--space-4);
}
.nut-pest__thumb--result { margin-bottom: var(--space-3); }
.nut-pest__loading { display: flex; align-items: center; justify-content: center; gap: var(--space-2); padding: var(--space-4) 0; }
.nut-pest__note { font-size: var(--text-body-sm); line-height: var(--lh-body-sm); opacity: .7; margin: var(--space-2) 0 0; }
.nut-pest__hint { opacity: .75; margin: 0 0 var(--space-4); }
.nut-pest__chat { margin-top: var(--space-4); border-top: 1px solid var(--line); padding-top: var(--space-3); }
.nut-pest__chathist { max-height: 120px; overflow-y: auto; display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-2); }
.nut-pest__chatrow { display: flex; align-items: center; gap: var(--space-2); }
.nut-pest__send {
  flex: 0 0 auto; width: 40px; height: 40px;
  border: 0; border-radius: 50%;
  background: var(--accent); color: var(--on-accent);
  font-size: 18px;
}
.nut-pest__turn { font-size: 13px; padding: 6px 10px; border-radius: var(--radius-lg); max-width: 85%; }
.nut-pest__turn--ai { align-self: flex-start; background: var(--line-2); }
.nut-pest__turn--me { align-self: flex-end; background: var(--accent); color: var(--on-accent); }

/* ─── Nutrition sub-tab strip + Library (v0.9.198, NUTRITION_LIBRARY_SPEC.md) ─── */
/* Always-visible strip: Log + the three Library sections. Underline marks active;
   scrolls horizontally on narrow phones rather than wrapping. */
.nut-tabstrip {
  display: flex; gap: var(--space-4); align-items: center;
  border-bottom: 1px solid var(--line); margin-bottom: var(--space-4);
  overflow-x: auto; -webkit-overflow-scrolling: touch;
}
.nut-tabstrip::-webkit-scrollbar { display: none; }
.nut-tab {
  flex: 0 0 auto; background: none; border: 0; cursor: pointer; font-family: inherit;
  color: var(--ink-2); font-size: 15px; padding: var(--space-3) 2px;
  border-bottom: 2px solid transparent; margin-bottom: -1px; white-space: nowrap;
  /* WAVE-2 touch target: ≥44pt hitbox in BOTH dimensions; label stays centered. */
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; min-width: 44px;
}
.nut-tab.is-active { color: var(--ink); font-weight: 600; border-bottom-color: var(--accent); }

.nut-lib { display: flex; flex-direction: column; }
.nut-lib__search { margin-bottom: var(--space-3); }
.nut-lib__actions { display: flex; gap: var(--space-3); margin-bottom: var(--space-3); }
.nut-lib__actions:empty { display: none; }

/* VOEDING-DE-COMBOTAB-LEGT-UIT-WAT-EEN-COMBO-IS (FINDING 6) — the orientation
   at the top of the Combo's tab. DELIBERATELY NOT A CARD: no border, no
   background, no icon, no illustration. The neighbouring recipe audit found
   that chrome at the top pushes the content away, and that verdict still
   stands; what buys the exception here is that the instrument itself is
   unknown, and an exception for two lines of prose is not an exception for a
   banner. The budget is measured, not argued: the first combo row has to stay
   above the fold on a phone. */
.nut-combointro { margin-bottom: var(--space-3); }
.nut-combointro__line { margin: 0; font-size: 13px; line-height: 1.4; color: var(--ink-2); }
/* Line two is the pay-off, not the definition — a shade quieter so the eye
   reads them in that order. */
.nut-combointro__line + .nut-combointro__line { color: var(--muted); margin-top: 2px; }
.nut-combointro__more {
  display: inline-block; margin-top: 4px; padding: 6px 0;
  background: none; border: 0; color: var(--accent);
  font-size: 13px; font-weight: 500; font-family: inherit; cursor: pointer;
}
.nut-combointro__more:hover { color: var(--accent-2); }

/* FINDING 7 — the quiet half of the pair. "Herhaalde combo's" is beheer, so it
   drops out of the button row entirely and becomes a text action underneath the
   one primary button. Still a real target: 44px tall, however light it looks. */
.nut-libmanage { margin-bottom: var(--space-2); }
.nut-libtext {
  display: inline-flex; align-items: center; min-height: 44px;
  background: none; border: 0; padding: 0; color: var(--accent);
  font-size: 14px; font-weight: 500; font-family: inherit; cursor: pointer;
}
.nut-libtext:hover { color: var(--accent-2); }
.nut-libaction {
  flex: 1 1 0; background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-sm);
  color: var(--accent); font-size: 14px; font-weight: 500; padding: 12px 14px; cursor: pointer; font-family: inherit;
}
.nut-libaction:hover { border-color: var(--accent); background: var(--accent-light); }
.nut-lib__list { margin-top: var(--space-2); }

/* Library list row: tappable body (open/preview) + trailing "+" (add to a day). */
.nut-libitem { display: flex; align-items: stretch; border-top: 1px solid var(--line-2); }
.nut-libitem:first-child { border-top: 0; }
/* VOEDING-DE-COMBOTAB-LEGT-UIT-WAT-EEN-COMBO-IS (FINDING 8) — the body was plain
   text sitting next to a "+", with nothing between them: on a touch screen
   there is no hover to reveal they are two separate targets. This hairline IS
   that boundary, and because .nut-libitem is align-items:stretch it runs the
   full height of the row instead of floating beside one line of it. */
.nut-libitem__main {
  flex: 1 1 auto; min-width: 0; text-align: left; background: none; border: 0;
  border-right: 1px solid var(--line-2);
  padding: var(--space-3) var(--space-3) var(--space-3) 0;
  cursor: pointer; font-family: inherit;
  display: flex; align-items: center; gap: var(--space-2);
}
.nut-libitem__main:hover { background: var(--accent-light); }
.nut-libitem__txt { flex: 1 1 auto; min-width: 0; }
/* The very chevron the meal slots already use for "this opens", so the two
   places say it the same way. Muted on purpose: it is an aanduiding, not a
   second thing competing with the name of the combo for the eye. */
.nut-libitem__chev { flex: 0 0 auto; display: inline-flex; align-items: center; color: var(--muted); }
.nut-libitem__name { font-size: var(--text-title); line-height: var(--lh-title); color: var(--ink); }
/* One line, ellipsised: a combo's item list can be far wider than the row. */
.nut-libitem__sub { font-size: 12px; color: var(--muted); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* VOEDING-EEN-COMBO-VIND-JE-OOK-OP-WAT-ERIN-ZIT — the ingredient that earned
   this row its place in the results; the search moves it to the front of the
   line above, because that line is clipped. Weight + ink colour, deliberately
   NOT a highlight fill: a background behind 12px --muted text lowers the
   contrast, which is the opposite of what marking a hit is for. */
.nut-libitem__hit { color: var(--ink); font-weight: 600; }
/* VOEDING-DE-COMBOTAB-LEGT-UIT-WAT-EEN-COMBO-IS (FINDING 8) — MEASURED, NOT
   GUESSED. The audit suspected this target was too small; the device settled it:
   on an iPhone-sized viewport the button rendered 40 x 85 px. The HEIGHT was
   never the problem (the row is align-items:stretch, so the button is as tall as
   the row) — the WIDTH was, by exactly 4px, because 2 x var(--space-3) around a
   glyph narrower than its own font-size adds up to 40. min-width closes that gap
   without touching the padding, so nothing else in the row shifts; the text
   column beside it is min-width:0 and simply ellipsises 4px earlier. 44px is the
   AAA floor (WCAG 2.2 2.5.8) this project holds itself to. */
.nut-libitem__add {
  flex: 0 0 auto; background: none; border: 0; color: var(--accent);
  font-size: 26px; line-height: 1; cursor: pointer; padding: 0 var(--space-3);
  min-width: 44px;
}
.nut-libitem__add:hover { color: var(--ink); }

/* v0.9.348 SHARED-RECIPES-AND-FOODS — per-record share/private lock on a
   library row. The icon doubles as the status indicator AND the toggle:
   closed lock = private (default for existing rows, post-GDPR backfill),
   open lock = shared with the Sassu pool. The icon swaps state immediately
   on tap (optimistic); the colour shift reinforces the new state. */
.nut-libitem__lock {
  flex: 0 0 auto; background: none; border: 0; cursor: pointer;
  padding: 0 var(--space-2); color: var(--muted); display: inline-flex;
  align-items: center; justify-content: center;
}
.nut-libitem__lock .nut-ic { width: 18px; height: 18px; }
.nut-libitem__lock:hover { color: var(--ink); }
.nut-libitem__lock--shared { color: var(--accent); }
.nut-libitem__lock--shared:hover { color: var(--accent-2); }

/* NUTRITION-RECIPE-COOK-MODE-PANTRY-DEDUCTION — icon-only "cook this" action on an
   owned recipe row. Frying-pan glyph; opens the cook sheet where the amounts
   actually used are deducted from stock. Sits between the share/lock and the +. */
.nut-libitem__cook {
  flex: 0 0 auto; background: none; border: 0; cursor: pointer;
  padding: 0 var(--space-2); color: var(--muted); display: inline-flex;
  align-items: center; justify-content: center;
}
.nut-libitem__cook .nut-ic { width: 18px; height: 18px; }
.nut-libitem__cook:hover { color: var(--accent); }

/* NUTRITION-RECIPES-CATEGORIES-AND-DRAG-SORT — user-defined subcategory buckets
   + drag-drop sort. Secondary "+ Create a category" action sits beside the
   accent recipe-create button (muted ink to keep the recipe action primary). */
.nut-libaction--secondary { color: var(--muted); }
.nut-libaction--secondary:hover { color: var(--accent); }

/* VOEDING-KOKEN-ZONDER-RECEPT-WORDT-DE-GROTE-KNOP-BOVENAAN — the one primary
   action of the Koken tab. Full width and accent-filled above the search field,
   so the outlined "+ create" pair below reads as the quieter second choice
   without either of them changing a class. Text rides --on-accent (luminance-
   derived per brand colour), never a hardcoded white. */
.nut-cookdoor {
  display: block; width: 100%; text-align: left;
  background: var(--accent); color: var(--on-accent);
  border: 0; border-radius: var(--radius-sm);
  padding: 14px 16px; margin-bottom: var(--space-3);
  cursor: pointer; font-family: inherit;
}
.nut-cookdoor:active { filter: brightness(0.94); }
.nut-cookdoor__lbl {
  display: block; color: var(--on-accent);
  font-size: var(--text-title); line-height: var(--lh-title); font-weight: 600;
}
/* Not decoration: this line carries the reason to press, so it wraps rather
   than truncates. */
.nut-cookdoor__sub {
  display: block; margin-top: 2px; color: var(--on-accent); opacity: 0.85;
  font-size: 12px; line-height: 1.35;
}

/* The heading over the user's OWN recipes, below the community search panel. */
.nut-ownhead { margin-top: var(--space-3); }
.nut-ownhead__title {
  margin: 0; color: var(--ink);
  font-size: var(--text-title); line-height: var(--lh-title); font-weight: 600;
}
.nut-ownhead__sub { margin-top: 2px; font-size: 12px; color: var(--muted); }

/* VOEDING-MIJN-KOOKBOEK-BEGINT-BIJ-MIJN-EIGEN-RECEPTEN — the count rides INSIDE
   the title so it reads as one phrase ("Mijn recepten 12"), not as a badge that
   has to be re-parsed. Muted and lighter than the title: it answers "how many",
   it does not compete for the eye. Hidden at zero, because "Mijn recepten 0"
   says the same thing as the empty state one line below and says it worse.
   [hidden] needs stating: any class carrying its own display beats the UA rule. */
.nut-ownhead__count { margin-left: 8px; color: var(--muted); font-size: 13px; font-weight: 500; }
.nut-ownhead__count[hidden] { display: none; }

/* VOEDING-WAT-JE-NET-KOOKTE-STAAT-BOVENAAN — the shelf of pans still on the go.
   Jayy asked for a SEPARATION between his fixed recipes and what he cooked, so the
   shelf closes with one hairline: the heading below it then reads as a different
   place instead of a third line of the same list. Nothing else — the rows are the
   ordinary recipe cards and must not start looking like a second kind of recipe. */
.nut-recent { padding-bottom: var(--space-3); border-bottom: 1px solid var(--line); }
.nut-recent .recipe-category__recipes { margin-top: var(--space-2); }

/* The community search panel, moved to the FOOT of both library tabs. A single
   hairline plus air — the spec allows a rule OR a wide margin, not both, so the
   panel's own bottom margin is zeroed here: it is the last thing on the tab and
   a trailing gap under a divider reads as an unfinished section. */
.nut-libcommunity {
  margin-top: var(--space-4); padding-top: var(--space-4);
  border-top: 1px solid var(--line);
}
.nut-libcommunity .nut-shared { margin-bottom: 0; }

/* "+ Categorie aanmaken" after its demotion out of the button row: a quiet text
   row under the LAST category, where ordering is actually being done. Not a
   button shape — it must not read as a peer of the one primary action up top. */
.nut-catadd {
  display: block; width: 100%; text-align: left;
  background: none; border: 0; padding: 12px 2px;
  color: var(--muted); font-size: 13px; cursor: pointer; font-family: inherit;
}
.nut-catadd:hover { color: var(--accent); }
.nut-catadd[hidden] { display: none; }

/* VOEDING-IN-DE-KALENDER-ZIE-JE-WAT-JE-KOOKTE — "Wat heb ik gekookt?" onderaan de
   lijst. Exact de vorm van .nut-catadd hierboven, en met opzet: het zijn allebei
   stille uitgangen onder de lijst, en een eigen vorm zou een derde gewicht
   toevoegen aan een tab die er al twee heeft (de kookdeur en "+ Recept maken"). */
.nut-cookhist {
  display: block; width: 100%; text-align: left;
  background: none; border: 0; padding: 12px 2px;
  color: var(--muted); font-size: 13px; cursor: pointer; font-family: inherit;
}
.nut-cookhist:hover { color: var(--accent); }

/* Mijn Eten's single create action gets the SAME primary form as the Koken tab's
   cook door: the two tabs must rhyme in build, not only in content. Declared
   after .nut-libaction:hover so the hover pair below actually wins it back. */
.nut-libaction--primary {
  background: var(--accent); border-color: var(--accent); color: var(--on-accent);
}
.nut-libaction--primary:hover {
  background: var(--accent); border-color: var(--accent); color: var(--on-accent);
  filter: brightness(0.94);
}

/* The offer to refill a pan the phone pushed away, inside the empty cook list. */
.nut-ric__resume { margin-top: var(--space-2); }

.recipes-categories-list { list-style: none; margin: var(--space-2) 0 0; padding: 0; }
.recipe-category { list-style: none; margin-bottom: var(--space-4); }
.recipe-category.dragging { opacity: 0.5; }
.recipe-category__header {
  position: sticky; top: 0; z-index: 1;
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) 0; background: var(--bg);
  border-bottom: 1px solid var(--line);
}
.recipe-category__name {
  flex: 1 1 auto; min-width: 0; margin: 0;
  font-size: 13px; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.recipe-category__count {
  flex: 0 0 auto; font-size: 12px; color: var(--muted);
  background: var(--line-2); border-radius: var(--radius-pill);
  padding: 1px 8px; min-width: 20px; text-align: center;
}
.recipe-category__drag,
.recipe-category__edit {
  flex: 0 0 auto; background: none; border: 0; cursor: pointer;
  color: var(--muted); display: inline-flex; align-items: center;
  justify-content: center; min-width: 44px; min-height: 44px; padding: 0;
}
.recipe-category__drag { cursor: grab; touch-action: none; }
.recipe-category__edit:hover { color: var(--accent); }
.recipe-category__drag .nut-ic,
.recipe-category__edit .nut-ic { width: 18px; height: 18px; }

.recipe-category__recipes { list-style: none; margin: 0; padding: 0; min-height: 12px; }
.recipe-category--uncategorized .recipe-category__name { font-style: italic; }

.recipe-row { display: flex; align-items: stretch; border-top: 1px solid var(--line-2); }
.recipe-row:first-child { border-top: 0; }
.recipe-row.dragging { opacity: 0.5; }
.recipe-row .nut-libitem { flex: 1 1 auto; min-width: 0; border-top: 0; }
.recipe-row__drag {
  flex: 0 0 auto; background: none; border: 0; cursor: grab;
  color: var(--muted); display: inline-flex; align-items: center;
  justify-content: center; min-width: 44px; min-height: 44px; padding: 0;
  touch-action: none;
}
.recipe-row__drag:hover { color: var(--ink); }
.recipe-row__drag .nut-ic { width: 18px; height: 18px; }

/* COACH-RECIPE-SHARE-PUSH-TO-CLIENT — coach-only secondary "Deel" button on a
   recipe row (only rendered for a real coach, not in acting-as mode). Subtle
   so the primary "+" add action stays dominant. */
.recipe-row__share-btn {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: var(--space-1);
  background: none; border: 0; cursor: pointer; color: var(--muted);
  padding: 0 var(--space-2); font-size: 13px; min-height: 44px;
}
.recipe-row__share-btn:hover { color: var(--accent); }
.recipe-row__share-btn .nut-ic { width: 16px; height: 16px; }

/* ─── VOEDING-EEN-RECEPT-IS-EEN-KAART-MET-EEN-KOOKKNOP ───────────────────────
   A recipe is a CARD with one dominant action, not a row with six wordless icons
   of equal weight (audit 2026-09-12, F1/F2). The element keeps `.recipe-row` too
   — the drag code reads that selector out of the live DOM — so every declaration
   that contradicts the row layout is written as `.recipe-row.recipe-card` and
   lives AFTER the row block above. Combos and Foods stay rows, untouched. */
.recipe-row.recipe-card {
  display: block;
  border-top: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  padding: var(--space-2) var(--space-2) var(--space-3);
  margin-bottom: var(--space-2);
}
.recipe-card__head { display: flex; align-items: flex-start; gap: var(--space-1); }
/* The grip keeps its own class (and its touch-action: none) from the row block.
   flex-start, not stretch: on a two-line card a centred grip drifts down to the
   macro line and reads as a handle for THAT, not for the card. */
.recipe-card__head > .recipe-row__drag { align-self: flex-start; }

/* F3 — the biggest target opens the card to LOOK, never to edit. */
.recipe-card__toggle {
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: flex-start; gap: var(--space-2);
  background: none; border: 0; cursor: pointer; color: var(--ink);
  text-align: left; padding: var(--space-2) 0; min-height: 44px;
}
.recipe-card__texts { flex: 1 1 auto; min-width: 0; display: block; }
.recipe-card__name {
  display: block; font-size: 17px; font-weight: 600; line-height: 1.25;
  overflow-wrap: anywhere;
}
.recipe-card__macros {
  display: block; margin-top: 2px; font-size: 13px; color: var(--muted);
  line-height: 1.35;
}
.recipe-card__cooked {
  display: block; margin-top: 2px; font-size: 12px; color: var(--muted);
}
.recipe-card__sharedby {
  display: inline-flex; align-items: center; gap: var(--space-1);
  margin-top: var(--space-1); font-size: 12px; color: var(--muted);
}
.recipe-card__sharedby .nut-ic { width: 13px; height: 13px; }
.recipe-card__chev {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  color: var(--muted); margin-top: 2px;
  transition: transform var(--dur-fast) var(--ease-standard);
}
.recipe-card__chev .nut-ic { width: 18px; height: 18px; }
.recipe-card.is-open .recipe-card__chev { transform: rotate(180deg); }

.recipe-card__more {
  flex: 0 0 auto; background: none; border: 0; cursor: pointer; color: var(--muted);
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px; padding: 0;
}
.recipe-card__more:hover { color: var(--ink); }
.recipe-card__more .nut-ic { width: 20px; height: 20px; }

/* F1/F2 — exactly two actions, both spelled out, equal width, one dominant. */
.recipe-card__actions { display: flex; gap: var(--space-2); margin-top: var(--space-1); }
.recipe-card__btn {
  flex: 1 1 0; min-width: 0; height: 42px;
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  background: var(--accent); border: 1px solid var(--accent); color: var(--on-accent);
  border-radius: var(--radius-sm); cursor: pointer;
  font-size: 14px; font-weight: 600; padding: 0 var(--space-2);
  transition: background var(--dur-fast) var(--ease-standard);
}
.recipe-card__btn:hover { background: var(--accent-2); border-color: var(--accent-2); }
.recipe-card__btn .nut-ic { width: 17px; height: 17px; flex: 0 0 auto; }
.recipe-card__btnlbl { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.recipe-card__btn--ghost {
  background: transparent; border-color: var(--line); color: var(--ink); font-weight: 500;
}
.recipe-card__btn--ghost:hover { background: var(--surface-muted); border-color: var(--ink); }

/* Expanded body: ingredients + notes, lazily fetched once per card per mount. */
.recipe-card__panel { margin-top: var(--space-3); }
.recipe-card__ings { list-style: none; margin: 0; padding: 0; }
.recipe-card__ing {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-3); padding: var(--space-1) 0; font-size: 14px;
  border-top: 1px solid var(--line-2);
}
.recipe-card__ing:first-child { border-top: 0; }
.recipe-card__ingname { min-width: 0; overflow-wrap: anywhere; }
.recipe-card__ingamt { flex: 0 0 auto; color: var(--muted); font-variant-numeric: tabular-nums; }
.recipe-card__notes {
  margin: var(--space-2) 0 0; font-size: 13px; color: var(--muted);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.recipe-card__panelmsg { margin: 0; font-size: 13px; color: var(--muted); }
.recipe-card__skel { display: grid; gap: var(--space-2); }
.recipe-card__skelline {
  display: block; height: 12px; border-radius: var(--radius-pill);
  background: var(--line-2);
}
.recipe-card__skelline:nth-child(2) { width: 80%; }
.recipe-card__skelline:nth-child(3) { width: 60%; }

.recipe-card__editwrap { margin-top: var(--space-3); }
.recipe-card__editlink {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--accent); font-size: 14px; font-weight: 600;
  text-align: left; min-height: 32px;
}
.recipe-card__editlink:hover { text-decoration: underline; }
.recipe-card__editnote { margin: 2px 0 0; font-size: 12px; color: var(--muted); }

/* VOEDING-EEN-GEIMPROVISEERD-GERECHT-VERDWIJNT-ALS-DE-PAN-OP-IS — the keep decision on
   a dish that was improvised. Deliberately quieter than the two action buttons above it
   (ghost, not accent): keeping is a one-off, Koken is what you came for. No border, no
   tint, no second card-within-a-card — this is a dish like any other, it just has not
   been promised a future yet. */
.recipe-card__keepwrap { margin-top: var(--space-3); }
.recipe-card__keep {
  display: inline-flex; align-items: center; gap: var(--space-2);
  min-height: 36px; padding: 0 var(--space-3);
  background: none; border: 1px solid var(--line); border-radius: var(--radius-pill);
  color: var(--ink); font-size: 14px; font-weight: 600; cursor: pointer;
}
.recipe-card__keep:disabled { opacity: 0.55; cursor: default; }
.recipe-card__keepnote { margin: var(--space-1) 0 0; font-size: 12px; color: var(--muted); }

/* The ⋯ sheet: named actions, each with the sentence that says what it does. */
.recipe-more { display: block; }
.recipe-more__item {
  width: 100%; text-align: left; background: none; cursor: pointer;
  border: 0; border-bottom: 1px solid var(--line-2);
  padding: var(--space-3) 0; display: flex; flex-direction: column; gap: 2px;
  color: var(--ink);
}
.recipe-more__item:last-child { border-bottom: 0; }
.recipe-more__label { font-size: 15px; font-weight: 600; }
.recipe-more__note { font-size: 12px; color: var(--muted); }

/* F4 — the two verbs, spoken out loud at both entrances. */
.nut-ric__scope,
.nut-rb__scope {
  margin: 0 0 var(--space-2); font-size: 13px; color: var(--muted); line-height: 1.4;
}

/* Client-side attribution chip: shown on a recipe a coach pushed to this user. */
.recipe-row__shared-badge {
  display: inline-flex; align-items: center; gap: var(--space-1);
  margin-top: var(--space-1); padding: 2px var(--space-2);
  font-size: 12px; color: var(--muted);
  border-left: 2px solid var(--accent);
}
.recipe-row__shared-badge .nut-ic { width: 13px; height: 13px; }

/* Client-picker bottom-sheet (reuses the .mo-sheet shell via buildSheet). */
.recipe-share-search {
  width: 100%; box-sizing: border-box; margin: var(--space-2) 0;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--line-2); border-radius: var(--radius-sm);
  background: var(--bg); color: var(--ink); font-size: 15px;
}
.recipe-share-client-list { list-style: none; margin: 0; padding: 0; }
.recipe-share-client { list-style: none; }
.recipe-share-client__btn {
  width: 100%; text-align: left; background: none; cursor: pointer;
  border: 0; border-bottom: 1px solid var(--line-2);
  padding: var(--space-3); display: flex; flex-direction: column;
  gap: 2px; color: var(--ink);
}
.recipe-share-client__btn:hover { background: var(--bg); }
.recipe-share-client__name { font-weight: 600; }
.recipe-share-client__email { font-size: 13px; color: var(--muted); }
.recipe-share-empty { padding: var(--space-3); color: var(--muted); text-align: center; }

/* Shared-pool search panel — mounted above My Recipes / My Foods. Folds back
   to a 1-line header when empty so it never crowds the user's own library. */
.nut-shared {
  background: var(--bg);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-sm);
  padding: var(--space-3);
  margin-bottom: var(--space-4);
}
.nut-shared__head {
  display: flex; align-items: center; gap: 6px;
  color: var(--muted); font-size: 12px; font-weight: 500;
  text-transform: uppercase; letter-spacing: .04em;
  margin-bottom: var(--space-2);
}
.nut-shared__head .nut-ic { width: 14px; height: 14px; }
.nut-shared__lbl { line-height: 1; }
.nut-shared__in { margin: 0; }
/* VOEDING-DE-BIBLIOTHEEK-OPENT-NAAST-DE-ZOEKBALK — library button parked IN the
   right-hand corner of the field, the same idiom as .nut-search__scan. The
   input keeps width:100% and reserves 44px of trailing padding, so the field
   reads exactly as wide as it did and typed text can never slide under the
   glyph. 44×44 is the tap target itself, not the glyph: .nut-search is
   min-height:44px, so the button fills the field's height without spilling out
   of it and stealing taps from the header above or the dropdown below. */
.nut-shared__field { position: relative; }
.nut-shared__in--lib { padding-right: 44px; }
.nut-shared__lib {
  position: absolute; right: 0; top: 50%; transform: translateY(-50%);
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; padding: 0; cursor: pointer;
  background: transparent; border: 0; border-radius: var(--radius-md);
  color: var(--accent);
}
.nut-shared__lib:hover { background: var(--line-2); }
.nut-shared__lib .nut-ic svg { width: 20px; height: 20px; }
/* VOEDING-EEN-LEGE-COMMUNITY-VERTELT-WAT-JE-KUNT-DOEN — the info button closes
   the header row. margin-left:auto pushes it to the far edge past the label.
   44×44 is the tap target, but the header is only ~14px tall, so negative
   vertical margins let the button keep its full height WITHOUT pushing the
   search field down — the row still measures the same as before. */
.nut-shared__info {
  margin-left: auto; margin-top: -14px; margin-bottom: -14px;
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; padding: 0; cursor: pointer;
  background: transparent; border: 0; border-radius: var(--radius-md);
  color: var(--muted);
}
.nut-shared__info:hover { background: var(--line-2); color: var(--ink); }
.nut-shared__info .nut-ic { width: 18px; height: 18px; }
.nut-shared__info .nut-ic svg { width: 18px; height: 18px; }
/* The give-side text button under the field. A quiet accent-on-text action, NOT
   a filled button: it stands beside the search box as an equal offer, never in
   front of it. Full-width block so the 44px tap target is easy to hit. */
.nut-shared__shareown {
  display: block; width: 100%; text-align: left;
  margin-top: var(--space-2);
  background: none; border: 0; padding: var(--space-2);
  min-height: 44px; cursor: pointer;
  font-family: inherit; font-size: 14px; font-weight: 600;
  color: var(--accent);
}
.nut-shared__shareown:hover { background: var(--accent-light); }
.nut-shared__shareown:disabled { opacity: .5; cursor: default; }
/* The second, quieter line under "no match": how full the cupboard is. */
.nut-shared__cnt {
  color: var(--muted); font-size: 12px;
  padding: 0 var(--space-2) var(--space-3);
  font-variant-numeric: tabular-nums;
}
/* Share-your-own sheet: a checkable row per private recipe. */
.nut-shareown__list { margin: var(--space-2) 0; }
.nut-shareown__row {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-2); min-height: 44px; cursor: pointer;
  border-bottom: 1px solid var(--line-2);
}
.nut-shareown__row:last-child { border-bottom: 0; }
.nut-shareown__row:hover { background: var(--accent-light); }
.nut-shareown__cb { flex: 0 0 auto; width: 20px; height: 20px; margin: 0; accent-color: var(--accent); }
.nut-shareown__txt { flex: 1 1 auto; min-width: 0; }
.nut-shareown__actions { margin-top: var(--space-4); }
.nut-shareown__all { text-align: center; }
/* Quiet heading over the empty-focus suggestions — it labels the handful of
   rows below it, so it must not read as loud as a real section header. */
.nut-shared__suglbl {
  color: var(--muted); font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em;
  padding: var(--space-2) var(--space-2) 6px;
}
.nut-shared__all .nut-shared__row-name { color: var(--accent); font-weight: 600; }
/* Library sheet: shelf rows (name · count · chevron) and the back row. */
.nut-lib__shelf { display: flex; align-items: center; gap: var(--space-2); }
.nut-lib__shelf-name { flex: 1 1 auto; font-size: 15px; color: var(--ink); }
.nut-lib__shelf-n {
  flex: 0 0 auto; font-size: 13px; color: var(--muted); font-variant-numeric: tabular-nums;
}
/* NUT_ICONS.chevron points DOWN (polyline 6 9 12 15 18 9). A shelf row goes
   sideways, not open, so it is turned a quarter to the right; the back row
   turns the same glyph the other quarter. One glyph, two directions. */
.nut-lib__shelf .nut-ic { flex: 0 0 auto; color: var(--muted); }
.nut-lib__shelf .nut-ic svg { width: 18px; height: 18px; transform: rotate(-90deg); }
.nut-lib__back { display: flex; align-items: center; gap: var(--space-2); color: var(--muted); }
.nut-lib__back .nut-ic svg { width: 18px; height: 18px; transform: rotate(90deg); }
.nut-lib__back .nut-lib__shelf-name { font-size: 14px; color: var(--muted); }
.nut-shared__drop {
  margin-top: var(--space-2);
  border-top: 1px solid var(--line-2);
  max-height: 320px; overflow-y: auto;
}
.nut-shared__row {
  display: block; width: 100%; text-align: left;
  background: none; border: 0; border-bottom: 1px solid var(--line-2);
  padding: var(--space-3) var(--space-2); cursor: pointer;
  font-family: inherit; color: var(--ink);
}
.nut-shared__row:last-child { border-bottom: 0; }
.nut-shared__row:hover { background: var(--accent-light); }
.nut-shared__row-name { font-size: 14px; color: var(--ink); }
.nut-shared__row-sub  { font-size: 12px; color: var(--muted); margin-top: 2px; }
.nut-shared__empty {
  color: var(--muted); font-size: 13px; padding: var(--space-3) var(--space-2);
}
/* Anonymity note on the preview sheet — sets the expectation explicitly. */
.nut-shared__note {
  color: var(--muted); font-size: 12px; line-height: 1.4;
  padding: var(--space-2) 0; margin-bottom: var(--space-2);
  border-bottom: 1px solid var(--line-2);
}
.nut-shared__items { margin-top: var(--space-2); }

/* First-time-share onboarding modal — fires once via localStorage. */
.nut-shared__modal {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(0,0,0,.55);
  display: flex; align-items: center; justify-content: center;
  padding: var(--space-5);
}
.nut-shared__card {
  background: var(--bg); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: var(--space-5); max-width: 420px; width: 100%;
  box-shadow: var(--elev-3);
}
.nut-shared__card-h {
  font-size: 17px; font-weight: 600; color: var(--ink);
  margin-bottom: var(--space-3);
}
.nut-shared__card-p {
  font-size: 14px; line-height: 1.45; color: var(--ink);
  margin: 0 0 var(--space-3);
}
.nut-shared__card-p--strong { font-weight: 600; }

/* Saved-meal preview rows (opening a meal from My Meals). */
/* Per-instance recipe ingredient customizer (NUTRITION-RECIPE-ADD-EDIT-INGREDIENTS-PER-INSTANCE) */
.nut-ric__list { margin: var(--space-2) 0; }
/* KOOKSESSIE-PANGEWICHT-VASTLEGGEN — the pan-weight step under the stock result. */
.nut-ric__weigh { margin-top: var(--space-4); }

/* KOOKSESSIE-LAATSTE-GEWICHT-ONTHOUDEN — the reuse switch borrows .nut-rb__link
   wholesale; only the spacing around the weight field differs. */
.nut-ric__reuse { margin-top: 8px; }
.nut-ric__lastw {
  display: flex; flex-direction: column; gap: 2px;
  font-size: 12.5px; line-height: 1.45; color: var(--muted);
  margin: 0 0 var(--space-2);
}
.nut-ric__lastw-note { color: var(--ink-2); }
.nut-ric__row {
  display: flex; align-items: center; gap: var(--space-3);
  border-top: 1px solid var(--line-2); padding: var(--space-3) 0;
}
.nut-ric__row:first-child { border-top: 0; }
.nut-ric__row.is-removed { opacity: 0.45; }
.nut-ric__main { flex: 1 1 auto; min-width: 0; }
.nut-ric__name { font-size: 14px; color: var(--ink); }
.nut-ric__macro { font-size: 12px; color: var(--muted); margin-top: 2px; }
.nut-ric__inwrap { flex: 0 0 auto; display: flex; align-items: center; gap: 4px; }
.nut-ric__in { width: 72px; padding: var(--space-2); text-align: right; }
.nut-ric__unit { font-size: 12px; color: var(--muted); }
.nut-ric__del {
  flex: 0 0 auto; background: none; border: 0; color: var(--muted);
  padding: 4px; cursor: pointer; line-height: 0;
}
.nut-ric__del:hover { color: var(--danger); }
/* VOEDING-TIJDENS-HET-KOKEN-VERVANG-JE-EEN-INGREDIENT — the per-row swap sits beside
   the remove, in the same clothes; the quiet line under the name says what this row
   stands in for and carries the way back. */
.nut-ric__swap {
  flex: 0 0 auto; background: none; border: 0; color: var(--muted);
  padding: 4px; cursor: pointer; line-height: 0;
}
.nut-ric__swap:hover { color: var(--accent); }
.nut-ric__swap:disabled { opacity: 0.4; cursor: default; }
.nut-ric__sub {
  display: flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--muted); margin-top: 2px;
}
/* The way back is the smallest icon on the row (it rides a 12px line, and .nut-ic
   sizes in em) — so it gets its tap area from padding, pulled back out with equal
   negative margin. That keeps the quiet line exactly where it sat, and lands the
   same ~24px target its two siblings on this row already have. Padding, never a
   positioned ::before: that would paint over the ingredient name above it. */
.nut-ric__subundo {
  flex: 0 0 auto; background: none; border: 0; color: var(--muted);
  padding: 6px; margin: -6px 0 -6px -6px; cursor: pointer; line-height: 0;
}
.nut-ric__subundo:hover { color: var(--accent); }
/* VOEDING-KOKEN-ZONDER-RECEPT-TELT-JE-PAN-MEE — de meelopende telling van de pan,
   direct onder de kop van de lade. Eigen strook zodat hij niet meeleest als nóg een
   uitlegregel; alleen tokens, dus hij blijft leesbaar in licht én donker. */
.nut-ric__pantotal {
  display: flex; align-items: baseline; flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
  margin: 0 0 var(--space-3); padding: var(--space-2) var(--space-3);
  border: 1px solid var(--line-2); border-radius: var(--radius-sm);
  background: var(--surface-muted);
}
.nut-ric__pantotal-lbl { font-size: 12px; color: var(--muted); }
.nut-ric__pantotal-val { font-size: 14px; font-weight: 600; color: var(--ink); }
.nut-ric__total { margin-top: var(--space-3); }
.nut-ric__total-lbl { font-size: 12px; color: var(--ink-2); margin-bottom: 4px; }
.nut-ric__hint { font-size: 12px; color: var(--danger); margin-top: var(--space-2); }
.nut-ric__addbtn { width: 100%; margin-top: var(--space-3); }
.nut-prev-row { border-top: 1px solid var(--line-2); padding: var(--space-3) 0; }
.nut-prev-row:first-child { border-top: 0; }
.nut-prev-row__name { font-size: 14px; color: var(--ink); }
.nut-prev-row__meta { font-size: 12px; color: var(--muted); margin-top: 2px; }

/* Destination picker — spacing above the "Meal" label. */
.nut-lib-dest__lbl { margin-top: var(--space-3); }
/* Day picker (step 1): primary "Today" at the top, ghost continue full-width,
   a left-aligned "‹ Change day" link in step 2. */
.nut-dest__today { margin-top: 0; }
.nut-dest__continue { width: 100%; margin-top: var(--space-2); }
.nut-dest__back { display: inline-block; margin: 0 0 var(--space-2); padding: 4px 0; }

/* My Recipes "coming soon" empty state (Phase 1 placeholder). */
.nut-lib-soon { text-align: center; padding: var(--space-6) var(--space-4); }
.nut-lib-soon__title { font-size: 16px; font-weight: 600; color: var(--ink); margin-bottom: var(--space-2); }
.nut-lib-soon__txt { font-size: 13px; color: var(--muted); line-height: 1.5; max-width: 32ch; margin: 0 auto; }

/* ─── Full-page recipe builder (v0.9.202) ───
   Takes over the whole Nutrition mount (no sub-tab strip). The recipe is built
   IN the page: name + servings, then a sheet of food rows each with an inline
   gram field you fine-tune while weighing — macros rescale by ratio live. */
.nut-rb { display: flex; flex-direction: column; }
.nut-rb__bar {
  display: flex; align-items: center; gap: var(--space-3);
  margin: 0 0 var(--space-4); padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--line);
}
.nut-rb__back {
  flex: 0 0 auto; background: none; border: 0; color: var(--ink-2);
  font-size: 28px; line-height: 1; cursor: pointer; padding: 0 4px 0 0; font-family: inherit;
}
.nut-rb__back:hover { color: var(--ink); }
.nut-rb__title { flex: 1 1 auto; font-size: 16px; font-weight: 600; color: var(--ink); }
.nut-rb__done {
  flex: 0 0 auto; background: var(--accent); border: 0; border-radius: var(--radius-sm);
  /* --on-accent (luminance-derived: dark on lime, white on blue), NEVER hardcoded
     #fff — white-on-lime fails contrast. Same protocol the Strength tab uses. */
  color: var(--on-accent); font-size: 14px; font-weight: 600; padding: 8px 18px; cursor: pointer; font-family: inherit;
}
.nut-rb__done:disabled { opacity: 0.6; }

/* RECIPE-INGREDIENTS-LINKED-TOGGLE — one compact line: switch · state label · '?'.
   The explanation lives behind the '?' (same idiom as the Strength 1RM helper) so
   there is exactly ONE place that explains this, never two side by side. */
.nut-rb__link {
  display: flex; align-items: center; gap: 10px;
  margin: 0 0 var(--space-2);
}
.nut-rb__link-sw { flex: 0 0 auto; }
.nut-rb__link-lbl {
  flex: 1 1 auto; min-width: 0;
  font-size: 13px; font-weight: 600; color: var(--ink-2);
}
.nut-rb__link-help {
  flex: 0 0 auto; width: 34px; height: 34px; margin-right: -8px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 0; padding: 0;
  color: var(--muted); cursor: pointer;
}
.nut-rb__link-help .nut-ic svg { width: 17px; height: 17px; }
.nut-rb__link-help.is-on { color: var(--ink); }
.nut-rb__link-note {
  display: none;
  font-size: 12.5px; line-height: 1.5; color: var(--muted);
  background: var(--surface); border: 1px dashed var(--line);
  border-radius: var(--radius-md); padding: 9px 11px;
  margin: 0 0 var(--space-2);
}
.nut-rb__link-note.is-open { display: block; }
.nut-rb__rows--unlocked {
  border-left: 3px solid var(--accent);
  padding-left: var(--space-3);
  margin-left: -3px;
}

.nut-rb__servwrap { display: flex; }
.nut-rb__servfield { width: 50%; max-width: 160px; margin-bottom: 0; }

/* NUTRITION-RECIPE-LIVE-PROPORTIONAL-SCALING — servings + reset row. */
.recipe-scale-row { display: flex; align-items: flex-end; gap: var(--space-2); }
.recipe-scale-row .nut-field { margin-bottom: 0; }
.recipe-reset-btn {
  flex: none;
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}
.recipe-reset-btn:hover { color: var(--ink); border-color: var(--ink); }

.nut-rb__shead {
  display: flex; align-items: center; justify-content: space-between;
  margin: var(--space-4) 0 var(--space-2);
}
.nut-rb__slbl { font-size: 12px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-2); }
.nut-rb__shead-r { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }
.nut-rb__count { font-size: 12px; color: var(--muted); text-align: right; }

.nut-rb__rows {
  display: flex; flex-direction: column;
  border: 1px solid var(--line); border-radius: var(--radius-md); overflow: hidden; background: var(--surface);
}
.nut-rb__empty { text-align: center; padding: var(--space-5) var(--space-3); }
.nut-rb__row {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-4); border-top: 1px solid var(--line-2);
}
.nut-rb__row:first-child { border-top: 0; }
.nut-rb__rmain {
  flex: 1 1 auto; min-width: 0; text-align: left; background: none; border: 0;
  color: var(--ink); font-size: 15px; cursor: pointer; font-family: inherit;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 4px 0;
}
.nut-rb__rmain:hover { color: var(--accent); }
.nut-rb__rright { flex: 0 0 auto; display: flex; align-items: center; gap: var(--space-3); }
.nut-rb__amt {
  display: inline-flex; align-items: center; gap: 4px;
  border: 1px solid var(--line-2); border-radius: var(--radius-md); padding: 4px 8px; background: var(--bg);
}
.nut-rb__amt:focus-within { border-color: var(--accent); }
.nut-rb__amtin {
  /* NUTRITION-AMOUNT-INPUT-AUTOSIZE-TO-CONTENT — width follows the value, no
     leading whitespace before small numbers; clamp keeps 1ch readable / 4-digit safe. */
  field-sizing: content; min-width: 3ch; max-width: 7ch;
  border: 0; background: none; color: var(--ink); font-size: 15px;
  text-align: right; font-variant-numeric: tabular-nums; font-family: inherit; padding: 0;
}
.nut-rb__amtin:focus { outline: none; }
.nut-rb__amtunit { font-size: 12px; color: var(--muted); }
.nut-rb__rstatic { font-size: 12px; color: var(--muted); }
.nut-rb__rkcal { min-width: 4.5em; text-align: right; font-size: 13px; color: var(--ink-2); font-variant-numeric: tabular-nums; }

/* RECEPT-CONTROLESCHERM-VOOR-HET-OPSLAAN — the check screen between pasting a
   list and it landing in the recipe. It is deliberately NOT a second, simpler
   row: every row here IS a .nut-rb__row with the same name button, amount box
   and kcal label; this block only stacks that row and hangs the review-only
   parts (state chip, source line, shortlist) underneath it. */
.nut-rvw__intro { font-size: 13px; color: var(--ink-2); margin: 0 0 var(--space-3); }
.nut-rvw__serv { margin-bottom: var(--space-4); }
.nut-rvw__grp { margin-bottom: var(--space-4); }
.nut-rvw__grphead {
  display: flex; align-items: center; gap: 6px; margin: 0 0 var(--space-2);
  font-size: 12px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-2);
}
.nut-rvw__grphead--unsure { color: var(--warning); }
.nut-rvw__grptog {
  display: flex; align-items: center; gap: 6px; width: 100%; padding: 8px 0;
  background: none; border: 0; cursor: pointer; font-family: inherit; text-align: left;
  font-size: 12px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-2);
}
/* The shared chevron glyph points DOWN; collapsed turns it to point right. */
.nut-rvw__grptog .nut-ic { transform: rotate(-90deg); transition: transform var(--dur-fast) var(--ease-standard); }
.nut-rvw__grptog[aria-expanded="true"] .nut-ic { transform: none; }
.nut-rvw__row { flex-direction: column; align-items: stretch; gap: 8px; }
.nut-rvw__row--unsure { border-left: 3px solid var(--warning); }
.nut-rvw__rtop { display: flex; align-items: center; gap: var(--space-3); }
.nut-rvw__meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.nut-rvw__chip {
  display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto;
  border: 1px solid var(--line-2); border-radius: 999px; padding: 2px 8px;
  font-size: 11px; font-weight: 600; color: var(--ink-2); background: var(--bg);
}
.nut-rvw__chip .nut-ic svg { width: 12px; height: 12px; display: block; }
.nut-rvw__chip--sure { color: var(--ok); border-color: currentColor; }
.nut-rvw__chip--unsure { color: var(--warning); border-color: currentColor; }
.nut-rvw__src { font-size: 12px; color: var(--muted); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nut-rvw__why { font-size: 12px; line-height: 1.4; color: var(--ink-2); }
.nut-rvw__cands { display: flex; flex-wrap: wrap; gap: 6px; }
.nut-rvw__cand {
  border: 1px solid var(--line-2); border-radius: var(--radius-md); background: var(--bg);
  color: var(--ink); font-family: inherit; font-size: 12px; padding: 6px 10px; cursor: pointer;
  display: inline-flex; align-items: center; gap: 5px; text-align: left;
}
.nut-rvw__cand:hover { border-color: var(--accent); }
.nut-rvw__cand.is-on { border-color: var(--accent); background: var(--accent-light); color: var(--accent); font-weight: 600; }
.nut-rvw__candkcal { color: var(--muted); font-weight: 400; }
.nut-rvw__cand.is-on .nut-rvw__candkcal { color: inherit; }
.nut-rvw__foot { margin-top: var(--space-4); }
.nut-rvw__go {
  display: block; width: 100%; padding: 14px; border: 0; border-radius: var(--radius-md);
  background: var(--accent); color: var(--on-accent); font-family: inherit; font-size: 15px; font-weight: 600; cursor: pointer;
}

.nut-rb__add {
  display: flex; align-items: center; gap: var(--space-2); justify-content: center;
  width: 100%; margin-top: var(--space-3);
  background: var(--bg); border: 1px dashed var(--line); border-radius: var(--radius-md);
  color: var(--accent); font-size: 14px; font-weight: 500; padding: 14px; cursor: pointer; font-family: inherit;
}
.nut-rb__add:hover { border-color: var(--accent); background: var(--accent-light); }
.nut-rb__addplus { font-size: 18px; line-height: 1; }

/* NUTRITION-RECIPE-AI-INGREDIENT-LIST — additive freeform-list fast-entry panel. */
.nut-rb__ai { margin-top: var(--space-3); border: 1px solid var(--line-2); border-radius: var(--radius-lg); overflow: hidden; }
.nut-rb__aitoggle {
  display: flex; align-items: center; gap: var(--space-2); width: 100%;
  background: var(--bg); border: 0; color: var(--ink); font-size: 14px; font-weight: 500;
  padding: 12px 14px; cursor: pointer; font-family: inherit; text-align: left;
}
.nut-rb__aitoggle:hover { background: var(--accent-light); }
.nut-rb__ailbl { flex: 1 1 auto; }
.nut-rb__aichev { flex: 0 0 auto; color: var(--muted); transition: transform var(--dur-fast) var(--ease-standard); }
.nut-rb__aitoggle[aria-expanded="true"] .nut-rb__aichev { transform: rotate(90deg); }
.nut-rb__aibody { padding: 12px 14px; border-top: 1px solid var(--line-2); display: flex; flex-direction: column; gap: 10px; }
.nut-rb__aitawrap { position: relative; }
.nut-rb__aita {
  width: 100%; box-sizing: border-box; resize: vertical; min-height: 76px;
  border: 1px solid var(--line-2); border-radius: var(--radius-md); background: var(--bg); color: var(--ink);
  font-size: 14px; font-family: inherit; padding: 10px; padding-right: 46px;
}
.nut-rb__aita:focus { outline: none; border-color: var(--accent); }
.nut-rb__aisubmit {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  background: var(--accent); color: var(--on-accent); border: 0; border-radius: var(--radius-md);
  padding: 11px; font-size: 14px; font-weight: 600; cursor: pointer; font-family: inherit;
}
.nut-rb__aisubmit:disabled { opacity: .6; cursor: default; }

/* NUTRITION-RECIPE-FOODS-ENTRY-FLOW-REDESIGN — entry affordances re-render off items. */
.nut-rb__entry { margin-top: var(--space-3); display: flex; flex-direction: column; gap: 10px; }
/* STATE A / reveal: the input card frame wraps textarea + mic + primary submit. */
.nut-rb__aicard {
  display: flex; flex-direction: column; gap: 10px;
  border: 1px solid var(--line-2); border-radius: var(--radius-md); padding: 12px 14px; background: var(--bg);
}
.nut-rb__aicard .nut-rb__aisubmit { padding: 12px; }
/* Secondary, quieter 'Add one food at a time'. */
.nut-rb__addquiet {
  align-self: center; background: transparent; border: 0; color: var(--muted);
  font-size: 13px; font-weight: 500; padding: 6px; cursor: pointer; font-family: inherit;
  text-decoration: underline; text-underline-offset: 2px;
}
.nut-rb__addquiet:hover { color: var(--accent); }
/* STATE B — two equal-weight side-by-side buttons. */
.nut-rb__entrybtns { display: flex; gap: 10px; }
.nut-rb__entrybtns > .nut-rb__add { flex: 1 1 0; margin-top: 0; }
.nut-rb__listbtn {
  flex: 1 1 0; display: flex; align-items: center; justify-content: center; gap: var(--space-2);
  background: var(--bg); border: 1px dashed var(--line); border-radius: var(--radius-md);
  color: var(--accent); font-size: 14px; font-weight: 500; padding: 14px; cursor: pointer; font-family: inherit;
}
.nut-rb__listbtn:hover { border-color: var(--accent); background: var(--accent-light); }
.nut-rb__est {
  font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .03em;
  color: var(--accent); border: 1px solid var(--accent); border-radius: var(--radius-sm); padding: 0 4px; line-height: 1.5;
}

.nut-rb__totals { margin-top: var(--space-4); padding-top: var(--space-3); border-top: 1px solid var(--line); }
.nut-rb__trow { font-size: 15px; font-weight: 600; color: var(--ink); }
.nut-rb__tsub { font-size: 13px; color: var(--muted); margin-top: 2px; }

.nut-rb__day { width: 100%; margin-top: var(--space-4); }
/* VOEDING-WEGGOOIEN-IS-EEN-KNOP-DIE-JE-ZIET — this used to be a borderless grey 13px
   word that a real user could not find at all. It is now its own block at the foot of
   the builder: a hairline above it, an outlined danger button across the full width
   (.btn--dangerghost carries the visible --danger border and .btn's 16px padding, so
   the tap target clears 44px), and one muted line under it promising the question.
   The block sits under the form on purpose — never beside "Bewaren", which keeps its
   place top-right in the header — so it is findable without being hit by accident. */
.nut-rb__discardblk {
  margin-top: var(--space-5); padding-top: var(--space-5); border-top: 1px solid var(--line);
}
.nut-rb__discard {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  width: 100%;
}
.nut-rb__discardnote {
  margin-top: var(--space-2); text-align: center; font-size: 13px; color: var(--muted);
}

/* Recipe notes / cooking instructions — appears under totals in the recipe
   builder/editor. Plain prose, so system font (no monospace). */
.fz-recipe-notes { margin-top: var(--space-4); }
.fz-recipe-notes__lbl {
  display: block;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-2);
  margin-bottom: var(--space-2);
}
.fz-recipe-notes__ta {
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: 10px;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  font-family: inherit;
  font-size: 14px;
  line-height: 1.4;
  resize: none;
  overflow: hidden;
  min-height: 80px;
  white-space: pre-wrap;
}
.fz-recipe-notes__ta:focus { outline: none; border-color: var(--accent); }
.fz-recipe-notes__count {
  margin-top: 4px;
  text-align: right;
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* v0.9.332 — COACH-LINK-EXISTING.
   Invitee banner shown at top of shell when /api/me/link-requests has pending
   rows. Mirrors .delbanner shape (border-left accent stripe) but uses the
   accent token instead of danger. Settings → Account "My clients" disclosure
   inherits .set-disclose / .set-group; the rules below add only what's new:
   email search row, three result sections, the no-Sassu-account card, the
   custom Accept/Decline sheet. NO hardcoded hex — every color goes through a
   token so dark theme inherits automatically. */
.coachlink-banner {
  margin: var(--space-3) var(--space-4);
  padding: 12px 12px 12px 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-sm);
  display: flex; align-items: center; gap: 12px;
  cursor: pointer;
  font-family: inherit;
  color: var(--ink);
}
.coachlink-banner:hover { background: var(--surface-muted); }
.coachlink-banner:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.coachlink-banner__avatar {
  width: 38px; height: 38px; border-radius: 50%;
  background: var(--accent); color: var(--on-accent);
  display: flex; align-items: center; justify-content: center;
  font-size: 14px; font-weight: 600; flex-shrink: 0;
}
.coachlink-banner__body { flex: 1; min-width: 0; }
.coachlink-banner__title {
  font-size: 14px; color: var(--ink); line-height: 1.35; margin: 0;
}
.coachlink-banner__title strong { font-weight: 600; }
.coachlink-banner__sub {
  font-size: 12px; color: var(--muted); margin: 2px 0 0;
}
.coachlink-banner__chev { color: var(--muted); font-size: 20px; flex-shrink: 0; }

/* Accept / Decline sheet — composed atop the existing .ui-dlg-overlay +
   .ui-dlg shell, so focus trap, Esc, and aria wiring all come for free. */
.coachlink-sheet { max-width: 420px; }
.coachlink-sheet__title { padding: 0; }
.coachlink-sheet__header {
  display: flex; align-items: center; gap: 14px; margin-bottom: 12px;
}
.coachlink-sheet__avatar {
  width: 48px; height: 48px; border-radius: 50%;
  background: var(--accent); color: var(--on-accent);
  display: flex; align-items: center; justify-content: center;
  font-size: 18px; font-weight: 600; flex-shrink: 0;
}
.coachlink-sheet__namebox { min-width: 0; }
.coachlink-sheet__name { font-size: 18px; color: var(--ink); font-weight: 600; }
.coachlink-sheet__sub { font-size: 13px; color: var(--muted); margin-top: 2px; }
.coachlink-sheet__body {
  font-size: 14px; color: var(--ink-2); line-height: 1.55; margin: 0 0 16px;
}
.coachlink-sheet__permlist {
  list-style: none; padding: 0; margin: 0 0 18px;
}
.coachlink-sheet__perm {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 0; font-size: 13px; color: var(--ink-2);
}
.coachlink-sheet__perm-icon {
  width: 16px; height: 16px; flex-shrink: 0; color: var(--accent);
}
.coachlink-sheet__perm--no .coachlink-sheet__perm-icon { color: var(--muted); }
.coachlink-sheet__perm--no { color: var(--muted); }

/* COACH-PLATFORM-SPRINT-8 consent modal. */
.consent-sheet__section { margin: 4px 0 14px; }
.consent-sheet__legend {
  font-size: 12px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); margin: 0 0 8px; font-weight: 600;
}
.consent-sheet__toggle {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 8px 0; font-size: 14px; color: var(--ink-2);
  border-bottom: 1px solid var(--line); cursor: pointer;
}
.consent-sheet__toggle:last-child { border-bottom: none; }
.consent-sheet__checkbox { width: 20px; height: 20px; accent-color: var(--accent); flex-shrink: 0; }
.consent-sheet__matrow {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 8px 0; font-size: 14px; color: var(--ink-2);
  border-bottom: 1px solid var(--line);
}
.consent-sheet__matrow:last-child { border-bottom: none; }
.consent-sheet__select {
  padding: 7px 10px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface-muted); color: var(--ink); font-family: inherit;
  font-size: 14px; outline: none; flex-shrink: 0;
}
.consent-sheet__select:focus { border-color: var(--accent); }
.consent-sheet__legalese { font-size: 13px; color: var(--muted); line-height: 1.5; margin: 4px 0 16px; }
.set-consent-log { list-style: none; padding: 0; margin: 8px 0 0; }
.set-consent-log__row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 6px 0; font-size: 13px; color: var(--ink-2);
  border-bottom: 1px solid var(--line);
}
.set-consent-log__row:last-child { border-bottom: none; }
.set-consent-log__when { color: var(--muted); flex-shrink: 0; font-variant-numeric: tabular-nums; }

/* "My clients" disclosure body — search row, hint, result sections, card. */
.clients-search {
  display: flex; gap: 8px; align-items: stretch;
  margin: var(--space-2) 0 6px;
}
.clients-search__input {
  flex: 1; padding: 10px 12px;
  background: var(--surface-muted);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-size: 15px; color: var(--ink);
  outline: none; font-family: inherit;
}
.clients-search__input:focus {
  border-color: var(--ink-2);
}
.clients-search__btn {
  padding: 10px 16px;
  background: var(--accent); color: var(--on-accent);
  border: 0; border-radius: var(--radius-sm);
  font-size: 14px; font-weight: 600; cursor: pointer; font-family: inherit;
}
.clients-search__btn:hover { background: var(--accent-2); }
.clients-search__btn:disabled { opacity: 0.55; cursor: progress; }
.clients-search__hint {
  font-size: 12px; color: var(--muted); margin: 0 0 var(--space-3);
  line-height: 1.5;
}
.clients-nfcard {
  background: var(--surface-muted);
  border: 1px dashed var(--line);
  border-radius: var(--radius-sm);
  padding: 14px;
  margin-bottom: var(--space-3);
}
.clients-nfcard__t {
  font-size: 14px; color: var(--ink); margin: 0 0 4px;
}
.clients-nfcard__t strong { font-weight: 600; }
.clients-nfcard__sub {
  font-size: 13px; color: var(--ink-2); margin: 0 0 10px; line-height: 1.5;
}
.clients-nfcard__linkrow {
  display: flex; gap: 8px; align-items: stretch;
}
.clients-nfcard__link {
  flex: 1; padding: 8px 10px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-size: 12px; color: var(--ink); font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  outline: none;
}
.clients-section { margin: var(--space-3) 0 0; }
.clients-section__h {
  font-size: 12px; font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted); margin: 0 0 6px;
}
.clients-section__list { display: flex; flex-direction: column; }
.client-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 6px; min-height: 56px;
}
.client-row + .client-row { border-top: 1px solid var(--line-2); }
.client-row__avatar {
  width: 34px; height: 34px; border-radius: 50%;
  background: var(--surface-muted);
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 600; color: var(--ink-2); flex-shrink: 0;
}
.client-row__body { flex: 1; min-width: 0; }
.client-row__name {
  font-size: 14px; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.client-row__meta { font-size: 12px; color: var(--muted); margin-top: 2px; }
.client-row__actions {
  display: flex; gap: 6px; flex-shrink: 0; align-items: center;
}
.client-row__badge {
  font-size: 11px; color: var(--muted);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 2px 8px;
}
.client-row__badge--ok {
  color: var(--ok); border-color: var(--ok);
}

/* ──────────────────────────────────────────────────────────
   Cardio · Modality Picker (Sprint 6, v0.9.372)
   Source-of-truth spec: cardio_tab_spec.md §2.1.
   Painted inside the cardio iframe via cardio-bridge.js; tokens
   resolve from cardio.html's :root preset block (Dag / Nacht).
   Start affordances paint background:var(--accent) +
   color:var(--on-accent) — luminance-derived per brand_color,
   per rule g + feedback_accent_buttons_on_accent_token.
   ────────────────────────────────────────────────────────── */
/* CARDIO-MAP-RENDER-LAYOUT-AUDIT-AND-FIX (v0.9.465) — defensive bottom
   padding on every outermost cardio surface so any future fixed-bottom
   UI (in-iframe player, save bar, etc.) can't clip the last row.
   Matches the kaizen 2026-06-06 STRENGTH-EDIT-ARCHIVE-SCROLL-OVERFLOW-FIX
   pattern: safe-area inset + 16px buffer. */
.fz-cardio-picker { padding: 16px 16px env(safe-area-inset-bottom, 0px); }
.fz-cardio-picker__grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
}

/* DIARY-LOG-BUTTONS-RELOCATE-WATER-NUTRITION-STEPS-CARDIO (v0.9.473) — Steps
   quick-log cell, mounted at the top of the Modality Picker. Visual chrome
   mirrors the .cal-metric--empty/--filled pair used on the Diary tab so the
   two relocated buttons (Water on Nutrition, Steps here) feel like one
   surface. Tokens only — Dag + Nacht inherit correctly. */
.fz-cardio-stepslog {
  appearance: none; -webkit-appearance: none;
  display: flex; align-items: center; gap: 10px;
  width: 100%; box-sizing: border-box;
  padding: 12px 14px; margin-bottom: 12px;
  border-radius: var(--radius-lg); border: 1px solid transparent;
  font: inherit; font-size: 14px;
  min-height: 44px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  text-align: left;
  transition: background var(--dur-base) var(--ease-standard), border-color var(--dur-base) var(--ease-standard), color var(--dur-base) var(--ease-standard);
}
.fz-cardio-stepslog:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.fz-cardio-stepslog--empty {
  background: var(--accent); color: var(--on-accent);
  border-color: var(--accent);
}
.fz-cardio-stepslog--empty .fz-cardio-stepslog__lbl {
  flex: 1 1 auto; font-weight: 600; font-size: 14px; line-height: 1.2;
}
.fz-cardio-stepslog--empty .fz-cardio-stepslog__hint {
  flex: 0 0 auto; font-size: 20px; font-weight: 600; line-height: 1; opacity: 0.9;
}
.fz-cardio-stepslog--filled {
  background: var(--surface); color: var(--fg);
  border-color: var(--line);
}
.fz-cardio-stepslog--filled .fz-cardio-stepslog__lbl {
  flex: 0 0 auto; color: var(--muted);
  font-size: 12px; font-weight: 500;
  text-transform: uppercase; letter-spacing: 0.02em;
}
.fz-cardio-stepslog--filled .fz-cardio-stepslog__val {
  flex: 1 1 auto; font-weight: 600; font-size: 15px;
  font-feature-settings: 'tnum' 1;
}
.fz-cardio-stepslog--filled .fz-cardio-stepslog__hint {
  flex: 0 0 auto; color: var(--muted); font-size: 18px; line-height: 1;
}

.fz-cardio-card {
  appearance: none; -webkit-appearance: none;
  background: var(--surface); color: var(--fg);
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: 12px; min-height: 116px;
  display: flex; flex-direction: column; gap: 8px;
  text-align: left; font: inherit; cursor: pointer;
}
.fz-cardio-card:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.fz-cardio-card__head {
  display: flex; align-items: center; gap: 8px;
}
.fz-cardio-card__icon {
  width: 24px; height: 24px;
  stroke: currentColor; fill: none; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
  flex: 0 0 auto;
}
.fz-cardio-card__label {
  font-size: 13px; font-weight: 600; letter-spacing: 0.01em;
}
.fz-cardio-card__chip {
  font-size: 11px; color: var(--muted);
  line-height: 1.3; min-height: 1.3em;
}
.fz-cardio-card__foot {
  margin-top: auto;
  display: flex; align-items: center; justify-content: space-between;
}
.fz-cardio-card__start {
  appearance: none;
  background: var(--accent); color: var(--on-accent);
  border: 0; border-radius: var(--radius-pill);
  padding: 5px 12px; font-size: 11px; font-weight: 700;
  letter-spacing: 0.04em; text-transform: uppercase;
  cursor: pointer;
}
.fz-cardio-card--saved { grid-column: 1 / -1; }
.fz-cardio-card--saved .fz-cardio-card__start {
  background: transparent; color: var(--fg);
  border: 1px solid var(--line);
}
/* ──────── CARDIO-COMMUTE-ROUTE-MANUAL-ENTRY — "Mijn routes" ──────── */
.fz-cardio-routes {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 14px;
  margin-bottom: 16px;
}
.fz-cardio-routes__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-bottom: 12px;
}
.fz-cardio-routes__head-l { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.fz-cardio-routes__title { font-size: 14px; font-weight: 600; color: var(--ink); }
.fz-cardio-routes__week { font-size: 11px; color: var(--muted); white-space: nowrap; }
.fz-cardio-routes__add {
  display: inline-flex; align-items: center; gap: 4px;
  background: none; border: none; cursor: pointer;
  color: var(--accent); font: inherit; font-size: 13px; padding: 4px 2px;
}
.fz-cardio-routes__add .fz-icon { width: 14px; height: 14px; }
.fz-cardio-routes__empty { font-size: 12px; color: var(--muted); padding: 6px 0; }

.fz-cardio-routes__card {
  background: var(--bg);
  border-radius: var(--radius-md);
  padding: 12px;
}
.fz-cardio-routes__card + .fz-cardio-routes__card { margin-top: 10px; }
.fz-cardio-routes__card-head {
  display: flex; align-items: center; gap: 10px; margin-bottom: 10px;
  -webkit-user-select: none; user-select: none;
}
.fz-cardio-routes__icon {
  width: 32px; height: 32px; flex: 0 0 auto;
  background: var(--line); border-radius: var(--radius-md);
  display: flex; align-items: center; justify-content: center;
  color: var(--ink);
}
.fz-cardio-routes__mode-txt { font-size: 10px; font-weight: 600; color: var(--ink); }
.fz-cardio-routes__meta { flex: 1; min-width: 0; }
.fz-cardio-routes__name {
  font-size: 14px; font-weight: 500; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fz-cardio-routes__detail {
  font-size: 12px; color: var(--muted); margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.fz-cardio-routes__week-strip { display: flex; align-items: center; gap: 6px; }
.fz-cardio-routes__nav {
  width: 24px; height: 24px; flex: 0 0 auto;
  background: none; border: none; cursor: pointer; color: var(--muted);
  display: flex; align-items: center; justify-content: center; padding: 0;
}
.fz-cardio-routes__nav .fz-icon { width: 14px; height: 14px; }
.fz-cardio-routes__days {
  display: flex; gap: 4px; flex: 1; justify-content: space-between;
}
.fz-cardio-routes__day {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
}
.fz-cardio-routes__day-lbl {
  font-size: 10px; color: var(--muted); text-transform: uppercase;
}
.fz-cardio-routes__dot {
  width: 28px; height: 28px; flex: 0 0 auto;
  border-radius: 50%; border: 2px solid var(--line);
  background: none; cursor: pointer; padding: 0;
  display: flex; align-items: center; justify-content: center;
  color: var(--muted); transition: background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
}
.fz-cardio-routes__dot .fz-icon { width: 13px; height: 13px; }
.fz-cardio-routes__dot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.fz-cardio-routes__ht { font-size: 9px; font-weight: 700; }
.fz-cardio-routes__dot.is-heen {
  background: var(--accent); border-color: var(--accent); color: var(--on-accent);
}
.fz-cardio-routes__dot.is-terug {
  background: var(--line); border-color: var(--accent); color: var(--ink);
}
.fz-cardio-routes__dot.is-heen_terug {
  background: var(--ok); border-color: var(--ok); color: var(--on-accent);
}

.fz-cardio-routes__legend {
  display: flex; gap: 12px; flex-wrap: wrap;
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line);
}
.fz-cardio-routes__lg {
  display: flex; align-items: center; gap: 6px;
  font-size: 11px; color: var(--muted);
}
.fz-cardio-routes__lg-dot { width: 12px; height: 12px; border-radius: 50%; }
.fz-cardio-routes__lg-dot.is-heen { background: var(--accent); }
.fz-cardio-routes__lg-dot.is-terug { background: var(--line); border: 2px solid var(--accent); }
.fz-cardio-routes__lg-dot.is-heen_terug { background: var(--ok); }

.fz-cardio-routes__form {
  margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--line);
}
.fz-cardio-routes__form-title { font-size: 13px; font-weight: 600; margin-bottom: 12px; color: var(--ink); }
.fz-cardio-routes__field { margin-bottom: 12px; }
.fz-cardio-routes__field-row { display: flex; gap: 12px; }
.fz-cardio-routes__field-row .fz-cardio-routes__field { flex: 1; min-width: 0; }
.fz-cardio-routes__lbl { display: block; font-size: 12px; color: var(--muted); margin-bottom: 4px; }
.fz-cardio-routes__input {
  width: 100%; background: var(--bg);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 10px 12px; font: inherit; font-size: 14px; color: var(--ink);
}
.fz-cardio-routes__input::placeholder { color: var(--muted); }
.fz-cardio-routes__input:focus { outline: none; border-color: var(--accent); }
/* CARDIO-ROUTE-AUTO-DISTANCE-MAPBOX — auto-distance spinner + microlabel. */
/* cardioSpin stond tot L102 bij .cardio-loader__spinner hierboven; die poort is
   weg en de keyframe verhuist mee naar zijn enige resterende gebruiker. Een
   animation-naam zonder keyframe faalt stil: de ring zou gewoon stilstaan. */
@keyframes cardioSpin { to { transform: rotate(360deg); } }
.fz-cardio-routes__input-wrap { position: relative; }
.fz-cardio-routes__input-wrap.is-loading::after {
  content: ''; position: absolute; right: 10px; top: 50%; margin-top: -8px;
  width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid var(--line); border-top-color: var(--accent);
  animation: cardioSpin var(--dur-loop) linear infinite; pointer-events: none;
}
.fz-cardio-routes__hint { font-size: 11px; color: var(--muted); margin-top: 4px; }
.fz-cardio-routes__hint:empty { display: none; }
.fz-cardio-routes__mode-seg { display: flex; gap: 8px; }
.fz-cardio-routes__mode {
  flex: 1; min-width: 0; background: var(--bg);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 10px 6px; cursor: pointer; font: inherit; font-size: 12px;
  color: var(--muted);
  display: flex; flex-direction: column; align-items: center; gap: 4px;
}
.fz-cardio-routes__mode .fz-icon { width: 18px; height: 18px; }
.fz-cardio-routes__mode.is-active {
  border-color: var(--accent); color: var(--ink); background: var(--accent-subtle);
}
.fz-cardio-routes__err { font-size: 12px; color: var(--accent-orange, #FB923C); margin-bottom: 8px; min-height: 0; }
.fz-cardio-routes__err:empty { display: none; }
.fz-cardio-routes__form-actions { display: flex; gap: 8px; align-items: center; }
.fz-cardio-routes__archive {
  background: none; border: none; cursor: pointer; font: inherit; font-size: 13px;
  color: var(--muted); padding: 10px 4px; margin-right: auto;
}
.fz-cardio-routes__cancel {
  background: none; border: 1px solid var(--line); border-radius: var(--radius-md);
  cursor: pointer; font: inherit; font-size: 14px; color: var(--ink); padding: 11px 16px;
}
.fz-cardio-routes__save {
  background: var(--accent); color: var(--on-accent);
  border: none; border-radius: var(--radius-md); cursor: pointer;
  font: inherit; font-size: 14px; font-weight: 600; padding: 12px 18px;
}

/* Long-press action sheet — overlays the cardio app surface. */
.fz-cardio-routes__sheet { position: fixed; inset: 0; z-index: 60; display: flex; align-items: flex-end; }
.fz-cardio-routes__sheet-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,0.5); }
.fz-cardio-routes__sheet-card {
  position: relative; width: 100%; max-width: 420px; margin: 0 auto;
  background: var(--surface); border-top-left-radius: var(--radius-lg); border-top-right-radius: var(--radius-lg);
  padding: 12px; display: flex; flex-direction: column; gap: 6px;
  padding-bottom: max(12px, env(safe-area-inset-bottom));
}
.fz-cardio-routes__sheet-title {
  font-size: 13px; font-weight: 600; color: var(--ink);
  padding: 8px 8px 10px; text-align: center;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fz-cardio-routes__sheet-btn {
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-md);
  cursor: pointer; font: inherit; font-size: 15px; color: var(--ink); padding: 14px;
}
.fz-cardio-routes__sheet-btn.is-danger { color: var(--accent-orange, #FB923C); }

.fz-cardio-picker__assign {
  margin-top: 18px; padding-top: 14px;
  border-top: 1px solid var(--line);
}
.fz-cardio-picker__assign-h {
  margin: 0 0 8px;
  font-size: 11px; color: var(--muted);
  letter-spacing: 0.1em; text-transform: uppercase; font-weight: 600;
}
.fz-cardio-picker__assign-empty {
  font-size: 12px; color: var(--muted);
}
.fz-cardio-assign {
  background: var(--surface); color: var(--fg);
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: 12px;
  display: flex; gap: 10px; align-items: flex-start;
  cursor: pointer; width: 100%; text-align: left;
  appearance: none; font: inherit;
}
.fz-cardio-assign + .fz-cardio-assign { margin-top: 8px; }
.fz-cardio-assign:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.fz-cardio-assign__icon {
  width: 22px; height: 22px; stroke: currentColor; fill: none;
  stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
  flex: 0 0 auto; margin-top: 2px;
}
.fz-cardio-assign__body { flex: 1; min-width: 0; }
.fz-cardio-assign__title { font-size: 13px; font-weight: 600; }
.fz-cardio-assign__meta {
  font-size: 11px; color: var(--muted); margin-top: 2px;
}
.fz-cardio-assign__when {
  font-size: 11px; color: var(--accent);
  align-self: center; white-space: nowrap;
}

/* ──────────────────────────────────────────────────────────
   Cardio — INDOOR live tracking (Sprint 7, v0.9.373).
   Top 30% is the hero-timer banner (visual reuse of the
   strength prototype's .hero-timer / .ht-time / .ht-min /
   .ht-sec / .ht-ms pattern — Inter tabular-nums display so
   the digits don't jitter). Below: manual stat inputs +
   pause-resume + end-session-confirm. Tokens-only, per
   css-hex-lint A11Y-12 / S22. --on-accent everywhere a
   control sits on --accent, per rule g + the accent-buttons
   feedback memory. No hex literals on paint properties.
   ────────────────────────────────────────────────────────── */
.fz-cardio-indoor-track {
  display: flex; flex-direction: column;
  min-height: calc(100vh - env(safe-area-inset-top) - env(safe-area-inset-bottom));
  min-height: calc(100dvh - env(safe-area-inset-top) - env(safe-area-inset-bottom)); /* dvh wins on modern engines, 100vh above stays fallback */
}
.fz-cardio-indoor-track__hero {
  /* Override the global fixed-position .hero-timer rule for the cardio
     iframe — here we want the hero in normal flow, taking the top 30%
     of the viewport per spec §2.2. */
  position: relative;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 8px;
  min-height: 30vh;
  padding: 24px 20px;
  background: var(--accent);
  color: var(--on-accent);
  border: 0; border-bottom: 1px solid var(--line);
}
.fz-cardio-indoor-track__phase {
  font-size: 11px; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--on-accent); opacity: 0.7;
}
.fz-cardio-indoor-track__hero .ht-time {
  font-size: 56px; font-weight: 700;
  line-height: 1; letter-spacing: -1px;
  color: var(--on-accent);
  font-family: 'Inter', 'DM Sans', -apple-system, sans-serif;
  font-variant-numeric: tabular-nums;
}
.fz-cardio-indoor-track__hero .ht-min,
.fz-cardio-indoor-track__hero .ht-sec {
  display: inline-block; text-align: center;
}
.fz-cardio-indoor-track__hero .ht-min { min-width: 1.1ch; }
.fz-cardio-indoor-track__hero .ht-sec { min-width: 2.2ch; }
.fz-cardio-indoor-track__hero .ht-ms {
  font-size: 26px; font-weight: 600;
  opacity: 0.55; display: inline-block;
  min-width: 2ch; text-align: left;
  vertical-align: baseline;
}
.fz-cardio-indoor-track__pause {
  appearance: none; -webkit-appearance: none;
  background: var(--on-accent); color: var(--accent);
  border: 0; border-radius: var(--radius-pill);
  padding: 10px 28px;
  font: inherit; font-weight: 700; font-size: 14px;
  letter-spacing: 0.04em; text-transform: uppercase;
  cursor: pointer;
  min-width: 140px;
}
.fz-cardio-indoor-track__pause:focus-visible {
  outline: 2px solid var(--on-accent); outline-offset: 3px;
}
.fz-cardio-indoor-track__end {
  position: absolute; top: 12px; right: 12px;
  appearance: none; -webkit-appearance: none;
  background: transparent; color: var(--on-accent);
  border: 1px solid var(--on-accent); border-radius: var(--radius-pill);
  padding: 4px 12px;
  font: inherit; font-size: 11px; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase;
  cursor: pointer;
  opacity: 0.85;
}
.fz-cardio-indoor-track__end[data-armed="1"] {
  background: var(--on-accent); color: var(--accent);
  opacity: 1;
}
.fz-cardio-indoor-track__stats {
  flex: 1;
  display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
  padding: 16px 16px calc(env(safe-area-inset-bottom, 0px) + 16px);
}
.fz-cardio-indoor-track__stat {
  grid-column: 1 / -1;
  font-size: 22px; font-weight: 600;
  color: var(--ink);
  font-family: 'Inter', 'DM Sans', -apple-system, sans-serif;
  font-variant-numeric: tabular-nums;
  text-align: center;
  padding: 8px 0;
}
.fz-cardio-indoor-track__weight,
.fz-cardio-indoor-track__field {
  display: flex; flex-direction: column; gap: 4px;
  font-size: 11px; color: var(--muted);
  letter-spacing: 0.04em;
}
.fz-cardio-indoor-track__weight { grid-column: 1 / -1; }
.fz-cardio-indoor-track__field--full { grid-column: 1 / -1; }
.fz-cardio-indoor-track__weight input,
.fz-cardio-indoor-track__field input,
.fz-cardio-indoor-track__field textarea {
  appearance: none; -webkit-appearance: none;
  background: var(--surface); color: var(--fg);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 10px 12px;
  font: inherit; font-size: 15px;
  font-variant-numeric: tabular-nums;
}
.fz-cardio-indoor-track__field textarea {
  font-variant-numeric: normal;
  resize: vertical; min-height: 48px;
}
.fz-cardio-indoor-track__weight input:focus,
.fz-cardio-indoor-track__field input:focus,
.fz-cardio-indoor-track__field textarea:focus {
  outline: 2px solid var(--accent); outline-offset: 1px;
  border-color: var(--accent);
}

/* ────────────────────────────────────────────────────────────
   CARDIO — Summary / Save screen (Sprint 8, v0.9.374)
   cardio_tab_spec.md §2.3. Indoor variant — map snapshot
   surface is intentionally absent (lands with the outdoor
   live-track sprint when GPS / route polyline arrive).
   Tokens-only — no hex literals on paint properties per
   css-hex-lint A11Y-12. --accent / --on-accent for the
   primary CTA per accent-buttons memory.
   ──────────────────────────────────────────────────────────── */
.fz-cardio-summary {
  padding: 16px 16px calc(env(safe-area-inset-bottom, 0px) + 16px);
  display: flex; flex-direction: column; gap: 16px;
}
.fz-cardio-summary__head {
  display: flex; flex-direction: column; gap: 2px;
}
/* Sprint 12 (CARDIO-WEATHER-CAPTURE, v0.9.378) — small chip in the summary
   head per spec §9.2. Renders icon + temperature + condition label + UV
   index when the session carries a captured weather_json snapshot. Tokens-
   only paint per css-hex-lint A11Y-12 / S22 — bg/border off --surface /
   --line, icon stroke off currentColor inherited from --fg. */
.fz-cardio-weather-chip {
  display: inline-flex; align-items: center; gap: 8px;
  margin-top: 6px;
  padding: 6px 10px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  font-size: 12px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  align-self: flex-start;
}
.fz-cardio-weather-chip__icon {
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--ink);
}
.fz-cardio-weather-chip__icon svg { display: block; }
.fz-cardio-weather-chip__temp {
  font-weight: 700;
}
.fz-cardio-weather-chip__cond {
  color: var(--muted);
  letter-spacing: 0.02em;
}
.fz-cardio-weather-chip__uv {
  color: var(--muted);
  border-left: 1px solid var(--line);
  padding-left: 8px;
  font-size: 11px;
  letter-spacing: 0.04em;
}
.fz-cardio-summary__title {
  font-size: 18px; font-weight: 700; color: var(--fg);
}
.fz-cardio-summary__sub {
  font-size: 11px; color: var(--muted);
  letter-spacing: 0.06em; text-transform: uppercase;
}
.fz-cardio-summary__stats {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
}
.fz-cardio-summary__stat {
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 12px;
  display: flex; flex-direction: column; gap: 2px;
}
.fz-cardio-summary__stat-v {
  font-family: 'Inter', 'DM Sans', -apple-system, sans-serif;
  font-variant-numeric: tabular-nums;
  font-size: 20px; font-weight: 700; color: var(--fg);
}
.fz-cardio-summary__stat-l {
  font-size: 11px; color: var(--muted);
  letter-spacing: 0.04em;
}
/* The honesty line under the stat grid — where the calorie number came from
   and how far it can be off. Only rendered for the app's OWN estimate. */
.fz-cardio-summary__kcal-note {
  font-size: 11px; line-height: 1.45; color: var(--muted);
  margin-top: 6px;
}
.fz-cardio-summary__section {
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 12px;
}
.fz-cardio-summary__section-h {
  font-size: 11px; font-weight: 700; color: var(--muted);
  letter-spacing: 0.08em; text-transform: uppercase;
  margin-bottom: 8px;
}
.fz-cardio-summary__splits-wrap {
  overflow-x: auto;
}
.fz-cardio-summary__splits {
  width: 100%; border-collapse: collapse;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.fz-cardio-summary__splits th,
.fz-cardio-summary__splits td {
  padding: 6px 8px;
  text-align: left;
  border-bottom: 1px solid var(--line);
}
.fz-cardio-summary__splits th {
  font-size: 11px; font-weight: 700; color: var(--muted);
  letter-spacing: 0.04em; text-transform: uppercase;
  cursor: pointer; user-select: none;
}
.fz-cardio-summary__splits th:hover { color: var(--fg); }
.fz-cardio-summary__splits td { color: var(--fg); }
.fz-cardio-summary__splits-empty td {
  text-align: center; color: var(--muted);
  padding: 16px 8px; font-style: italic;
}
.fz-cardio-summary__field {
  display: flex; flex-direction: column; gap: 4px;
  font-size: 11px; color: var(--muted);
  letter-spacing: 0.04em;
}
.fz-cardio-summary__field--full { width: 100%; }
.fz-cardio-summary__field select,
.fz-cardio-summary__field input,
.fz-cardio-summary__field textarea {
  appearance: none; -webkit-appearance: none;
  background: var(--surface); color: var(--fg);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 10px 12px;
  font: inherit; font-size: 15px;
  font-variant-numeric: tabular-nums;
}
.fz-cardio-summary__field textarea {
  font-variant-numeric: normal;
  resize: vertical; min-height: 64px;
}
.fz-cardio-summary__field select:focus,
.fz-cardio-summary__field input:focus,
.fz-cardio-summary__field textarea:focus {
  outline: 2px solid var(--accent); outline-offset: 1px;
  border-color: var(--accent);
}
.fz-cardio-summary__rpe {
  display: flex; flex-direction: column; gap: 6px;
}
.fz-cardio-summary__rpe-head {
  display: flex; justify-content: space-between; align-items: center;
  font-size: 11px; color: var(--muted);
  letter-spacing: 0.04em;
}
.fz-cardio-summary__rpe-v {
  font-family: 'Inter', 'DM Sans', -apple-system, sans-serif;
  font-variant-numeric: tabular-nums;
  font-size: 18px; font-weight: 700; color: var(--fg);
  letter-spacing: 0;
}
.fz-cardio-summary__rpe input[type="range"] {
  width: 100%;
  accent-color: var(--accent);
}
.fz-cardio-summary__rpe-scale {
  display: flex; justify-content: space-between;
  font-size: 10px; color: var(--muted);
}
.fz-cardio-summary__toggle {
  display: flex; align-items: center; gap: 10px;
  cursor: pointer;
}
.fz-cardio-summary__toggle input[type="checkbox"] {
  width: 20px; height: 20px;
  accent-color: var(--accent);
  cursor: pointer;
}
.fz-cardio-summary__toggle-label {
  font-size: 14px; color: var(--fg);
}
.fz-cardio-summary__actions {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
  margin-top: 4px;
}
.fz-cardio-summary__btn {
  appearance: none; -webkit-appearance: none;
  border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 14px 16px;
  font: inherit; font-size: 15px; font-weight: 700;
  cursor: pointer;
}
.fz-cardio-summary__btn--save {
  background: var(--accent); color: var(--on-accent);
  border-color: var(--accent);
}
.fz-cardio-summary__btn--ghost {
  background: var(--surface); color: var(--fg);
}
.fz-cardio-summary__btn:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.fz-cardio-summary__btn:disabled {
  opacity: 0.6; cursor: progress;
}

/* ──────────────────────────────────────────────────────────
   Cardio — MAP subsystem (Sprint 9, v0.9.375).
   Leaflet 1.9.x mount point + CSS-var theming layer (spec
   §4.2 — re-themable per tenant without code changes).
   The map root carries .fz-cardio-map; tokens default in
   :root (lime-night + blue-day presets) and may be over-
   ridden per-instance by PwaMapProvider.init({themeVars}).
   The OSM tile-pane carries `filter: var(--cardio-tile-filter)`
   so a tenant theme can hue-shift / desaturate the raster
   tiles to match its brand without re-baking tile assets.
   No hex literals on paint properties — tokens-only, per
   css-hex-lint A11Y-12 / S22.
   ────────────────────────────────────────────────────────── */
.fz-cardio-map {
  position: relative;
  width: 100%;
  min-height: 240px;
  margin: 0;
  padding: 0;
  background: var(--surface);
  /* Sprint 9 ships the abstraction; the indoor live-track surface
     keeps this hidden via display:none in cardio.html — outdoor GPS
     modes will reveal it in the live-track sprint. */
}
.fz-cardio-map .leaflet-tile-pane {
  /* Tenant brand-shift hook (spec §4.2). Default `none` is the
     vanilla OSM raster; an override can set e.g.
     `sepia(0.4) hue-rotate(220deg) saturate(0.85)` to brand-blend
     the basemap. The filter targets the tile pane only — markers,
     polylines, and the attribution control stay unfiltered so
     they keep WCAG contrast on top of the shifted basemap. */
  filter: var(--cardio-tile-filter);
}
.fz-cardio-map .leaflet-control-attribution {
  /* OSM tile-usage policy requires the attribution to remain
     visible. We re-style it to match the SPA tokens but never
     hide it — the css-hex-lint exempts :root tokens, the
     attribution copy itself is preset-aware. */
  background: var(--surface);
  color: var(--muted);
  font-size: 10px;
  padding: 2px 6px;
  border-radius: var(--radius-sm) 0 0 0;
}
.fz-cardio-map .leaflet-control-attribution a {
  color: var(--ink-2);
  border-bottom: 0;
}

/* ──────────────────────────────────────────────────────────
   Cardio — outdoor GPS live-track surface (Sprint 10,
   v0.9.376). Layout per cardio_tab_spec.md §2.2:
     top 60% live map (#fz-cardio-map host) +
     bottom 40% stat panel (.fz-cardio-gps-track__panel) +
     manual lap-button row + iOS banner above the panel +
     paused-overlay layered on top of the whole surface.
   Tokens-only — no hex literals on paint properties per
   css-hex-lint A11Y-12 / S22. --on-accent everywhere a
   control sits on --accent, per rule g + the accent-buttons
   feedback memory.
   ────────────────────────────────────────────────────────── */
.fz-cardio-gps-track {
  position: relative;
  display: flex; flex-direction: column;
  min-height: 40vh;
}
.fz-cardio-gps-track__panel {
  flex: 1;
  display: flex; flex-direction: column;
  background: var(--surface);
  color: var(--ink);
  padding: 12px 16px calc(env(safe-area-inset-bottom, 0px) + 16px);
  gap: 10px;
}
.fz-cardio-gps-track__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px;
}
.fz-cardio-gps-track__phase {
  font-size: 11px; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--muted);
}
/* CARDIO-GPS-LIFECYCLE — activity label sits LEFT of the toggle so the
   user always sees what's being measured (walk / run / cycle / …). */
.fz-cardio-gps-track__activity {
  display: flex; flex-direction: column; gap: 2px;
  min-width: 0; flex: 1 1 auto;
}
.fz-cardio-gps-track__activity-l {
  font-size: 11px; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--muted);
}
.fz-cardio-gps-track__activity-v {
  font-size: 18px; font-weight: 700;
  line-height: 1.1;
  color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* CARDIO-GPS-LIFECYCLE — START/STOP toggle. Off (data-state="off") is
   the accent-filled primary action. On (data-state="on") flips to a
   destructive outlined treatment so STOP reads as a deliberate
   negative-space action, never confusable with a normal accent CTA.
   --on-accent is used per feedback_accent_buttons_on_accent_token. */
.fz-cardio-gps-track__toggle {
  appearance: none; -webkit-appearance: none;
  border: 0; border-radius: var(--radius-pill);
  padding: 12px 22px;
  font: inherit; font-size: 14px; font-weight: 800;
  letter-spacing: 0.1em; text-transform: uppercase;
  cursor: pointer;
  flex: 0 0 auto;
  min-width: 96px;
  text-align: center;
  transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.fz-cardio-gps-track__toggle[data-state="off"] {
  background: var(--accent); color: var(--on-accent);
}
.fz-cardio-gps-track__toggle[data-state="on"] {
  background: transparent; color: var(--fg);
  border: 2px solid var(--fg);
}
.fz-cardio-gps-track__toggle[data-armed="1"] {
  background: var(--fg); color: var(--surface);
  border-color: var(--fg);
}
.fz-cardio-gps-track__primary {
  text-align: center;
  padding: 6px 0 2px;
}
.fz-cardio-gps-track__primary-v {
  font-size: 44px; font-weight: 700;
  letter-spacing: -1px; line-height: 1;
  color: var(--ink);
  font-family: 'Inter', 'DM Sans', -apple-system, sans-serif;
  font-variant-numeric: tabular-nums;
}
.fz-cardio-gps-track__primary-l {
  font-size: 11px; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--muted);
  margin-top: 4px;
}
.fz-cardio-gps-track__stats {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
}
.fz-cardio-gps-track__stat {
  background: var(--surface-muted, transparent);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 8px 10px;
  display: flex; flex-direction: column; gap: 2px;
}
.fz-cardio-gps-track__stat-v {
  font-size: 20px; font-weight: 600;
  color: var(--ink);
  font-family: 'Inter', 'DM Sans', -apple-system, sans-serif;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.fz-cardio-gps-track__stat-v .ht-ms {
  font-size: 14px; opacity: 0.6;
}
.fz-cardio-gps-track__stat-l {
  font-size: 10px; font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--muted);
}
.fz-cardio-gps-track__weight {
  display: flex; flex-direction: column; gap: 4px;
  font-size: 11px; color: var(--muted);
  letter-spacing: 0.04em;
}
.fz-cardio-gps-track__weight input {
  appearance: none; -webkit-appearance: none;
  background: var(--surface); color: var(--fg);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 10px 12px;
  font: inherit; font-size: 15px;
  font-variant-numeric: tabular-nums;
}
.fz-cardio-gps-track__actions {
  display: grid; grid-template-columns: auto 1fr; gap: 10px;
  margin-top: auto;
}
.fz-cardio-gps-track__lap {
  appearance: none; -webkit-appearance: none;
  background: var(--surface); color: var(--fg);
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  padding: 12px 22px;
  font: inherit; font-weight: 700; font-size: 13px;
  letter-spacing: 0.08em; text-transform: uppercase;
  cursor: pointer;
}
.fz-cardio-gps-track__pause {
  appearance: none; -webkit-appearance: none;
  background: var(--accent); color: var(--on-accent);
  border: 0; border-radius: var(--radius-pill);
  padding: 14px 28px;
  font: inherit; font-weight: 700; font-size: 14px;
  letter-spacing: 0.04em; text-transform: uppercase;
  cursor: pointer;
}
.fz-cardio-gps-track__pause[data-paused="1"] {
  background: var(--ok, var(--accent)); color: var(--on-accent);
}

/* iOS Safari "keep your screen on" banner — spec §7.7 mitigation.
   Rendered above the stat panel on iOS Safari only; on every other
   browser the bridge skips the markup entirely. */
.fz-cardio-ios-banner {
  background: var(--accent); color: var(--on-accent);
  font-size: 12px; font-weight: 600; line-height: 1.35;
  padding: 8px 14px;
  text-align: center;
}

/* GPS status strip — shown while waiting for the first GPS sample or when
   watchPosition errors out. The dot uses a subtle pulse for the acquiring
   state and a steadier hue for the error state so the user can read intent
   at a glance without reading copy. */
.fz-cardio-gps-status {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 14px;
  background: var(--surface);
  color: var(--ink);
  border-bottom: 1px solid var(--line);
  font-size: 13px; line-height: 1.35;
}
.fz-cardio-gps-status[hidden] { display: none; }
.fz-cardio-gps-status__dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--muted);
  flex: 0 0 auto;
}
.fz-cardio-gps-status[data-state="acquiring"] .fz-cardio-gps-status__dot {
  background: var(--accent);
  animation: fz-cardio-gps-pulse var(--dur-pulse) var(--ease-standard) infinite;
}
.fz-cardio-gps-status[data-state="error"] .fz-cardio-gps-status__dot {
  background: var(--danger);
}
.fz-cardio-gps-status[data-state="error"] {
  color: var(--danger); font-weight: 600;
}
/* CARDIO-GPS-OVERLAY-DISMISS-AND-MAP-FILL (v0.9.420) — paused-pill state.
   Surfaced after the user tap-dismisses the modal overlay; clicking the
   pill re-opens the modal so the full guidance is one tap away. The
   warning hue uses --warning if defined, falling back to --accent so the
   theme's day/night both have a sensible amber. */
.fz-cardio-gps-status[data-state="paused"] .fz-cardio-gps-status__dot {
  background: var(--warning, var(--accent));
}
.fz-cardio-gps-status[data-state="paused"] {
  color: var(--ink); font-weight: 600;
  cursor: pointer;
  touch-action: manipulation;
}
.fz-cardio-gps-status__msg { flex: 1; }
@keyframes fz-cardio-gps-pulse {
  0%, 100% { opacity: 0.35; transform: scale(0.85); }
  50%      { opacity: 1;    transform: scale(1.15); }
}

/* CARDIO-MAP-FILL + CARDIO-GPS-OVERLAY-DISMISS-AND-MAP-FILL (v0.9.611) —
   When the GPS map host is visible (showMapHost() sets data-fz-map-visible),
   flip the body into an exact-height flex column so #fz-cardio-map stretches
   to fill ALL remaining space below the stat panel. Using height:100dvh (not
   min-height) is critical — min-height let the container grow taller than the
   iframe, leaving a gray strip above or below the map. overflow:hidden clips
   any accidental overflow inside the iframe boundary.
   data-fz-cardio-mode="gps" is kept for GPS-specific sub-rules (gps-track
   min-height, etc.) but the flex layout now lives on data-fz-map-visible so
   future non-GPS map surfaces get the same fill automatically. */
body[data-fz-map-visible] {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  margin: 0;
}
body[data-fz-map-visible] #fz-cardio-app {
  flex: 0 0 auto;
  min-height: 0;
  padding-bottom: 0;
}
body[data-fz-map-visible] #fz-cardio-map {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
}
body[data-fz-cardio-mode="gps"] .fz-cardio-gps-track {
  min-height: 0;
}

/* CARDIO-MAP-RECENTER-BTN (v0.9.611) — FAB overlaid on the GPS map.
   Hidden until the user drags (dragstart → setFollow(false)); tap re-enables
   auto-follow and pans back to the current position. position:absolute works
   because Leaflet sets position:relative on its container during init(). */
.fz-cardio-recenter {
  position: absolute;
  bottom: 16px;
  right: 16px;
  z-index: 1000;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: none;
  background: var(--surface);
  color: var(--accent);
  box-shadow: var(--elev-1);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
}
.fz-cardio-recenter[hidden] { display: none; }

/* CARDIO-MAP-BACKGROUND-PICKER (v0.9.613) — map is the full-screen
   background layer; picker tiles float over it in a transparent overlay.
   #fz-cardio-app uses pointer-events:none so touches on the transparent
   gaps fall through to the Leaflet map (allowing map pan). The picker
   itself restores pointer-events:auto so cards + scroll work normally.
   The picker is its own scroll container (overflow-y:auto, max-height
   fits viewport) so vertical fling on the cards area scrolls the list. */
body[data-fz-cardio-mode="picker"] #fz-cardio-map {
  position: fixed;
  inset: 0;
  z-index: 0;
  display: block;
}
body[data-fz-cardio-mode="picker"] #fz-cardio-app {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: transparent;
  min-height: 0;
  padding: 0;
  overflow: visible;
}
body[data-fz-cardio-mode="picker"] .fz-cardio-picker {
  pointer-events: auto;
  background: transparent;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  max-height: calc(100dvh - env(safe-area-inset-top, 0px));
  /* CHAT-LAUNCH-VISIBLE-ON-ALL-SURFACES-NO-OVERLAP (v0.9.701) — the parent's
     global chat-launch pill floats over the top-right of this full-screen map
     view; add the pill strip (52px) to the top padding so the first activity
     card never slides under it. (Shared app.css: this rule runs inside the
     cardio iframe, the recenter button is bottom-right so it's unaffected.) */
  padding: calc(env(safe-area-inset-top, 0px) + 16px + 52px) 16px
           calc(env(safe-area-inset-bottom, 0px) + var(--tabbar-h, 64px) + 8px);
}
/* Picker recenter button — always visible, sits above tabbar. Overrides
   the GPS recenter's bottom:16px with room for the tabbar. */
.fz-cardio-recenter--picker {
  display: flex;
  bottom: calc(var(--tabbar-h, 64px) + env(safe-area-inset-bottom, 0px) + 12px);
  pointer-events: auto;
}

/* ===== Sprint 11 — CARDIO-HR-WEB-BLUETOOTH-AND-ZONES (v0.9.377) =====
   HR widget + zone color tokens + BPM-synced pulse animation + post-
   session zone-bar chart per cardio_tab_spec §6.5 + §6.6. The zone
   colors map Karvonen-style intensity to perceptually-ordered hues
   (cool blue → warm red) and resolve against the same Dag + Nacht
   token presets the rest of the cardio surface uses, so contrast
   holds in both themes. */
:root,
html[data-theme-mode="dark"] {
  --hr-zone-1: #2e7dff;  /* Recovery — cool blue */
  --hr-zone-2: #2ec4b6;  /* Aerobic — teal */
  --hr-zone-3: #c8ff4a;  /* Tempo — accent lime */
  --hr-zone-4: #ff9f1c;  /* Threshold — amber */
  --hr-zone-5: #e84545;  /* VO2 max — red */
  --hr-zone-fg: #121212;
}
html[data-theme-mode="light"] {
  --hr-zone-1: #2a6df4;
  --hr-zone-2: #14a39a;
  --hr-zone-3: #88b800;
  --hr-zone-4: #d97706;
  --hr-zone-5: #c0392b;
  --hr-zone-fg: #ffffff;
}

/* BPM-synced pulse — animation-duration is driven by an inline
   --fz-hr-pulse-dur CSS variable set by cardio-bridge.js's hrWidgetHtml()
   to (60 / bpm)s. The keyframes scale the .fz-cardio-hr__big block by
   3 % at the systolic peak so the pulse is visible without dominating
   the layout. */
@keyframes fz-hr-pulse {
  0%   { transform: scale(1);    opacity: 1; }
  35%  { transform: scale(1.03); opacity: 0.95; }
  60%  { transform: scale(1);    opacity: 1; }
  100% { transform: scale(1);    opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  @keyframes fz-hr-pulse {
    0%, 100% { transform: none; opacity: 1; }
  }
}

.fz-cardio-hr {
  margin: 12px 0 0;
  padding: 14px;
  border-radius: var(--radius-lg);
  background: var(--surface);
  border: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 10px;
  transition: background-color var(--dur-base) var(--ease-standard), border-color var(--dur-base) var(--ease-standard), color var(--dur-base) var(--ease-standard);
}
.fz-cardio-hr[data-zone="Z1"] { background: var(--hr-zone-1); border-color: var(--hr-zone-1); color: var(--hr-zone-fg); }
.fz-cardio-hr[data-zone="Z2"] { background: var(--hr-zone-2); border-color: var(--hr-zone-2); color: var(--hr-zone-fg); }
.fz-cardio-hr[data-zone="Z3"] { background: var(--hr-zone-3); border-color: var(--hr-zone-3); color: var(--hr-zone-fg); }
.fz-cardio-hr[data-zone="Z4"] { background: var(--hr-zone-4); border-color: var(--hr-zone-4); color: var(--hr-zone-fg); }
.fz-cardio-hr[data-zone="Z5"] { background: var(--hr-zone-5); border-color: var(--hr-zone-5); color: var(--hr-zone-fg); }

.fz-cardio-hr__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px;
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em;
}
.fz-cardio-hr__zone {
  font-weight: 700;
  font-size: 13px;
}
.fz-cardio-hr__max {
  font-size: 11px; opacity: 0.8;
}
.fz-cardio-hr__connect {
  appearance: none;
  background: var(--accent); color: var(--on-accent);
  border: 0; border-radius: var(--radius-pill);
  padding: 6px 14px;
  font-size: 12px; font-weight: 700;
  cursor: pointer;
}
.fz-cardio-hr__connect:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
.fz-cardio-hr__big {
  display: flex; align-items: baseline; justify-content: center;
  gap: 6px;
  font-variant-numeric: tabular-nums;
  animation: fz-hr-pulse var(--fz-hr-pulse-dur, var(--dur-pulse)) var(--ease-standard) infinite;
  transform-origin: center;
}
.fz-cardio-hr[data-zone] .fz-cardio-hr__big {
  /* Only animate when connected (zone attribute means we have a BPM) */
}
.fz-cardio-hr:not([data-zone]) .fz-cardio-hr__big {
  animation: none;
}
.fz-cardio-hr__bpm {
  font-size: 56px; font-weight: 800; line-height: 1;
}
.fz-cardio-hr__bpm-l {
  font-size: 14px; font-weight: 600; opacity: 0.8;
}
.fz-cardio-hr__manual {
  display: flex; gap: 8px; align-items: center; justify-content: center;
}
.fz-cardio-hr__tap {
  appearance: none;
  background: var(--surface); color: var(--fg);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 6px 14px;
  font-size: 13px; font-weight: 700;
  cursor: pointer;
}
.fz-cardio-hr[data-zone] .fz-cardio-hr__tap {
  background: rgba(0, 0, 0, 0.18);
  color: var(--hr-zone-fg);
  border-color: rgba(0, 0, 0, 0.25);
}
.fz-cardio-hr__manual input[type="number"] {
  appearance: none;
  background: var(--surface); color: var(--fg);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 6px 10px;
  font-size: 13px;
  width: 80px; text-align: center;
}
.fz-cardio-hr[data-zone] .fz-cardio-hr__manual input[type="number"] {
  background: rgba(0, 0, 0, 0.18);
  color: var(--hr-zone-fg);
  border-color: rgba(0, 0, 0, 0.25);
}
.fz-cardio-hr__tiz {
  display: grid; grid-template-columns: repeat(5, 1fr);
  gap: 6px;
  margin-top: 4px;
}
.fz-cardio-hr__tiz-cell {
  text-align: center;
  border-radius: var(--radius-md);
  padding: 6px 4px;
  background: rgba(255, 255, 255, 0.06);
  font-variant-numeric: tabular-nums;
}
.fz-cardio-hr[data-zone] .fz-cardio-hr__tiz-cell {
  background: rgba(0, 0, 0, 0.18);
}
.fz-cardio-hr__tiz-l {
  font-size: 11px; font-weight: 700; opacity: 0.75;
}
.fz-cardio-hr__tiz-v {
  font-size: 13px; font-weight: 700; line-height: 1.2;
}
.fz-cardio-hr__tiz-cell[data-zone="Z1"] .fz-cardio-hr__tiz-l { color: var(--hr-zone-1); }
.fz-cardio-hr__tiz-cell[data-zone="Z2"] .fz-cardio-hr__tiz-l { color: var(--hr-zone-2); }
.fz-cardio-hr__tiz-cell[data-zone="Z3"] .fz-cardio-hr__tiz-l { color: var(--hr-zone-3); }
.fz-cardio-hr__tiz-cell[data-zone="Z4"] .fz-cardio-hr__tiz-l { color: var(--hr-zone-4); }
.fz-cardio-hr__tiz-cell[data-zone="Z5"] .fz-cardio-hr__tiz-l { color: var(--hr-zone-5); }
.fz-cardio-hr[data-zone] .fz-cardio-hr__tiz-cell .fz-cardio-hr__tiz-l {
  color: var(--hr-zone-fg);
}

/* Indoor + GPS surfaces both render the widget via a [data-fz-hr-slot]
   wrapper. The wrappers below give the widget the right side-padding
   so it lines up with the surrounding stat / action rows. */
.fz-cardio-indoor-track__hr,
.fz-cardio-gps-track__hr {
  padding: 0 14px;
}

/* Post-session zone-bar chart (§6.6) — 5 horizontal bars, color-coded,
   length proportional to minutes-in-zone. The .fz-cardio-zonebar__rng
   pill carries the BPM range for that zone (e.g. "114–133 bpm") so the
   user can read max_hr / zones at a glance. */
.fz-cardio-zonebar {
  display: flex; flex-direction: column; gap: 8px;
  padding: 4px 0 2px;
}
.fz-cardio-zonebar--empty {
  color: var(--muted);
  font-size: 13px;
  padding: 12px 0;
  text-align: center;
}
.fz-cardio-zonebar__row {
  display: grid;
  grid-template-columns: 28px 1fr auto;
  align-items: center;
  gap: 10px;
  font-variant-numeric: tabular-nums;
}
.fz-cardio-zonebar__l {
  font-weight: 700; font-size: 13px;
  text-align: center;
}
.fz-cardio-zonebar__row[data-zone="Z1"] .fz-cardio-zonebar__l { color: var(--hr-zone-1); }
.fz-cardio-zonebar__row[data-zone="Z2"] .fz-cardio-zonebar__l { color: var(--hr-zone-2); }
.fz-cardio-zonebar__row[data-zone="Z3"] .fz-cardio-zonebar__l { color: var(--hr-zone-3); }
.fz-cardio-zonebar__row[data-zone="Z4"] .fz-cardio-zonebar__l { color: var(--hr-zone-4); }
.fz-cardio-zonebar__row[data-zone="Z5"] .fz-cardio-zonebar__l { color: var(--hr-zone-5); }
.fz-cardio-zonebar__track {
  height: 14px; border-radius: var(--radius-md);
  background: var(--line);
  overflow: hidden;
}
.fz-cardio-zonebar__fill {
  height: 100%;
  border-radius: var(--radius-md);
  transition: width var(--dur-base) var(--ease-standard);
}
.fz-cardio-zonebar__row[data-zone="Z1"] .fz-cardio-zonebar__fill { background: var(--hr-zone-1); }
.fz-cardio-zonebar__row[data-zone="Z2"] .fz-cardio-zonebar__fill { background: var(--hr-zone-2); }
.fz-cardio-zonebar__row[data-zone="Z3"] .fz-cardio-zonebar__fill { background: var(--hr-zone-3); }
.fz-cardio-zonebar__row[data-zone="Z4"] .fz-cardio-zonebar__fill { background: var(--hr-zone-4); }
.fz-cardio-zonebar__row[data-zone="Z5"] .fz-cardio-zonebar__fill { background: var(--hr-zone-5); }
.fz-cardio-zonebar__v {
  font-size: 13px; font-weight: 700;
  display: flex; align-items: baseline; gap: 6px;
}
.fz-cardio-zonebar__rng {
  font-size: 10px; font-weight: 600;
  opacity: 0.6;
}

/* ───────────────────────────────────────────────────────────────
   Sprint 13 (CARDIO-SAVED-ROUTES-AND-GHOST-RERUN, v0.9.379)
   cardio_tab_spec §11 — saved-routes library + ghost re-run +
   post-finish side-by-side comparison. Three blocks below:
     .fz-cardio-route-library  — list + filter chips + sort + range
     .fz-cardio-ghost-stat-cell — live ghost-time stat (ahead/behind)
     .fz-cardio-comparison      — summary side-by-side new vs prev
   ───────────────────────────────────────────────────────────── */

/* --cardio-ghost is the reference-route polyline token; resolved by
   PwaMapProvider.addGhostPolyline. Themed defaults per preset so a
   tenant skin can override via CSS-only. */
:root,
html[data-theme-mode="dark"] {
  --cardio-ghost: #5b8def;
  --cardio-ghost-stat-ahead:  #6ee85a;
  --cardio-ghost-stat-behind: #ff7e7e;
}
html[data-theme-mode="light"] {
  --cardio-ghost: #ffb300;
  --cardio-ghost-stat-ahead:  #1f9a4b;
  --cardio-ghost-stat-behind: #d23434;
}

.fz-cardio-route-library { padding: 16px 16px calc(env(safe-area-inset-bottom, 0px) + 16px); }
.fz-cardio-route-library__head {
  display: flex; align-items: center; gap: 10px; margin-bottom: 12px;
}
.fz-cardio-route-library__back {
  appearance: none; background: transparent; color: var(--fg);
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  padding: 6px 12px; font-size: 12px; font-weight: 600; cursor: pointer;
}
.fz-cardio-route-library__title {
  font-size: 15px; font-weight: 700; letter-spacing: 0.01em;
}
.fz-cardio-route-library__count {
  margin-left: auto; font-size: 11px; color: var(--muted);
}
.fz-cardio-route-library__filters {
  display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px;
}
.fz-cardio-route-library__chip {
  appearance: none; background: var(--surface); color: var(--fg);
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  padding: 5px 10px; font-size: 11px; font-weight: 600; cursor: pointer;
  letter-spacing: 0.02em;
}
.fz-cardio-route-library__chip[aria-pressed="true"] {
  background: var(--accent); color: var(--on-accent); border-color: var(--accent);
}
.fz-cardio-route-library__sort-row {
  display: flex; align-items: center; gap: 8px; margin-bottom: 14px;
  flex-wrap: wrap;
}
.fz-cardio-route-library__sort-row label {
  font-size: 11px; color: var(--muted); text-transform: uppercase;
  letter-spacing: 0.08em; font-weight: 600;
}
.fz-cardio-route-library__sort {
  appearance: none; background: var(--surface); color: var(--fg);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 6px 10px; font-size: 12px;
}
.fz-cardio-route-library__distance-range {
  display: flex; align-items: center; gap: 6px; flex: 1; min-width: 0;
}
.fz-cardio-route-library__distance-range input {
  appearance: none; -webkit-appearance: none;
  background: var(--surface); color: var(--fg);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 6px 8px; font-size: 12px; width: 100%; min-width: 0;
  font-variant-numeric: tabular-nums;
}
.fz-cardio-route-library__distance-range span { font-size: 11px; color: var(--muted); }
.fz-cardio-route-library__list {
  display: flex; flex-direction: column; gap: 10px;
}
.fz-cardio-route-library__row {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: 12px;
  display: grid; grid-template-columns: 64px 1fr auto; gap: 12px;
  align-items: center; cursor: pointer;
}
.fz-cardio-route-library__row:focus-within {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.fz-cardio-route-library__snap {
  width: 64px; height: 64px; border-radius: var(--radius-md);
  background-color: var(--surface);
  background-size: cover; background-position: center;
  border: 1px solid var(--line); overflow: hidden;
}
.fz-cardio-route-library__snap--placeholder {
  background-image:
    linear-gradient(45deg, transparent 48%, var(--cardio-polyline, var(--accent)) 48%, var(--cardio-polyline, var(--accent)) 52%, transparent 52%),
    linear-gradient(-30deg, transparent 60%, var(--cardio-ghost) 60%, var(--cardio-ghost) 64%, transparent 64%);
  opacity: 0.55;
}
.fz-cardio-route-library__name {
  font-size: 14px; font-weight: 600; margin-bottom: 4px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fz-cardio-route-library__meta {
  font-size: 11px; color: var(--muted);
  display: flex; gap: 8px; flex-wrap: wrap;
}
.fz-cardio-route-library__meta b { color: var(--fg); font-weight: 600; }
.fz-cardio-route-library__star {
  appearance: none; background: transparent; border: 0; cursor: pointer;
  padding: 6px; color: var(--muted);
}
.fz-cardio-route-library__star[aria-pressed="true"] { color: var(--accent); }
.fz-cardio-route-library__star svg {
  width: 22px; height: 22px;
  stroke: currentColor; fill: none; stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
}
.fz-cardio-route-library__star[aria-pressed="true"] svg { fill: currentColor; }
.fz-cardio-route-library__empty {
  text-align: center; padding: 40px 20px; color: var(--muted); font-size: 12px;
}
.fz-cardio-route-library__detail {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: 14px; margin-top: 14px; display: none;
}
.fz-cardio-route-library__detail.is-open { display: block; }
.fz-cardio-route-library__detail-h {
  font-size: 13px; font-weight: 600; margin-bottom: 6px;
}
.fz-cardio-route-library__detail-meta {
  font-size: 11px; color: var(--muted); margin-bottom: 12px;
}
.fz-cardio-route-library__detail-actions {
  display: flex; gap: 8px; flex-wrap: wrap;
}
.fz-cardio-route-library__btn {
  appearance: none; flex: 1 1 0; padding: 9px 14px; border-radius: var(--radius-md);
  font-size: 12px; font-weight: 700; cursor: pointer;
  letter-spacing: 0.04em; text-transform: uppercase;
}
.fz-cardio-route-library__btn--rerun {
  background: var(--accent); color: var(--on-accent); border: 0;
}
.fz-cardio-route-library__btn--rename {
  background: transparent; color: var(--fg); border: 1px solid var(--line);
}
.fz-cardio-route-library__btn--delete {
  background: transparent; color: var(--cardio-ghost-stat-behind);
  border: 1px solid var(--cardio-ghost-stat-behind);
}

/* Ghost-time stat — overrides the calorie stat cell during a re-run.
   Bridge writes data-fz-ghost-state="ahead|behind" so we recolour
   without churning DOM. */
.fz-cardio-ghost-stat-cell {
  font-variant-numeric: tabular-nums;
}
.fz-cardio-ghost-stat-cell[data-fz-ghost-state="ahead"]  { color: var(--cardio-ghost-stat-ahead); }
.fz-cardio-ghost-stat-cell[data-fz-ghost-state="behind"] { color: var(--cardio-ghost-stat-behind); }

/* Post-finish side-by-side comparison. Renders inside a
   .fz-cardio-summary__section block, so margins are owned by the
   parent — our root just paints the card chrome. */
.fz-cardio-comparison {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: 14px;
}
.fz-cardio-comparison__h {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--muted); font-weight: 600; margin-bottom: 10px;
}
.fz-cardio-comparison__cols {
  display: grid; grid-template-columns: 1fr 12px 1fr; gap: 8px;
  align-items: stretch;
}
.fz-cardio-comparison__col {
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 10px;
}
.fz-cardio-comparison__col-h {
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--muted); font-weight: 600; margin-bottom: 8px;
}
.fz-cardio-comparison__col--new  .fz-cardio-comparison__col-h { color: var(--accent); }
.fz-cardio-comparison__col--prev .fz-cardio-comparison__col-h { color: var(--cardio-ghost); }
.fz-cardio-comparison__row {
  display: flex; justify-content: space-between; padding: 4px 0;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.fz-cardio-comparison__row b { font-weight: 600; }
.fz-cardio-comparison__divider {
  align-self: center; color: var(--muted); font-weight: 700;
  text-align: center; font-size: 12px;
}
.fz-cardio-comparison__delta {
  margin-top: 12px; padding: 10px; border-radius: var(--radius-md);
  display: flex; justify-content: space-between; align-items: center;
}
.fz-cardio-comparison__delta--ahead {
  background: color-mix(in srgb, var(--cardio-ghost-stat-ahead) 12%, transparent);
  border: 1px solid var(--cardio-ghost-stat-ahead);
}
.fz-cardio-comparison__delta--behind {
  background: color-mix(in srgb, var(--cardio-ghost-stat-behind) 12%, transparent);
  border: 1px solid var(--cardio-ghost-stat-behind);
}
.fz-cardio-comparison__delta-l {
  font-size: 11px; color: var(--muted); text-transform: uppercase;
  letter-spacing: 0.08em; font-weight: 600;
}
.fz-cardio-comparison__delta-v {
  font-size: 16px; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.fz-cardio-comparison__delta--ahead  .fz-cardio-comparison__delta-v { color: var(--cardio-ghost-stat-ahead); }
.fz-cardio-comparison__delta--behind .fz-cardio-comparison__delta-v { color: var(--cardio-ghost-stat-behind); }

/* v0.9.380 CARDIO-MANUAL-ROUTE-DRAWING-AND-ADHERENCE (cardio_tab_spec §10)
   Pre-planned route DRAWING surface + LIVE-session adherence overlay.
   The planner panel floats over the full-bleed map; the adherence overlay
   stacks above the GPS stat panel so the dashed planned-route polyline
   stays visible behind it. Stray-toast pins to the bottom of the surface
   with a single Sassu-branded action. Waypoint markers use a DivIcon so
   theming flows through CSS variables (no sprite swaps). */
/* CARDIO-MAP-RENDER-LAYOUT-AUDIT-AND-FIX (v0.9.465) — planner switches from
   floating absolute-overlay to a normal-flow flex column. The dedicated map
   host (#fz-cardio-planner-map) sits between the stats row and the form as
   a flex:1 1 0 slot so the layout reads header → hint → stats → big map →
   form → actions (Jayy screenshot 2 / _MOCK_cardio_map_themed_v1.html ratio
   ~50–60% viewport). Pre-fix used position:absolute + margin-top:auto on the
   form which left an empty band above the form and the persistent #fz-cardio-map
   sibling extending past the iframe viewport. min-height:100dvh keeps the
   surface stable across iOS Safari URL-bar collapse; padding-bottom carries
   the kaizen 2026-06-06 STRENGTH-EDIT-ARCHIVE-SCROLL-OVERFLOW-FIX defensive
   pattern (safe-area + 16px buffer) so any future fixed-bottom UI can't clip
   the Save row. */
.fz-cardio-route-planner {
  display: flex; flex-direction: column; gap: 8px;
  padding: 12px 12px calc(env(safe-area-inset-bottom, 0px) + 16px);
  min-height: 100vh;
  min-height: 100dvh;
}
.fz-cardio-route-planner__map {
  flex: 1 1 0;
  min-height: 240px;
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--line);
}
.fz-cardio-route-planner__head {
  display: flex; align-items: center; gap: 8px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 8px 10px;
  color: var(--ink);
}
.fz-cardio-route-planner__back,
.fz-cardio-route-planner__undo {
  background: transparent; color: var(--fg);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 6px 10px; font-size: 13px; cursor: pointer;
}
.fz-cardio-route-planner__undo[disabled] { opacity: 0.4; cursor: default; }
.fz-cardio-route-planner__title {
  flex: 1; text-align: center;
  font-size: 14px; font-weight: 600;
}
.fz-cardio-route-planner__hint {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 8px 12px;
  font-size: 12px; color: var(--muted);
}
.fz-cardio-route-planner__stats {
  display: flex; justify-content: space-around; gap: 8px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 8px 12px;
  font-size: 13px; color: var(--fg);
  font-variant-numeric: tabular-nums;
}
.fz-cardio-route-planner__form {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 12px;
}
.fz-cardio-route-planner__field { display: grid; gap: 4px; font-size: 12px; color: var(--muted); }
.fz-cardio-route-planner__field--full { grid-column: 1 / -1; }
.fz-cardio-route-planner__field input,
.fz-cardio-route-planner__field select,
.fz-cardio-route-planner__field textarea {
  background: var(--bg); color: var(--fg);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 8px 10px; font-size: 14px;
  font-family: inherit;
}
.fz-cardio-route-planner__field textarea { resize: vertical; min-height: 56px; }
.fz-cardio-route-planner__form-foot {
  grid-column: 1 / -1;
  display: flex; gap: 8px; justify-content: flex-end;
}
.fz-cardio-route-planner__btn {
  border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 10px 14px; font-size: 14px; font-weight: 600; cursor: pointer;
}
.fz-cardio-route-planner__btn--secondary {
  background: var(--surface); color: var(--fg);
}
.fz-cardio-route-planner__btn--primary {
  background: var(--accent); color: var(--on-accent);
  border-color: var(--accent);
}
.fz-cardio-route-planner__btn[disabled] { opacity: 0.5; cursor: default; }

.fz-cardio-route-library__draw {
  background: var(--accent); color: var(--on-accent);
  border: 1px solid var(--accent); border-radius: var(--radius-md);
  padding: 6px 12px; font-size: 13px; font-weight: 600; cursor: pointer;
}

/* Waypoint markers — Leaflet DivIcon. The numbered chip uses the planned-
   route color so the planner reads as a single visual system. */
.fz-cardio-waypoint {
  background: transparent;
  pointer-events: auto;
}
.fz-cardio-waypoint__num {
  display: flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--cardio-planned);
  color: var(--on-planned);
  border: 2px solid var(--surface);
  font-size: 12px; font-weight: 700;
  font-family: inherit;
  box-shadow: var(--elev-1);
}

/* Adherence overlay — slots inside the .fz-cardio-gps-track__panel. The
   percentage + deviation cells mirror the .fz-cardio-gps-track__stat
   shape so the surface reads as a cohesive stack. data-fz-adh-strayed
   tints the overlay red when the user is past the configured threshold. */
.fz-cardio-adherence-overlay {
  display: flex; flex-direction: column; gap: 6px;
  margin: 4px 0;
  padding: 8px 10px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}
.fz-cardio-adherence-overlay__head {
  display: flex; align-items: center; gap: 8px;
  font-size: 12px; color: var(--muted);
}
.fz-cardio-adherence-overlay__name {
  flex: 1;
  font-size: 12px; font-weight: 600; color: var(--fg);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fz-cardio-adherence-overlay__close {
  background: transparent; border: 0; color: var(--muted);
  font-size: 18px; line-height: 1; cursor: pointer; padding: 2px 6px;
}
.fz-cardio-adherence-overlay__row {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
}
.fz-cardio-adherence-overlay__cell {
  text-align: center;
  padding: 4px 0;
}
.fz-cardio-adherence-overlay__v {
  font-size: 18px; font-weight: 700; color: var(--fg);
  font-variant-numeric: tabular-nums;
}
.fz-cardio-adherence-overlay__l {
  font-size: 11px; color: var(--muted);
}
.fz-cardio-adherence-overlay[data-fz-adh-strayed="1"] {
  border-color: var(--danger);
}
.fz-cardio-adherence-overlay[data-fz-adh-strayed="1"] .fz-cardio-adherence-overlay__v {
  color: var(--danger);
}

.fz-cardio-route-library__btn--attach {
  background: var(--cardio-planned); color: var(--on-planned);
  border-color: var(--cardio-planned);
}

/* Stray-from-route toast — single-line notification with a Sassu-styled
   "Uit" disable action. Pinned bottom-center; fades after ~5.5 s in JS. */
.fz-cardio-stray-toast {
  position: absolute;
  left: 50%; transform: translateX(-50%);
  bottom: calc(env(safe-area-inset-bottom, 0) + 76px);
  z-index: 12;
  display: flex; align-items: center; gap: 10px;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--danger);
  border-radius: var(--radius-pill);
  padding: 8px 12px 8px 14px;
  font-size: 13px;
  box-shadow: var(--elev-2);
  max-width: calc(100% - 24px);
}
.fz-cardio-stray-toast__msg { color: var(--fg); }
.fz-cardio-stray-toast__action {
  background: transparent; color: var(--muted);
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  padding: 4px 10px; font-size: 12px; cursor: pointer;
}

/* CARDIO-CONSENT-AND-PRIVACY-UI (cardio_tab_spec §13) — first-time GPS
   consent dialog rendered into #fz-cardio-app, gating the Modality Picker
   from starting an outdoor session until users.cardio_consent_at is
   stamped via POST /api/me/cardio-consent. Anatomie spiegelt de overige
   cardio-surfaces (rounded card, --surface fill, accent CTA); copy zelf
   blijft sober en plain-language Nederlands. */
.fz-cardio-consent {
  padding: 16px;
  display: flex; flex-direction: column; gap: 14px;
  max-width: 520px; margin: 0 auto;
  /* Own scroll container so the CTA stays reachable inside the cardio iframe,
     independent of the parent SPA scroll + bottom-nav chrome. */
  max-height: 100vh;
  max-height: 100dvh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  /* Reserve room for the iOS home indicator + the parent app's bottom-nav
     (~72px) so the OK button never hides behind the chrome. */
  padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 88px);
}
.fz-cardio-consent__h {
  margin: 0;
  font-size: 18px; font-weight: 700; line-height: 1.25;
  color: var(--ink);
}
.fz-cardio-consent__lede {
  margin: 0; font-size: 14px; color: var(--fg); line-height: 1.45;
}
.fz-cardio-consent__card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: 14px;
  display: flex; flex-direction: column; gap: 10px;
}
.fz-cardio-consent__card-h {
  margin: 0; font-size: 12px; color: var(--muted);
  letter-spacing: 0.1em; text-transform: uppercase; font-weight: 600;
}
.fz-cardio-consent__list {
  margin: 0; padding-left: 18px;
  font-size: 13px; line-height: 1.5; color: var(--fg);
}
.fz-cardio-consent__list li { margin-bottom: 4px; }
.fz-cardio-consent__list li:last-child { margin-bottom: 0; }
.fz-cardio-consent__note {
  margin: 0; font-size: 12px; color: var(--muted); line-height: 1.45;
}
.fz-cardio-consent__actions {
  display: flex; flex-direction: column; gap: 8px;
  margin-top: 4px;
}
.fz-cardio-consent__btn {
  appearance: none; -webkit-appearance: none;
  border: 0; border-radius: var(--radius-pill);
  padding: 12px 16px; font: inherit;
  font-size: 14px; font-weight: 700; letter-spacing: 0.01em;
  cursor: pointer;
}
.fz-cardio-consent__btn--ok {
  background: var(--accent); color: var(--on-accent);
}
.fz-cardio-consent__btn--ok[disabled] {
  opacity: 0.6; cursor: progress;
}
.fz-cardio-consent__btn--no {
  background: transparent; color: var(--fg);
  border: 1px solid var(--line);
}
.fz-cardio-consent__error {
  margin: 0; padding: 8px 10px;
  background: var(--surface); border: 1px solid var(--danger);
  border-radius: var(--radius-md); color: var(--danger);
  font-size: 12px; line-height: 1.4;
}

/* CARDIO-CONSENT-SCREEN-SCROLLABLE-AND-COMPACT-ON-SMALL-IPHONE — compact the
   surface on short viewports (iPhone 13/14 mini, SE 3) so the OK button fits
   above the parent SPA bottom-nav. CTA padding stays ≥44px (Apple HIG). */
@media (max-height: 740px) {
  .fz-cardio-consent {
    gap: 10px; padding: 12px 14px;
    padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 88px);
  }
  .fz-cardio-consent__lede { font-size: 13px; }
  .fz-cardio-consent__card { gap: 8px; padding: 12px; }
  .fz-cardio-consent__card-h { font-size: 11px; }
  .fz-cardio-consent__list { line-height: 1.4; }
  .fz-cardio-consent__actions { margin-top: 0; }
}

/* CARDIO-TILES-OUTDOOR-FOCUSED-REWORK — inline 'What was it?' prompt for the
   Other tile. Modal overlay over the picker; the card mirrors the consent
   dialog token palette (--surface / --line / --accent / --on-accent). */
.fz-cardio-other-prompt {
  position: fixed; inset: 0; z-index: 50;
  display: flex; align-items: center; justify-content: center;
  padding: 16px;
  background: rgba(0, 0, 0, 0.5);
}
.fz-cardio-other-prompt__card {
  width: 100%; max-width: 360px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: 16px;
  display: flex; flex-direction: column; gap: 12px;
  box-shadow: var(--elev-3);
}
.fz-cardio-other-prompt__h {
  margin: 0; font-size: 16px; color: var(--fg); font-weight: 700;
}
.fz-cardio-other-prompt__input {
  appearance: none; -webkit-appearance: none;
  width: 100%; box-sizing: border-box;
  padding: 10px 12px; font: inherit; font-size: 15px;
  background: var(--bg); color: var(--fg);
  border: 1px solid var(--line); border-radius: var(--radius-md);
}
.fz-cardio-other-prompt__input:focus {
  outline: 2px solid var(--accent); outline-offset: 1px;
}
.fz-cardio-other-prompt__hint {
  margin: 0; font-size: 12px; color: var(--danger); line-height: 1.4;
}
.fz-cardio-other-prompt__actions {
  display: flex; gap: 8px; justify-content: flex-end;
}
.fz-cardio-other-prompt__btn {
  appearance: none; -webkit-appearance: none;
  border: 0; border-radius: var(--radius-pill);
  padding: 10px 16px; font: inherit;
  font-size: 14px; font-weight: 700;
  cursor: pointer;
}
.fz-cardio-other-prompt__btn--primary {
  background: var(--accent); color: var(--on-accent);
}
.fz-cardio-other-prompt__btn--secondary {
  background: transparent; color: var(--fg);
  border: 1px solid var(--line);
}

/* Mijn data — Settings sectie (spec §13.4 + §13.5). Reuses the existing
   .set-* card / row idioms in renderSettings(); only the in-progress
   spinner + 30-day deletion banner need their own classes. */
.set-mijn-data__hint {
  margin: 0 0 10px; font-size: 12px; color: var(--muted); line-height: 1.45;
}
.set-mijn-data__busy {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--muted);
}
.set-mijn-data__deleted {
  margin-top: 10px; padding: 10px 12px;
  background: var(--surface); border: 1px solid var(--accent);
  border-radius: var(--radius-md); font-size: 13px; color: var(--fg);
  line-height: 1.45;
}

/* CARDIO-SETTINGS-AND-WHITELABEL-THEMING (v0.9.383) — Cardio settings card
   rows. Reuses the .set-* base patterns (set-group / set-card / set-row);
   only the segmented-control bar + the labelled fields need their own
   classes. Tokens come from the Dag + Nacht preset vars so both themes get
   correct contrast without per-rule overrides. Accent-on-accent rule g:
   every accent-background tile takes its text colour from --on-accent,
   never a literal #fff/white. */
.set-cardio-field {
  display: flex; flex-direction: column; gap: 6px;
  padding: 12px 14px;
}
.set-cardio-field__label {
  font-size: 12px; color: var(--muted); line-height: 1.3;
  letter-spacing: 0.01em;
}
.set-cardio-seg {
  display: flex; flex-wrap: wrap; gap: 6px;
}
.set-cardio-seg__tile {
  flex: 1 1 0; min-width: 0;
  padding: 9px 10px;
  background: var(--surface-muted);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  font: inherit; font-size: 13px; font-weight: 500;
  cursor: pointer;
  text-align: center;
  transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
}
.set-cardio-seg__tile:hover { border-color: var(--line-2); }
.set-cardio-seg__tile.is-on {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}
.set-cardio-seg__tile:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* CHECKIN-LOGS-TITLE-METRIC-SINGLE-WORD-LABELS-AND-GLOBAL-UNITS — Units
   segmented control on the main Settings page. Same shape + tokens as the
   cardio segmented control (.set-cardio-seg) so the chrome reads as one
   shared pattern; lives in the main Settings card layout so the inner card
   padding wraps it correctly without the per-field column the cardio page
   uses. Two-tile (Metric / Imperial) so we let flex distribute equally
   without min-width tweaking. */
.set-units-segmented {
  display: flex; flex-wrap: wrap; gap: 6px;
  /* v0.9.508 SETTINGS-PADDING-AND-PROFILE-KERNING-V2 — segmented control
     previously bled to the card edges. Mirror .set-themes inset so the
     [Metric][Imperial] tiles align with the section's standard 16px gutter. */
  padding: var(--space-2) var(--space-4) var(--space-4);
  margin-top: 0;
}
.set-units-segmented__tile {
  flex: 1 1 0; min-width: 0;
  padding: 10px 12px;
  background: var(--surface-muted);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  font: inherit; font-size: 13px; font-weight: 600;
  cursor: pointer;
  text-align: center;
  transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
}
.set-units-segmented__tile:hover { border-color: var(--line-2); }
.set-units-segmented__tile.is-on {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
  box-shadow: var(--glow-soft);
}
.set-units-segmented__tile:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.set-cardio-input,
.set-cardio-select {
  width: 100%;
  padding: 10px 12px;
  background: var(--surface-muted);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  font: inherit; font-size: 14px;
  -webkit-appearance: none; appearance: none;
}
.set-cardio-select {
  /* native chevron via padding + bg-svg so it tracks the theme tokens */
  padding-right: 36px;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 18px) 50%, calc(100% - 13px) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.set-cardio-input:focus-visible,
.set-cardio-select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

/* CARDIO-SETTINGS-AND-WHITELABEL-THEMING — Super-admin tenant theme panel
   (public/admin/cardio-tenant-theme.html). Standalone page so layout uses
   max-width + page padding here rather than the SPA shell. */
.adm-cardio-theme {
  max-width: 720px; margin: 0 auto;
  padding: max(16px, env(safe-area-inset-top)) 16px 48px;
  background: var(--bg); color: var(--ink); min-height: 100vh;
  min-height: 100dvh; /* dvh wins on modern engines, 100vh above stays fallback */
}
.adm-cardio-theme__head {
  display: flex; flex-direction: column; gap: 4px;
  padding: 8px 0 16px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 16px;
}
.adm-cardio-theme__back {
  align-self: flex-start;
  font-size: 13px; color: var(--accent);
  text-decoration: none;
}
.adm-cardio-theme__title {
  font-size: 22px; font-weight: 700; color: var(--ink);
  margin-top: 4px;
}
.adm-cardio-theme__sub {
  font-size: 12px; color: var(--muted);
}
.adm-cardio-theme__gate {
  padding: 24px; text-align: center; color: var(--muted);
}
.adm-cardio-theme__field {
  display: flex; flex-direction: column; gap: 6px;
  padding: 12px 14px;
}
.adm-cardio-theme__label {
  font-size: 13px; color: var(--ink); font-weight: 500;
}
.adm-cardio-theme__color-wrap {
  display: flex; align-items: center; gap: 10px;
}
.adm-cardio-theme__color {
  flex: 0 0 44px;
  width: 44px; height: 44px;
  padding: 0;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  cursor: pointer;
}
.adm-cardio-theme__hex {
  flex: 1 1 auto; min-width: 0;
  padding: 9px 11px;
  background: var(--surface-muted);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  font: inherit; font-size: 13px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  text-transform: uppercase;
}
.adm-cardio-theme__textinput {
  width: 100%;
  padding: 10px 12px;
  background: var(--surface-muted);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  font: inherit; font-size: 13px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.adm-cardio-theme__textinput:focus-visible,
.adm-cardio-theme__hex:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}
.adm-cardio-theme__preview {
  position: relative;
  margin: 12px 14px;
  height: 180px;
  border-radius: var(--radius-md);
  overflow: hidden;
  border: 1px solid var(--line);
  /* Defaults that the JS overrides at runtime to apply the live theme. */
  --cardio-polyline: var(--accent);
  --cardio-marker:   #1A1A2E;
  --cardio-planned:  #5B8DEF;
}
.adm-cardio-theme__preview-tile {
  position: absolute; inset: 0;
  background-size: cover; background-position: center;
}
.adm-cardio-theme__preview-svg {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
}
.adm-cardio-theme__preview-polyline {
  stroke: var(--cardio-polyline);
}
.adm-cardio-theme__preview-planned {
  stroke: var(--cardio-planned);
}
.adm-cardio-theme__preview-marker {
  fill: var(--cardio-marker);
}
.adm-cardio-theme__modalities {
  display: flex; flex-direction: column;
}
.adm-cardio-theme__mod-row {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 14px;
  font-size: 14px; color: var(--ink);
  border-bottom: 1px solid var(--line-2);
  cursor: pointer;
}
.adm-cardio-theme__mod-row:last-child { border-bottom: 0; }
.adm-cardio-theme__mod-row input[type=checkbox] {
  width: 18px; height: 18px; accent-color: var(--accent);
}
.adm-cardio-theme__radios {
  display: flex; flex-direction: column;
}
.adm-cardio-theme__radio {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 14px;
  font-size: 14px; color: var(--ink);
  border-bottom: 1px solid var(--line-2);
  cursor: pointer;
}
.adm-cardio-theme__radio:last-child { border-bottom: 0; }
.adm-cardio-theme__radio input[type=radio] {
  width: 18px; height: 18px; accent-color: var(--accent);
}
.adm-cardio-theme__save-bar {
  display: flex; justify-content: space-between; gap: 10px;
  margin: 16px 0 8px;
}
.adm-cardio-theme__reset,
.adm-cardio-theme__save {
  padding: 11px 16px;
  font: inherit; font-size: 14px; font-weight: 600;
  border-radius: var(--radius-md);
  cursor: pointer;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
}
.adm-cardio-theme__save {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}
.adm-cardio-theme__save[disabled],
.adm-cardio-theme__reset[disabled] {
  opacity: 0.6; cursor: not-allowed;
}
.adm-cardio-theme__status {
  margin-top: 8px;
  padding: 0 4px;
  font-size: 12px; color: var(--muted);
  min-height: 16px;
}
.adm-cardio-theme__status.is-error { color: var(--danger); }

/* EXERCISE-POOL-N-WAY (v0.9.428) — bottom-sheet picker rows for 3+-member
   pools. Rendered into the existing fz-mc-overlay + fz-mc-sheet container.
   Token-driven; spec tokens (--surface-2, --text-1) mapped to this repo's
   real tokens (--surface-card, --ink) so the css-hex-lint stays clean and
   the rows track Dag/Nacht presets automatically. */
.fz-pool-list {
  display: flex; flex-direction: column;
  gap: 8px; margin-bottom: 12px;
}
.fz-pool-row {
  width: 100%; padding: 14px;
  background: var(--surface-card); color: var(--ink);
  border: none; border-radius: var(--radius-md);
  font-size: 15px; font-weight: 600; font-family: 'DM Sans', sans-serif;
  text-align: left; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.fz-pool-row:active { opacity: 0.7; }
.fz-pool-add-btn {
  width: 100%; padding: 13px;
  background: transparent; color: var(--accent);
  border: 1.5px solid var(--accent); border-radius: var(--radius-md);
  font-size: 14px; font-weight: 700; font-family: 'DM Sans', sans-serif;
  cursor: pointer; margin-bottom: 8px;
  -webkit-tap-highlight-color: transparent;
}

/* ──────────────────────────────────────────────────────────
   Goals — DIARY-7-GOALS (v0.9.431). Behavioral lead-indicator
   targets (kcal, protein, training frequency, sleep, steps, mood)
   surfaced on Diary. Token-driven; no hex literals — css-hex-lint
   stays clean. Day/Week goal cards reuse .cal-card frame.
   ────────────────────────────────────────────────────────── */

/* /goals editor page */
.goals-page {
  display: flex; flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-4);
  max-width: 560px; margin: 0 auto;
}
.goals-header {
  display: flex; align-items: center; gap: var(--space-3);
}
.goals-back {
  width: 40px; height: 40px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--radius-pill);
  background: transparent; color: var(--ink);
  cursor: pointer;
}
.goals-back svg { width: 22px; height: 22px; }
.goals-back:active { background: var(--surface-card); }
.goals-header__titles { display: flex; flex-direction: column; gap: 2px; }
.goals-header__title {
  margin: 0; font-size: 22px; font-weight: 700;
  color: var(--ink);
}
.goals-header__sub {
  margin: 0; font-size: 13px; color: var(--section-label);
}
.goals-loading {
  padding: var(--space-4); text-align: center;
  color: var(--muted); font-size: 14px;
}
.goals-list {
  display: flex; flex-direction: column;
  gap: var(--space-3);
}
.goals-item {
  background: var(--surface-card);
  border-radius: var(--radius-lg);
  padding: var(--space-3) var(--space-4);
  display: flex; flex-direction: column;
  gap: 10px;
}
.goals-item__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3);
}
.goals-item__name {
  font-size: 15px; font-weight: 600; color: var(--ink);
}
.goals-item__unit {
  font-size: 13px; color: var(--section-label);
}
.goals-item__input-wrap {
  display: flex; align-items: center; gap: 8px;
}
.goals-item__input {
  flex: 1 1 auto;
  background: var(--surface-bg);
  color: var(--ink);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-md);
  padding: 10px 12px;
  font-size: 16px;
  font-variant-numeric: tabular-nums;
  min-width: 0;
}
.goals-item__input:focus {
  outline: 2px solid var(--accent);
  border-color: var(--accent);
}
.goals-item__add {
  align-self: flex-start;
  background: var(--accent);
  color: var(--on-accent);
  border: 0; border-radius: var(--radius-md);
  padding: 8px 14px;
  font-size: 14px; font-weight: 600;
  cursor: pointer;
}
.goals-item__add:active { opacity: 0.85; }
.goals-item__remove {
  width: 32px; height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; color: var(--section-label);
  border: 0; border-radius: var(--radius-pill);
  cursor: pointer;
}
.goals-item__remove svg { width: 18px; height: 18px; }
.goals-item__remove:active { color: var(--ink); }

/* Calendar — Day Goals card */
.cal-goals-card { display: flex; flex-direction: column; }
.cal-goals-won {
  font-variant-numeric: tabular-nums;
  color: var(--section-label);
}
.cal-goals-won.is-won { color: var(--accent); font-weight: 700; }
.cal-goal-row {
  display: grid;
  grid-template-columns: 22px 1fr auto auto;
  align-items: baseline;
  gap: 8px;
  padding: 6px 0;
  border-top: 1px solid var(--line-2);
  font-size: 13px;
}
.cal-goal-row:first-of-type { border-top: 0; padding-top: 4px; }
.cal-goal-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px;
}
.cal-goal-icon svg { width: 16px; height: 16px; }
.cal-goal-icon--met    { color: var(--accent); }
.cal-goal-icon--missed { color: var(--section-label); }
.cal-goal-icon--nodata {
  color: var(--section-label);
  font-size: 14px; font-weight: 600;
}
.cal-goal-row__label  { color: var(--ink); }
.cal-goal-row__actual {
  color: var(--ink); font-weight: 500;
  font-variant-numeric: tabular-nums;
}
.cal-goal-row__target {
  color: var(--section-label);
  font-variant-numeric: tabular-nums;
}
.cal-goal-empty {
  color: var(--muted); font-size: 13px;
  padding: 6px 0;
}

/* DAGBOEK-OPRUIMING-NA-DE-WEEKGRAFIEK — the 'This week' collapsible and its
   week-goal bars are gone; the week chart + average cards answer the same
   question in the Diary opening. .cal-goal-empty above stays: the DAY goals
   card still uses it. */

/* DIARY-WEEK-SPARKLINE-STRIP (v0.9.544) — small-multiples trend strip under
   the 'This week' header. Three side-by-side mini sparklines (Protein avg /
   Weight / Mood avg). Each metric owns its own Y-axis (Tufte: same-units-
   only is the overlay rule — these three units differ, so small-multiples,
   never a shared axis). Token-driven, dag + nacht parity. */
.spark-strip {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px dashed var(--line-2);
  touch-action: pan-y;
}
.spark-mini {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.spark-mini__hd {
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.spark-mini__svg {
  display: block;
  width: 100%;
  height: 36px;
  cursor: ew-resize;
}
.spark-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.spark-line--dot {
  fill: var(--accent);
  stroke: none;
}
.spark-cursor {
  stroke: var(--ink);
  stroke-width: 1;
  opacity: 0;
  pointer-events: none;
  vector-effect: non-scaling-stroke;
}
.spark-cursor__dot {
  fill: var(--ink);
  stroke: var(--surface);
  stroke-width: 0.6;
  opacity: 0;
  pointer-events: none;
  vector-effect: non-scaling-stroke;
}
.spark-strip--scrub .spark-cursor { opacity: 0.55; }
.spark-strip--scrub .spark-cursor__dot { opacity: 1; }
.spark-tip {
  position: absolute;
  left: var(--spark-tip-x, 50%);
  bottom: calc(100% + 2px);
  transform: translateX(-50%);
  font-size: 11px;
  font-weight: 600;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 2px 6px;
  white-space: nowrap;
  font-feature-settings: 'tnum' 1;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-standard);
}
.spark-mini--scrub .spark-tip { opacity: 1; }

/* DIARY-METRIC-TREND-SPARKLINES — long-term progress section on the Diary tab.
   One scrub-able sparkline card per tracked metric. Reuses the .spark-line /
   .spark-cursor / .spark-tip visual vocabulary; token-driven, Dag + Nacht
   parity. */
/* DAGBOEK-DE-TRENDS-KRIJGEN-HUN-GLAZEN-PANEEL — de sectie was een KALE strook:
   een gestippelde bovenrand en verder niets, dus kop, periodeknoppen en de
   lege-staat-zin lagen rechtstreeks op body.tab-calendar::before (de dagfoto of
   de nachtfoto). Gemeten 2026-09-23 zakte .diary-trends__note daar naar
   1,14-2,58:1 op zijn slechtste vakje, in alle zes de afwerkingen. De sectie
   krijgt daarom EXACT dezelfde plaat als .fz-weekavg--empty: een eigen kaartvlak
   als basis (voor elke niet-Dagboek-omgeving) en onder body.tab-calendar de
   gedeelde 30px-glasplaat — geen tweede glasrecept. De gestippelde scheidslijn
   verdwijnt: de plaatrand IS de scheiding, precies zoals bij de buren. */
.diary-trends {
  margin-top: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.diary-trends__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.diary-trends__title {
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.diary-trends__toggle {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  background: var(--surface-2, var(--surface));
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}
.diary-trends__period {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  padding: 3px 9px;
  border-radius: var(--radius-md);
  cursor: pointer;
}
.diary-trends__period--on {
  background: var(--accent);
  color: var(--on-accent);
}
.diary-trends__note {
  font-size: 13px;
  color: var(--muted);
  margin: 0;
  padding: 4px 0;
}
/* DAGBOEK-DE-TRENDS-KRIJGEN-HUN-GLAZEN-PANEEL — de inkt volgt het glas, zoals
   het .fz-weekavg__*-blok verderop. --ink en --muted zijn mid-luminant en halen
   4,5:1 op GEEN enkele tint boven een geblurde foto; de twee glas-inkttokens
   flippen mee met Dag/Nacht én met alle zes de afwerkingen.
   De kop en de lege-staat-zin (lopende tekst, en het enige wat de contrastpoort
   op deze tab graadt) gaan op --glass-ink, niet op --glass-ink-2: gemeten op het
   SLECHTSTE vakje haalt ink-2 4,03:1 (donker, licht fotovakje) en 4,41:1 (licht,
   donker fotovakje) — onder de norm. De periodeknoppen zijn bediening, geen
   lopende tekst, en houden --glass-ink-2.
   De knoppenstrip zelf draagt --surface-2: op glas is dat een dicht gat, dus hij
   volgt .analytics-goal-card__dir hierboven — transparant met een glashaarlijn. */
body.tab-calendar .diary-trends__title,
body.tab-calendar .diary-trends__note { color: var(--glass-ink); }
body.tab-calendar .diary-trends__period { color: var(--glass-ink-2); }
body.tab-calendar .diary-trends__period--on { color: var(--on-accent); }
body.tab-calendar .diary-trends__toggle {
  background: transparent;
  border: 1px solid var(--glass-edge);
}
.trend-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 8px 10px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}
.trend-card__hd {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.trend-card__label {
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.trend-card__last {
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
  font-feature-settings: 'tnum' 1;
  white-space: nowrap;
}
.trend-card__body {
  display: flex;
  gap: 6px;
}
.trend-card__ygutter {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-end;
  min-width: 26px;
  padding: 1px 0;
  font-size: 9px;
  font-weight: 600;
  color: var(--muted);
  font-feature-settings: 'tnum' 1;
  white-space: nowrap;
}
.trend-card__plotcol {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.trend-card__plot {
  position: relative;
}
.trend-card__svg {
  height: 56px;
  width: 100%;
  display: block;
}
.trend-card__endpt {
  fill: var(--accent);
  stroke: none;
}
.trend-card__pt {
  position: absolute;
  font-size: 10px;
  font-weight: 700;
  color: var(--ink);
  font-feature-settings: 'tnum' 1;
  white-space: nowrap;
  pointer-events: none;
  transform: translateY(-135%);
}
.trend-card__pt--start { left: 0; }
.trend-card__pt--end { right: 0; }
.trend-card__pt.trend-card__pt--below { transform: translateY(35%); }
.trend-card__foot {
  display: flex;
  justify-content: space-between;
  font-size: 10px;
  font-weight: 600;
  color: var(--muted);
  font-feature-settings: 'tnum' 1;
}
.trend-card--scrub .spark-cursor { opacity: 0.55; }
.trend-card--scrub .spark-cursor__dot { opacity: 1; }
.trend-card--scrub .trend-card__tip { opacity: 1; }

/* DAGRITME-JE-CIJFERS-IN-TRENDS-EN-BIJ-JE-COACH — het gevoelscijfer als eigen
   lijn per onderwerp, in dezelfde kaart als de metriek-lijnen ernaast.

   EEN ONDERWERP IS EEN KLEUR. Ochtend en avond zijn hetzelfde onderwerp dat je
   twee keer op een dag voelt, dus ze erven allebei var(--accent) van
   .spark-line en worden onderscheiden door de streepjes van de avond. Een
   tweede kleur zou lezen als een tweede ding dat gemeten wordt.

   De streepjesmaat volgt .diary-tempt-line, de lijn die dit idioom in deze app
   al gebruikt — een tweede streepjesmaat is een tweede taal. */
.spark-line--evening { stroke-dasharray: 5 3; }
.trend-card__legend {
  display: flex;
  gap: 12px;
  align-items: center;
  font-size: 10px;
  font-weight: 600;
  color: var(--muted);
}
.trend-card__legend-item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.trend-card__legend-line {
  display: inline-block;
  width: 14px;
  height: 0;
  border-top: 1.6px solid var(--accent);
}
.trend-card__legend-line--evening { border-top-style: dashed; }
.trend-card__compare {
  margin: 0;
  font-size: 11px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--muted);
}

/* DIARY-TODAY-MINIMALIST-AUDIT (v0.9.543) — Today card is 6 rows max. Each
   row is label + progress-bar-or-blank + value. No icon column, no inner
   eyebrow sub-blocks (Nutrients/Activity/Gym): the row IS the unit. Reuses
   .cal-card chrome for outer surface + radius. */
.cal-today-merged { display: flex; flex-direction: column; }
.cal-today-merged__hd {
  align-items: baseline;
  gap: 8px;
}
.cal-today-merged__sub {
  font-size: 13px;
  color: var(--muted);
  font-weight: 500;
  margin-left: -2px;
}
.cal-tm-goals {
  display: flex;
  flex-direction: column;
  margin-top: 4px;
}
.cal-tm-row {
  display: grid;
  grid-template-columns: 90px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  font-size: 13px;
}
.cal-tm-row + .cal-tm-row {
  border-top: 1px solid var(--line-2);
}
.cal-tm-row__label {
  color: var(--ink-2);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cal-tm-row__bar {
  height: 6px;
  background: var(--surface-muted);
  border-radius: var(--radius-pill);
  overflow: hidden;
  position: relative;
}
.cal-tm-row__fill {
  height: 100%;
  background: var(--section-label);
  border-radius: var(--radius-pill);
  transition: width var(--dur-base) var(--ease-standard);
}
.cal-tm-row__fill--met { background: var(--accent); }
.cal-tm-row__val {
  color: var(--ink);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  white-space: nowrap;
}
.cal-tm-row__val--empty {
  color: var(--muted);
  font-weight: 500;
}
.cal-tm-row__val em {
  color: var(--muted);
  font-style: normal;
  font-weight: 500;
  margin-left: 4px;
}
.cal-tm-row--link {
  cursor: pointer;
}
.cal-tm-row--link .cal-tm-row__val { color: var(--accent); }
.cal-tm-row--link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm); }
.cal-tm-row__chev { color: var(--accent); margin-left: 4px; }

/* DAGBOEK-DE-WEEK-IN-EEN-BLIK — de weekgrafiek bovenaan het Dagboek. Alles in
   tokens: de grafiek moet in het nachtthema dezelfde tekening zijn, niet een
   tweede tekening met eigen kleuren. */
.fz-weekchart {
  margin: 4px 0 0;
  padding: 12px 12px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
/* DAGBOEK-DE-WEEKGRAFIEK-SCHUIFT-MEE-MET-JE-VINGER — de zijwaartse rij: één
   pagina per week, vorige weken links, deze week rechts. Letterlijk het scroll-
   oppervlak van de Voeding-stippenrij (.nut-consist__pages): native scroll +
   scroll-snap, zodat de rij met de eigen vaart en rubber-banding van het platform
   meeschuift en er geen touch-handler bestaat die met pull-to-refresh vecht. */
.fz-wkchart__pages {
  display: flex; overflow-x: auto; overflow-y: hidden;
  /* auto: de enige geprogrammeerde sprong is de landing op de bekeken week bij de
     eerste tekening, en die mag niet zichtbaar bewegen. ‹ › schuiven zacht via
     scrollBy(behavior:'smooth') — één plek waar de beweging vandaan komt. */
  scroll-snap-type: x mandatory; scroll-behavior: auto;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}
.fz-wkchart__pages::-webkit-scrollbar { display: none; }
.fz-wkchart__page { flex: 0 0 100%; scroll-snap-align: start; min-width: 0; }
/* De schildwacht die de volgende oudere week bijlaadt: geen zichtbaar gewicht,
   maar hij moet een doos hebben, anders ziet de IntersectionObserver hem nooit. */
.fz-wkchart__more { flex: 0 0 1px; align-self: stretch; }
.fz-wkchart__loading {
  margin: 2px 0 0; font-size: 11px; color: var(--muted); text-align: center;
}
.fz-weekchart__plot { position: relative; }
.fz-weekchart__svg { display: block; width: 100%; height: auto; }
/* De tikvlakken liggen als onzichtbare kolommen over de tekening heen, zodat de
   hele kolom de deur naar die dag is en niet alleen het staafje zelf. */
.fz-weekchart__cols { position: absolute; inset: 0; display: flex; }
.fz-weekchart__col {
  flex: 1 1 0;
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.fz-weekchart__col[disabled] { cursor: default; }
.fz-weekchart__col:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 8px; }
.fz-weekchart__bar--kcal { fill: var(--nutrition); }
.fz-weekchart__bar--prot { fill: var(--accent); }
/* DAGBOEK-ALLE-MACROS-IN-DE-WEEKGRAFIEK — koolhydraten en vet lenen de macro-
   tokens die de Voeding-tab al voor precies deze twee gebruikt. Bewust GEEN
   nieuw paar kleuren: dan zou "welke kleur is vet" twee antwoorden hebben,
   afhankelijk van welke tab je openhebt. De triade is per afwerking opnieuw
   gezet (zes keer, plus de oklch-herhaling) en houdt tegen het kaartvlak
   var(--surface) minimaal 3,71:1 — ruim boven de 3:1 die een gevuld vlak
   nodig heeft. Geen rood: --danger blijft het alarm, niet een macro. */
.fz-weekchart__bar--carbs { fill: var(--mac-c); }
.fz-weekchart__bar--fat   { fill: var(--mac-f); }
.fz-weekchart__bar--today { opacity: .5; }
.fz-weekchart__band { fill: var(--ink); opacity: .08; }
.fz-weekchart__goalline { stroke: var(--muted); stroke-width: 1; stroke-dasharray: 3 3; }
.fz-weekchart__goaltxt { fill: var(--muted); font-size: 9px; }
.fz-weekchart__axis { stroke: var(--line); stroke-width: 1; }
.fz-weekchart__empty { stroke: var(--line); stroke-width: 4; stroke-linecap: round; }
.fz-weekchart__todaybox { fill: none; stroke: var(--line); stroke-width: 1; stroke-dasharray: 3 3; }
.fz-weekchart__lbl { fill: var(--muted); font-size: 11px; }
.fz-weekchart__lbl--future { opacity: .4; }
.fz-weekchart__lbl--today { fill: var(--ink); font-weight: 600; }
.fz-weekchart__train { fill: var(--training); }
/* VOEDING-IN-DE-KALENDER-ZIE-JE-WAT-JE-KOOKTE — het kook-merkteken onder de dag.
   Het trainingspuntje is gevuld, de pan is een LIJNTEKENING: twee merktekens van
   dezelfde grootte in dezelfde kleur zouden alleen aan hun vorm te onderscheiden
   zijn, en op 6 pixels is dat geen onderscheid. Vorm én kleur verschillen nu. */
.fz-weekchart__cook { fill: none; stroke: var(--nutrition); stroke-width: 1.4; stroke-linecap: round; }
.fz-weekchart__legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  margin: 2px 2px 0;
  font-size: 11px;
  color: var(--muted);
}
.fz-weekchart__leg { display: inline-flex; align-items: center; gap: 5px; }
.fz-weekchart__swatch { width: 9px; height: 9px; border-radius: 2px; flex: none; }
.fz-weekchart__swatch--kcal  { background: var(--nutrition); }
.fz-weekchart__swatch--prot  { background: var(--accent); }
.fz-weekchart__swatch--carbs { background: var(--mac-c); }
.fz-weekchart__swatch--fat   { background: var(--mac-f); }
.fz-weekchart__swatch--goal  { height: 0; border-top: 2px dashed var(--muted); border-radius: 0; }
.fz-weekchart__swatch--train { background: var(--training); border-radius: 50%; }
/* Minder beweging is een medische grens, geen smaakinstelling: het snappen blijft
   (dat is de vorm van de rij), het zachte schuiven verdwijnt. */
@media (prefers-reduced-motion: reduce) {
  .fz-wkchart__pages { scroll-behavior: auto; scroll-snap-type: x mandatory; }
}
/* Er is niets voorbij deze week; de knop zegt dat ook. */
.cal-nav[disabled] { opacity: .35; cursor: default; }

/* De twee cijferkaarten: het gemiddelde dat de staven laten zien, uitgesproken. */
/* flex-wrap zit er voor de macroregel hieronder: die krijgt een volle regel
   ONDER het paar kaarten. Als derde kolom ernaast zou hij de twee cijfers
   versmallen tot ze afbreken, en dat zijn juist de getallen waarvoor je kijkt. */
.fz-weekavg { display: flex; flex-wrap: wrap; gap: 10px; margin: 12px 0 0; }
.fz-weekavg__card {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.fz-weekavg__lbl { font-size: 12px; color: var(--muted); }
.fz-weekavg__val { font-size: 22px; font-weight: 700; color: var(--ink); line-height: 1.15; }
.fz-weekavg__sub { font-size: 12px; color: var(--muted); }
/* DAGBOEK-HET-DOELCIJFER-IS-EEN-DEUR — de deur draagt geen knopvorm: geen
   achtergrond, geen kader, geen accentkleur, alleen de chevron als aanwijzing.
   De kaart blijft een cijferkaart. */
.fz-weekavg__goal {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  align-self: flex-start;
  margin: 0;
  padding: 2px 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.fz-weekavg__goal:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 6px; }
.fz-weekavg__goal:active { opacity: .7; }
.fz-weekavg__chev { width: 13px; height: 13px; flex: 0 0 auto; color: var(--muted); }
.fz-weekavg__none { margin: 12px 2px 0; font-size: 13px; line-height: 1.45; color: var(--muted); }
/* DAGBOEK-ALLE-MACROS-IN-DE-WEEKGRAFIEK — het weekgemiddelde van koolhydraten en
   vet, als ÉÉN regel in plaats van twee extra kaarten: vier cijferkaarten is een
   tabel en de belofte van dit blok is één blik. Zelfde secundaire inkt als de
   legenda van de grafiek, die op ditzelfde glas al gemeten is. */
.fz-weekavg__macros {
  flex: 1 0 100%;
  margin: 0 2px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--muted);
}
/* DAGBOEK-JE-STELT-JE-DAGDOEL-ZELF-IN — de lege staat is een kolom (regel, knop,
   eventueel de coachregel), geen rij van twee kaarten. Wel op dezelfde PLAAT:
   het Dagboek staat op een foto en DIARY-RING-ON-GLASS heeft al vastgelegd dat
   niets hier bloot op die foto mag liggen (zie de glasregel verderop). */
.fz-weekavg--empty {
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.fz-weekavg--empty .fz-weekavg__none { margin: 0; }
.fz-weekavg__setgoal {
  min-height: 44px;
  padding: 10px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
  line-height: 1.25;
  cursor: pointer;
}
.fz-weekavg__setgoal:active { filter: brightness(0.95); }
.fz-weekavg__setgoal:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.fz-weekavg__setgoal[disabled] { opacity: .6; cursor: default; }
.fz-weekavg__coachnote { margin: 0 2px; font-size: 12px; line-height: 1.45; color: var(--muted); }
/* Op de glasplaat van het Dagboek zijn --muted en --ink niet de leesbare paren;
   de tab heeft daar eigen tokens voor, die met Dag/Nacht meebewegen. */
body.tab-calendar .fz-weekchart__legend,
body.tab-calendar .fz-wkchart__loading,
body.tab-calendar .fz-weekavg__lbl,
body.tab-calendar .fz-weekavg__sub,
body.tab-calendar .fz-weekavg__none,
body.tab-calendar .fz-weekavg__chev,
body.tab-calendar .fz-weekavg__coachnote { color: var(--glass-ink-2); }
body.tab-calendar .fz-weekavg__setgoal {
  border-color: var(--glass-hair);
  background: var(--glass-fill, transparent);
  color: var(--glass-ink);
}
body.tab-calendar .fz-weekavg__val { color: var(--glass-ink); }
/* SVG-tekst neemt `fill`, niet `color`. */
body.tab-calendar .fz-weekchart__lbl,
body.tab-calendar .fz-weekchart__goaltxt { fill: var(--glass-ink-2); }
body.tab-calendar .fz-weekchart__lbl--today { fill: var(--glass-ink); }
body.tab-calendar .fz-weekchart__axis,
body.tab-calendar .fz-weekchart__empty,
body.tab-calendar .fz-weekchart__todaybox { stroke: var(--glass-hair); }
body.tab-calendar .fz-weekchart__goalline { stroke: var(--glass-ink-2); }

/* ── DAGBOEK-JE-DAGDOELEN-ZIE-JE-ZONDER-TE-LEZEN ───────────────────────────
   Zeven KOLOMMEN onder de cijferkaarten, elk recht onder zijn eigen kolom in de
   grafiek. Die uitlijning komt NIET uit een tweede kolombreedte: het paneel
   heeft dezelfde 12px binnenrand + 1px lijn als .fz-weekchart, en de zeven
   kolommen delen de resterende breedte in zevenen — precies zoals de tikvlakken
   .fz-weekchart__col dat boven de tekening doen. Zou hier een vaste px-breedte
   staan, dan zou hij bij elke schermbreedte een stukje mislopen.

   WAAROM ICONEN EN GEEN BOLLETJES MEER. Tot v0.9.2876 stond hier per dag één
   abstracte vorm: vol · halve maan · streepje. Woordloos getest begreep 0 van de
   2 kijkers wat de rij was; niemand kon zeggen HOEVEEL doelen een dag had of
   WELK doel er miste, en het halve maantje las als "slaap". In de UAT van
   2026-09-25 (29 blinde kijkers, vijf rondes) haalde de iconenkolom 12/12 op het
   concept en 12/12 op het aantal doelen. Dus: per dag de iconen van je eigen
   doelen, en de VORM van het icoonvakje draagt de toestand.

   DRIE VORMEN, NIET DRIE KLEUREN — kleur alleen mag nooit de enige drager van
   betekenis zijn, en bij deze rij komt daar iets bij: de kleuren zijn hier de
   legenda van de grafiek erboven, dus ze zeggen WELK doel, niet OF het gehaald
   is. Dat laatste zegt de vorm:
     gehaald            = gevulde schijf, icoon donker erin
     vandaag nog te doen = gestippeld rondje, icoon licht erin
     gemist (voorbij)    = alleen het icoon, klein en stil grijs
     toekomst            = hetzelfde icoon, heel licht: het plan van de week
   Alles gehaald tilt de hele kolom op: accentkader + een goud vinkje in de hoek.

   VANDAAG IS EEN DOORGETROKKEN KADER, NOOIT GESTIPPELD. Gemeten in ronde 5:
   een gestippeld vandaag-kader las als "hier staat nog alles open", omdat
   stippels in dit onderdeel al precies één ding betekenen — nog te doen. */
.fz-dayq {
  margin: 10px 0 0;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.fz-dayq__row { display: flex; }
.fz-dayq__d {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 6px 1px;
  /* Doorzichtige rand op ELKE kolom, zodat het kader van vandaag hieronder niets
     opschuift — anders staat de rij een pixel scheef zodra vandaag meedoet. */
  border: 1.5px solid transparent;
  border-radius: 12px;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.fz-dayq__d[disabled] { cursor: default; }
/* Vandaag: een DOORGETROKKEN kader. Zie de kop hierboven — gestippeld betekent
   in dit onderdeel al "nog te doen" en een gestippeld dagkader las daardoor als
   "hier staat nog alles open". */
.fz-dayq__d--today { border-color: var(--muted); background: var(--surface-muted); }
/* Alles gehaald. Staat NA --today, zodat een volle dag van vandaag het
   accentkader krijgt: dat is het sterkere antwoord van de twee. */
.fz-dayq__d--all {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.fz-dayq__d:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Het gouden vinkje in de hoek van een volle dag — de stempel van de
   stempelkaart. Hangt buiten de kolom, dus zonder eigen plek in de flow. */
.fz-dayq__badge {
  position: absolute;
  top: -7px; right: -5px;
  width: 16px; height: 16px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--goal-all);
  color: var(--on-goal-all);
}
.fz-dayq__badge svg { width: 10px; height: 10px; }
.fz-dayq__i {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px; height: 24px;
  border: 1.5px solid transparent;
  border-radius: 50%;
}
.fz-dayq__svg { width: 15px; height: 15px; }
/* GEHAALD — gevulde schijf in de kleur van het doel. Het icoon erin is
   --surface: donker op een donkere afwerking, wit op een lichte, dus het
   kantelt vanzelf mee met de doelkleuren die hetzelfde doen. */
.fz-dayq__i--done { color: var(--surface); }
.fz-dayq__i--done.fz-dayq__i--kcal     { background: var(--nutrition); }
.fz-dayq__i--done.fz-dayq__i--protein  { background: var(--accent); }
.fz-dayq__i--done.fz-dayq__i--training { background: var(--training); }
.fz-dayq__i--done.fz-dayq__i--sleep_hr { background: var(--mood); }
.fz-dayq__i--done.fz-dayq__i--steps    { background: var(--goal-steps); }
.fz-dayq__i--done.fz-dayq__i--water    { background: var(--hydration); }
/* VANDAAG NOG TE DOEN — het lege rondje van de stempelkaart. */
.fz-dayq__i--todo { border-color: var(--ink); border-style: dashed; color: var(--ink); }
/* GEMIST op een dag die voorbij is — alleen het icoon, kleiner en stil. Geen
   rood en geen kruis: een gemiste dag is geen verwijt, en dat was al het
   besluit bij de weekgrafiek. */
.fz-dayq__i--miss { color: var(--muted); }
.fz-dayq__i--miss .fz-dayq__svg { width: 13px; height: 13px; }
/* TOEKOMST — het plan van de week, zo licht dat het niet meedingt naar de
   aandacht. Niet aan te raken, dus ook geen doel van de contrastpoort. */
.fz-dayq__i--future { color: var(--line); }
.fz-dayq__i--future .fz-dayq__svg { width: 13px; height: 13px; }
.fz-dayq-line {
  margin: 8px 2px 0;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.45;
  color: var(--ink);
}
/* Boven "Check-in today" zit de regel in de voortgangskaart en hoort de ruimte
   dus ONDER hem, niet erboven. */
.fz-dayq-line--checkin { margin: 0 0 8px; }
/* Op het Dagboek staat de kaart op glas, dus leest alles hier tegen het glas en
   niet tegen --surface. Het icoon IN een gevulde schijf blijft --surface: dat
   leest tegen de DOELKLEUR eronder en niet tegen het glas. */
body.tab-calendar .fz-dayq-line { color: var(--glass-ink); }
body.tab-calendar .fz-dayq__d--today { border-color: var(--glass-ink-2); }
body.tab-calendar .fz-dayq__row .fz-dayq__i--todo { border-color: var(--glass-ink); color: var(--glass-ink); }
body.tab-calendar .fz-dayq__row .fz-dayq__i--miss { color: var(--glass-ink-2); }
body.tab-calendar .fz-dayq__row .fz-dayq__i--future { color: var(--glass-hair); }

/* DAGBOEK-MIJN-DAGVRAAG-INSTELLEN — het potlood staat op een eigen kopregel
   BOVEN de zeven vakjes. Niet ernaast: .fz-dayq__row moet exact even breed
   blijven als de zeven kolommen van de grafiek erboven, en een knop op diezelfde
   regel knijpt de rij smaller — dan staat vakje 7 onder kolom 6 en lees je het
   antwoord van de buurdag. Geen knopvorm en geen accentkleur: de rij is een
   uitslag, en het potlood is de weg terug naar de vraag, niet de hoofdhandeling
   van het scherm.

   DAGBOEK-JE-DAGDOELEN-ZIE-JE-ZONDER-TE-LEZEN — de kop "Mijn dagvraag" is hier
   weg. Hij beloofde een vraag die nergens gesteld werd, en de rij eronder is nu
   zelf te lezen. Het potlood blijft als enige (tekstloze) deur en schuift naar
   rechts; de regel blijft op zichzelf staan, want een knop NAAST de rij knijpt
   hem smaller en dan staat kolom 7 onder kolom 6. */
.fz-dayq__hd {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  /* Negatieve marge rechts: de knop mag in de kaartrand hangen zodat hij optisch
     op de rechterrand uitlijnt, zonder de rij eronder aan te raken. */
  margin: -2px -4px 2px 0;
}
.fz-dayq__edit {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px; height: 26px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--muted);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.fz-dayq__edit svg { width: 15px; height: 15px; }
.fz-dayq__edit:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
body.tab-calendar .fz-dayq__edit { color: var(--glass-ink); }

/* DAGBOEK-JE-DAGDOELEN-ZIE-JE-ZONDER-TE-LEZEN — DE AANRAKING LEERT DE LEGENDA.
   Tik op een dag en je krijgt per doel dezelfde vorm terug, maar nu mét zijn
   naam en zijn getal. Zo hoeft de rij zelf geen legenda te dragen (die zou hem
   weer tekst maken) en leert wie één keer tikt de hele rij lezen — Nintendo
   leert zijn besturing ook door je hem te laten gebruiken, niet door hem uit te
   leggen. Onderaan staat dezelfde deur als de kolom in de grafiek: de dag in
   Voeding. */
.fz-dayq-pop__list { margin: 0; padding: 0; list-style: none; }
.fz-dayq-pop__row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  border-top: 1px solid var(--line);
}
.fz-dayq-pop__row:first-child { border-top: 0; }
.fz-dayq-pop__name { flex: 1 1 auto; min-width: 0; font-size: 15px; font-weight: 600; color: var(--ink); }
.fz-dayq-pop__val { flex: 0 0 auto; font-size: 13px; color: var(--muted); }
.fz-dayq-pop__door { width: 100%; margin-top: 16px; }

/* Het instelblad zelf. Eén schakelaar per rij en, als hij aanstaat, één
   getalveld — de rij is de vraag en het veld het antwoord, dus ze staan onder
   elkaar en niet naast elkaar te vechten om dezelfde breedte. */
.fz-dayq-set__intro { margin: 0 0 12px; font-size: 13px; color: var(--muted); }
.fz-dayq-set__row {
  padding: 10px 0;
  border-top: 1px solid var(--line);
}
.fz-dayq-set__row:first-of-type { border-top: 0; }
.fz-dayq-set__head { display: flex; align-items: center; gap: 12px; }
.fz-dayq-set__lbl { flex: 1 1 auto; min-width: 0; font-size: 15px; font-weight: 600; color: var(--ink); }
.fz-dayq-set__body { margin: 10px 0 0; }
.fz-dayq-set__dir { margin-bottom: 8px; }
.fz-dayq-set__num { display: flex; align-items: center; gap: 8px; margin: 0 0 8px; }
.fz-dayq-set__num:last-child { margin-bottom: 0; }
/* `display: flex` hierboven verslaat de UA-regel achter het hidden-attribuut,
   dus een verborgen veld bleef STAAN: het tweede kcal-veld (alleen voor
   'tussen') stond ook bij 'minstens' onder het eerste, leeg, zonder uitleg.
   Klasse + attribuut weegt zwaarder dan de klasse alleen — geen !important. */
.fz-dayq-set__num[hidden],
.fz-dayq-set__body[hidden] { display: none; }
.fz-dayq-set__num .nut-field__in { flex: 1 1 auto; min-width: 0; }
.fz-dayq-set__unit { flex: 0 0 auto; font-size: 13px; color: var(--muted); }
/* Een doel van je coach is te LEZEN, niet te bewerken: grijs plus één regel die
   zegt waar het vandaan komt. Dezelfde regel als de dagdoel-lade van Voeding. */
.fz-dayq-set__row--locked .fz-dayq-set__lbl,
.fz-dayq-set__row--locked .nut-field__in { color: var(--muted); }
.fz-dayq-set__row--locked .nut-field__in { background: var(--surface-2, var(--surface)); }
.fz-dayq-set__note { margin: 0; font-size: 12px; color: var(--muted); }
.fz-dayq-set__full { margin: 12px 0 0; font-size: 13px; font-weight: 600; color: var(--ink); }
.fz-dayq-set__done { width: 100%; margin-top: 16px; }

.fz-diary-checkin-button:active { filter: brightness(0.95); }
/* DIARY-CHECKIN-BAND-BEHIND-OPEN-CHECKIN-BUTTON (v0.9.494) — the Diary tab's
   Check-in CTA sits between the week-pills and the ANALYTICS band; give it
   a touch of breathing room so it reads as a distinct hop in the scan
   rhythm rather than crowding either neighbor. */
.fz-diary-checkin-button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  min-height: 56px;
  margin-top: 16px;
  margin-bottom: 16px;
  padding: 16px 18px;
  border: 1px solid var(--ink);
  border-radius: 20px;
  background: var(--ink);
  color: var(--bg);
  font-size: 16px;
  font-weight: 600;
  line-height: 1.25;
  text-align: left;
  cursor: pointer;
  box-shadow: var(--elev-2);
}
.fz-diary-checkin-button:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.fz-diary-checkin-button__chev { flex: none; width: 20px; height: 20px; opacity: .75; }
.fz-cal-subscribe { margin: 20px 0 8px; }
.fz-cal-subscribe__btn {
  display: block;
  width: 100%;
  min-height: 48px;
  padding: 13px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.fz-cal-subscribe__btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.fz-cal-subscribe__btn[disabled] { opacity: .6; cursor: default; }
.fz-cal-subscribe__note {
  margin: 8px 2px 0;
  font-size: 12px;
  line-height: 1.45;
  color: var(--muted);
}
.fz-day-cycle-cal-sheet {
  position: fixed;
  z-index: 130;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 220px;
  max-width: 320px;
  padding: 12px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-3);
  font-family: inherit;
}
.fz-day-cycle-cal-sheet__opt {
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 10px 14px;
  min-height: 44px;
  text-align: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
}
.fz-day-cycle-cal-sheet__opt:active {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}

/* Full-tool page (mounted at /day-cycle). DAY-CYCLE-FULL-TOOL-AND-WIDGET-
   VISUAL-POLISH (v0.9.484) — stronger phase segmentation (per-phase cards
   with a thin colored left bar), calmer hierarchy, balanced padding,
   symmetric columns, decluttered time-range cell. All functional
   invariants (cascade math, slippage, padlock toggle, H+M auto-roll,
   cross-midnight, edit-mode sheet) untouched. */
.fz-day-cycle-page {
  padding: var(--space-2) 0 var(--space-6);
  color: var(--ink);
}
/* DAGRITME-ONDERIN-STAAT-KLAAR — deze kop was één onbreekbare rij, en daarmee
   één rij te lang. Gemeten op 375 px in de doe-weergave van /day-cycle: de
   geschiedenis en de ⋮ eindigden op 467 px in een vak dat tot 371 reikt, dus de
   rechterkant van de kop lag bijna honderd pixels buiten beeld. Het weghalen van
   de Klaar-pil scheelde maar 38 px — de echte veelvraat is "Je open stappen
   samen: 8u 10m", die 160 px breed is en niet mag afbreken.
   Daarom mag de kop nu WRAPPEN en krijgt die regel een eigen regel (zie
   __remaining hieronder): bovenaan de titel met zijn knoppen, daaronder de stand
   van je dag. Een rij met iets erbij schuift zo niet meer buiten beeld maar naar
   beneden, en dat is wat de poort meet (dagritme-onderin-staat-klaar.spec.js). */
.fz-day-cycle-page__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: 14px;
  row-gap: 6px;
  padding: 12px 4px 14px;
}
.fz-day-cycle-page__back,
.fz-day-cycle-page__edit {
  background: transparent;
  border: 0;
  padding: 6px;
  color: var(--ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  min-width: 24px;
  min-height: 24px;
  border-radius: var(--radius-md);
  transition: background var(--dur-fast) var(--ease-standard);
}
.fz-day-cycle-page__back:hover,
.fz-day-cycle-page__edit:hover {
  background: var(--line-2);
}
.fz-day-cycle-page__back svg,
.fz-day-cycle-page__edit svg {
  width: 20px;
  height: 20px;
}
.fz-day-cycle-page__title {
  flex: 1 1 auto;
  /* Zonder dit houdt een flex-kind zijn min-content breedte vast en duwt het de
     knoppen rechts van zich het beeld uit in plaats van zelf af te breken. */
  min-width: 0;
  margin: 0;
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -0.005em;
  line-height: 1.2;
}
.fz-day-cycle-page__body { display: flex; flex-direction: column; gap: 16px; }

/* ═══════════ CHECK-IN-TOONT-JE-DAGRITME-BOVENAAN ═══════════
   Hetzelfde dagritme, maar nu ÍN een andere pagina (/check-in). Dat vraagt geen
   nieuwe vormentaal, alleen minder pagina: geen eigen bovenmarge, en een kop die
   een tussenkopje is in plaats van een paginatitel. De terugknop en de
   geschiedenisknop worden hier in JS al weggelaten — de gastpagina heeft ze. */
.fz-day-cycle-page--embedded {
  padding: 0;
}
.fz-day-cycle-page--embedded .fz-day-cycle-page__head {
  padding: 0 4px 10px;
}
.fz-day-cycle-page--embedded .fz-day-cycle-page__title {
  font-size: 15px;
}
.fz-day-cycle-page__loading,
.fz-day-cycle-page__error,
.fz-day-cycle-page__empty {
  padding: 16px;
  font-size: 13px;
  color: var(--muted);
  text-align: center;
}

/* DAY-CYCLE-EMPTY-START-AI-INTAKE — fresh-user hero + AI intake/preview UI.
   Tokens-only (Dag/Nacht safe); accent button uses --on-accent (never white,
   per feedback_accent_buttons_on_accent_token). */
.fz-day-cycle-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  padding: 40px 20px;
  text-align: center;
}
/* DIARY-DAYCYCLE-EMPTY-STATE-CREATE-CTA — compact variant inside the check-in card. */
.fz-day-cycle-empty--widget { gap: 12px; padding: 8px 4px 4px; }
.fz-day-cycle-empty--widget .fz-day-cycle-empty__lead { font-size: 13px; }
.fz-day-cycle-empty__lead {
  margin: 0;
  max-width: 320px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--muted);
}
.fz-day-cycle-empty__cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font: inherit;
  font-size: 15px;
  font-weight: 700;
  padding: 12px 20px;
  border: 0;
  border-radius: var(--radius-lg);
  background: var(--accent);
  color: var(--on-accent);
  cursor: pointer;
}
.fz-day-cycle-empty__cta svg { width: 18px; height: 18px; }
.fz-day-cycle-empty__self {
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  background: transparent;
  border: 0;
  color: var(--muted);
  text-decoration: underline;
  cursor: pointer;
}

/* ═══════════ DAGRITME-IEDEREEN-BEGINT-MET-EEN-STANDAARDRITME ═══════════
   Het standaardritme als ZICHTBAAR aanbod op de lege staat: acht regels met
   hun soortvlakje, en daaronder één knop. Links uitgelijnd binnen een verder
   gecentreerd scherm — een lijst die je moet kunnen lézen als lijst mag niet
   midden-uitgelijnd zijn, want dan zwemmen de namen los van hun vlakjes.
   Tokens only, licht én donker. */
.fz-dc-offer {
  width: 100%;
  max-width: 340px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 16px 16px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  text-align: left;
}
.fz-dc-offer__grp {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.fz-dc-offer__lbl {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.fz-dc-offer__row {
  display: flex;
  align-items: center;
  gap: 12px;
}
.fz-dc-offer__name {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
}
/* De slaapregel is geen rij: hij staat TUSSEN de twee lijstjes en zegt alleen
   hoeveel uur er tussen zit (DAGRITME-SLAAP-IS-EEN-VAST-BLOK). */
.fz-dc-offer__sleep {
  font-size: 12px;
  color: var(--muted);
  padding-left: 42px;
}
.fz-dc-offer__cta {
  align-self: stretch;
  justify-content: center;
}
/* De AI-intake zakte naar de tweede plaats: dezelfde knop, zonder accentvlak.
   Twee accentknoppen op één scherm is geen eerste keuze meer. */
.fz-day-cycle-empty__alt {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  padding: 10px 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}
.fz-day-cycle-empty__alt svg { width: 17px; height: 17px; color: var(--muted); }

.fz-day-cycle-intake__dots {
  display: flex;
  gap: 8px;
  justify-content: center;
  padding: 4px 0 16px;
}
.fz-day-cycle-intake__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--line-2);
}
.fz-day-cycle-intake__dot.is-done { background: var(--muted); }
.fz-day-cycle-intake__dot.is-active { background: var(--accent); }
.fz-day-cycle-intake__q {
  margin: 0 0 12px;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.4;
  color: var(--ink);
}
.fz-day-cycle-intake__ta {
  width: 100%;
  box-sizing: border-box;
  font: inherit;
  font-size: 14px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
  resize: vertical;
}
.fz-day-cycle-intake__foot { justify-content: space-between; }
/* DAYCYCLE-AI-IMPROVE-ROUND — what the user said in an earlier round, shown as
   a quiet reminder ABOVE the (empty) box, never poured into it. */
.fz-day-cycle-intake__recall {
  margin: -6px 0 8px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--muted);
}
.fz-day-cycle-intake__err {
  margin-top: 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  text-align: center;
}
.fz-day-cycle-intake__err-msg {
  margin: 0;
  font-size: 13px;
  color: var(--danger);
}

.fz-day-cycle-preview__group { margin-bottom: 16px; }
.fz-day-cycle-preview__lbl {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--muted);
  margin-bottom: 6px;
}
.fz-day-cycle-preview__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-md);
  margin-bottom: 6px;
  background: var(--surface);
}
.fz-day-cycle-preview__name {
  font-size: 14px;
  color: var(--ink);
}
.fz-day-cycle-preview__dur {
  font-size: 12px;
  color: var(--muted);
  flex: 0 0 auto;
}

/* DAYCYCLE-AI-IMPROVE-ROUND — the proposal as a decision per step. The
   word-mark carries the action (text, never an icon alone), the reason sits
   under it in a quieter voice, and the switch turns that ONE line off. */
.fz-day-cycle-improve__scope {
  margin: 0 0 14px;
  font-size: 13px;
  color: var(--ink-2);
}
.fz-day-cycle-improve__row {
  padding: 10px 12px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-md);
  margin-bottom: 6px;
  background: var(--surface);
}
.fz-day-cycle-improve__row.is-off { opacity: 0.5; }
.fz-day-cycle-improve__top {
  display: flex;
  align-items: center;
  gap: 10px;
}
.fz-day-cycle-improve__tag {
  flex: 0 0 auto;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 3px 7px;
  border-radius: 999px;
  background: var(--line-2);
  color: var(--ink-2);
}
.fz-day-cycle-improve__tag--change { background: var(--accent); color: var(--on-accent); }
.fz-day-cycle-improve__tag--add    { background: var(--accent); color: var(--on-accent); }
.fz-day-cycle-improve__tag--remove { background: var(--danger); color: #fff; }
.fz-day-cycle-improve__names {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px;
}
.fz-day-cycle-improve__name { font-size: 14px; color: var(--ink); }
.fz-day-cycle-improve__was {
  font-size: 12px;
  color: var(--muted);
  text-decoration: line-through;
}
.fz-day-cycle-improve__why {
  margin: 6px 0 0;
  font-size: 12px;
  line-height: 1.4;
  color: var(--muted);
}
.fz-day-cycle-improve__sw {
  flex: 0 0 auto;
  position: relative;
  width: 40px;
  height: 24px;
  cursor: pointer;
}
.fz-day-cycle-improve__sw input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}
.fz-day-cycle-improve__sw-track {
  position: absolute;
  inset: 0;
  border-radius: 999px;
  background: var(--line-2);
  transition: background 0.15s ease;
  pointer-events: none;
}
.fz-day-cycle-improve__sw-track::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--surface);
  transition: transform 0.15s ease;
}
.fz-day-cycle-improve__sw input:checked + .fz-day-cycle-improve__sw-track {
  background: var(--accent);
}
.fz-day-cycle-improve__sw input:checked + .fz-day-cycle-improve__sw-track::after {
  transform: translateX(16px);
}
.fz-day-cycle-improve__sw input:focus-visible + .fz-day-cycle-improve__sw-track {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* /check-in — DIARY-CHECKIN-BAND-BEHIND-OPEN-CHECKIN-BUTTON (v0.9.494). Thin
   shell hosting the Day Cycle widget + Mood entry card + Log Sleep + Log
   Weight surfaces. Chrome mirrors /day-cycle: back chevron + page title in
   the header, gap-16 stack in the body, 16px outer-edge padding. Routed-view
   padding follows the v0.9.489 mood-page pattern (12px top + 16px sides +
   16px bottom, then .app-main supplies nav-clearance). */
.checkin-page {
  padding: var(--space-3) var(--space-4) var(--space-4);
  color: var(--ink);
}
.checkin-page__head {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 0 0 12px;
}
.checkin-page__back {
  background: transparent;
  border: 0;
  padding: 6px;
  color: var(--ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  min-width: 24px;
  min-height: 24px;
  border-radius: var(--radius-md);
  transition: background var(--dur-fast) var(--ease-standard);
}
.checkin-page__back:hover { background: var(--line-2); }
.checkin-page__back svg {
  width: 20px;
  height: 20px;
}
.checkin-page__title {
  flex: 1 1 auto;
  margin: 0;
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -0.005em;
  line-height: 1.2;
}
.checkin-page__body {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* CHECKIN-HCD-REDESIGN-BUILD — small-caps date eyebrow at the top of the
   check-in page. It labels the DAY, not the card below it: the first element
   underneath is .ck-head (the day question — the bookmark next to it went with
   CHECK-IN-DE-BLADWIJZER-GAAT-WEG), so the old negative
   margin-bottom — which glued the eyebrow to a quick-log button stack that no
   longer exists — made the date collide with the line under it.
   No left inset either — the eyebrow must align with the body padding. */
.checkin-page__eyebrow {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 var(--space-3) 0;
}

/* CHECKIN-PAGE-REORDER-PLUS-MOOD-ANALYTICS-ICON-BUTTON (v0.9.506) — 3-per-row
   grid for the quick-log buttons. grid-template-columns is set inline by
   public/assets/js/checkin/index.js based on the count of enabled metrics
   (1 → 1 col / 2 → 2 cols / 3+ → 3 cols, rows wrap). Disabled-metric cells
   stay empty and are hidden via `:empty` so they don't reserve grid slots. */
.checkin-page__logs-grid {
  display: grid;
  gap: 12px;
}
.checkin-page__logs-grid__cell:empty { display: none; }
/* Tighten button chrome inside the narrower grid cell so single-word labels
   (Sleep / Weight / Waist / …) still read on a 360px viewport (~105px per
   cell at 3-per-row after the 12px gaps). Targets only the quick-log cells
   to avoid bleeding into other .cal-metric usages elsewhere. */
.checkin-page__logs-grid__cell .cal-metric {
  padding-left: 12px;
  padding-right: 12px;
}
.checkin-page__logs-grid__cell .cal-metric__lbl {
  font-size: 13px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* CHECKIN-LAYOUT-METRICS-TOP-DAYCYCLE-MOOD-BOTTOM (v0.9.979) — the hoofdmetric
   (order=0 enabled metric) gets a slightly larger/bolder label. Size-token bump
   only; the cell stays the same .cal-metric component. */
.checkin-page__logs-grid__cell.metric-row--primary .cal-metric__lbl {
  font-size: 15px;
  font-weight: 700;
}
/* CHECKIN-BUTTON-MINIMALIST-DEMOTION (v0.9.553) — demote the empty-state
   quick-log tiles on /check-in only. Diary's identical .cal-metric--empty
   cells stay accent-filled (still acting as a primary "log this" affordance
   inline with the calendar). Scoped under .checkin-page__logs-grid__cell
   so the global rule at L2923 is untouched. Border uses --line not --accent
   to read subtler than Save entry, matching the spec's
   tile-quieter-than-Save-entry hierarchy. */
.checkin-page__logs-grid__cell .cal-metric--empty {
  background: transparent;
  color: var(--ink);
  border-color: var(--card-edge, var(--line));
}
.checkin-page__logs-grid__cell .cal-metric--empty .cal-metric__hint {
  opacity: 0.7;
}

/* ── CHECK-IN-DE-LOSSE-WIDGETS-GAAN-WEG ──────────────────────────────────
   CHECKIN-HCD-REDESIGN-BUILD gaf deze pagina een voortgangskaart over vier
   gegroepeerde oppervlakken (Metingen-disclosure, Reflectie, dagritme-strip,
   foto-chip). Alle vijf zijn weg: het dagritme IS de check-in. Wat hier nog aan
   .ck-*-klassen staat is precies wat dat ritme niet draagt — de kop van de dag
   (.ck-head, sinds CHECK-IN-DE-BLADWIJZER-GAAT-WEG alleen nog de dagvraag-zin),
   de afsluiting (.ck-closing) en de zondagse terugblik (.ck-recap).
   Elke kleur loopt via een thema-token, zodat Dag en Nacht beide houden. */
.checkin-page__eyebrow--past { color: var(--accent); }

.ck-card {
  background: var(--surface-card, var(--surface));
  border: 1px solid var(--card-edge, var(--line));
  border-radius: var(--radius-lg);
  padding: 16px;
}

/* CHECK-IN-DE-BLADWIJZER-GAAT-WEG — hier stond .ck-moment, de ronde bladwijzer
   naast de dagvraag (NUTRITION-CHECKIN-MEANINGFUL-MOMENTS-BUTTON). De knop is
   weg; het blad eronder niet, want ema/ema-sheet.js gebruikt dezelfde tags en
   de zondagse terugblik noemt het laatste moment nog. Daarom blijft alles van
   .fz-mm-* hieronder staan. */

/* Mini-sheet: reuses .premium-overlay/.premium-sheet chrome; only the chip grid
   is new. Two columns on narrow phones so eight tags stay one screen, no scroll. */
.fz-mm-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
@media (min-width: 420px) { .fz-mm-grid { grid-template-columns: repeat(4, 1fr); } }
.fz-mm-chip {
  padding: 12px 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: background .12s ease, border-color .12s ease, color .12s ease;
}
.fz-mm-chip:hover { border-color: var(--accent); }
.fz-mm-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.fz-mm-chip--on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.fz-mm-chip[disabled] { cursor: default; opacity: .6; }
.fz-mm-chip--on[disabled] { opacity: 1; }

/* NUTRITION-EMA-PING-FIRST-14-DAYS — the nightly one-tap probe. Same
   .premium-overlay/.premium-sheet chrome as the moments sheet; the three smileys
   are the only new furniture, and the optional tag layer reuses .fz-mm-grid
   verbatim so both surfaces stay one visual language. */
.fz-ema-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.fz-ema-smiley {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 16px 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--ink);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background .12s ease, border-color .12s ease, color .12s ease, opacity .12s ease;
}
.fz-ema-smiley svg { width: 34px; height: 34px; }
.fz-ema-smiley__label { line-height: 1.2; text-align: center; }
.fz-ema-smiley:hover { border-color: var(--accent); }
.fz-ema-smiley:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.fz-ema-smiley--on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.fz-ema-smiley[disabled] { cursor: default; }
.fz-ema-smiley--dim { opacity: .45; }
.fz-ema-tags { margin-top: 18px; }
.fz-ema-tags__lede { margin: 0 0 10px; font-size: 13px; color: var(--muted); }
@media (prefers-reduced-motion: reduce) {
  .fz-ema-smiley { transition: none; }
}

.ck-done-row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 15px;
  font-weight: 700;
  color: var(--accent);
}
.ck-done-row__tick {
  width: 22px;
  height: 22px;
  flex: 0 0 auto;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.ck-done-row__tick svg { width: 14px; height: 14px; }

/* Peak-end celebration on a fully completed day. Deliberately NOT confetti —
   this happens daily, and a burst that big turns into noise within a week. */
.ck-celebrate {
  font-size: 14px;
  line-height: 1.45;
  color: var(--ink-2, var(--ink));
}

/* CHECK-IN-DE-LOSSE-WIDGETS-GAAN-WEG — de afsluiting hing ónder de vier
   bolletjes van de voortgangskaart en glinsterde eroverheen. Die kaart is weg,
   dus de afsluiting staat nu op zichzelf onder het dagritme: één vinkregel en
   één zin. Leeg is hij er niet: zolang het blok van NU niet rond is staat hier
   geen lege doos die ruimte houdt voor iets wat nog niet gebeurd is. */
.ck-closing { display: flex; flex-direction: column; gap: 10px; }
.ck-closing:not(.ck-closing--done) { display: none; }
/* De glinstering liep over de bolletjes; die zijn er niet meer, dus loopt hij
   over het vinkje — het enige element dat pas op dit moment verschijnt. */
@keyframes ck-closing-glow {
  0%   { opacity: .5; }
  40%  { opacity: 1; }
  100% { opacity: .5; }
}
.ck-closing--shimmer .ck-done-row__tick { animation: ck-closing-glow 600ms ease-in-out 3; }
@media (prefers-reduced-motion: reduce) {
  .ck-closing--shimmer .ck-done-row__tick { animation: none; }
}

/* CHECK-IN-DE-LOSSE-WIDGETS-GAAN-WEG — de kop van de check-in. Hij draagt wat
   de kop van de voortgangskaart droeg en wat niet met die kaart mee mocht
   verdwijnen: de dagvraag-zin links, de bladwijzerknop rechts.
   CHECK-IN-DE-BLADWIJZER-GAAT-WEG haalde die knop weg, en daarmee de tweede
   flex-kolom. De rij blijft een flexrij, want de dagvraag-zin rekent op
   `flex: 1 1 auto; min-width: 0` voor zijn afbreekgedrag; de `gap` valt vanzelf
   stil (gap telt alleen TUSSEN kinderen), dus er blijft geen ruimte over die
   voor het icoon gereserveerd was en de kop kan niet verspringen. */
.ck-head { display: flex; align-items: flex-start; gap: 10px; }
.ck-head .fz-dayq-line--checkin { flex: 1 1 auto; min-width: 0; }

/* CHECK-IN-WEGEN-PER-WEEK-FOTO-PER-MAAND-DE-STANDAARD-IS-RUSTIG — een lege cel
   waar de app vandaag niet om vraagt, op de Dagboek-tab. Geen opacity: stil is
   niet hetzelfde als slecht leesbaar. Een doorzichtige cel haalt het label mee
   omlaag en dan zakt het naar 3,03:1 — precies de klacht uit
   CONTRAST-DE-ZACHTE-TEKST-IS-OOK-IN-HET-DONKER-TE-LEZEN, die deze cel in de
   contrastpoort meet. */
.cal-metric--quiet {
  background: transparent;
  color: var(--ink);
  border-color: var(--line);
}

/* Het "?" naast de titel van het dagritme. Het stond in de kop van de
   dagritme-strip op /check-in; die strip verdween met
   CHECK-IN-DE-LOSSE-WIDGETS-GAAN-WEG, de uitleg niet. */
.ck-help {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--muted);
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}

/* NUTRITION-CHECKIN-SUNDAY-RECAP-CARD — the week's closing sentence, above the
   day rhythm on Sundays. Deliberately the QUIETEST card on the page: no accent
   fill, no border emphasis, muted icons. It sits FIRST for recency, not for
   weight — the day rhythm must stay the only primary surface, so this one reads
   at body scale and takes its colour entirely from --muted/--ink. Every colour
   is a token, so Dag and Nacht both hold. */
.ck-recap { display: flex; flex-direction: column; gap: 10px; }
.ck-recap__title {
  margin: 0;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--muted);
}
.ck-recap__list { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 8px; }
.ck-recap__row { display: flex; align-items: center; gap: 10px; }
.ck-recap__icon { width: 17px; height: 17px; flex: 0 0 auto; color: var(--muted); }
.ck-recap__text { font-size: 14px; font-weight: 600; color: var(--ink); }
.ck-recap__empty { margin: 0; font-size: 14px; color: var(--muted); }

/* 320px viewport — the four dot labels and the two time chips are the two
   places that overflow first, so tighten those rather than the whole page. */
@media (max-width: 359px) {
  .ck-card { padding: 14px; }
  .ck-recap__text, .ck-recap__empty { font-size: 13px; }
}

/* DAGRITME-ELKE-ZIN-ZEGT-WAT-JE-DOET — de tijdkaart is geen rij van drie
   kolommen meer (Start | slotje | Einde) maar twee regels onder elkaar: zon =
   ochtendlijstje af, maan = avondlijstje begint. Elke regel noemt zijn lijstje,
   de vastgezette kant draagt het woord "vast", de andere kant de knop. */
.fz-day-cycle-page__controls {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}
.fz-day-cycle-times__line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 10px;
  min-height: 44px;
}
.fz-day-cycle-times__ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 24px;
  color: var(--ink-2);
}
.fz-day-cycle-times__line.is-fixed .fz-day-cycle-times__ico { color: var(--accent); }
.fz-day-cycle-times__field {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 8px;
  flex: 1 1 auto;
  min-width: 0;
}
.fz-day-cycle-times__lbl {
  font-size: 14px;
  line-height: 1.3;
  color: var(--ink);
}
.fz-day-cycle-times__inp {
  font: inherit;
  font-size: 18px;
  font-variant-numeric: tabular-nums;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
  text-align: center;
  transition: border-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard);
}
.fz-day-cycle-times__inp:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--ring-accent);
}
.fz-day-cycle-times__line.is-fixed .fz-day-cycle-times__inp { border-color: var(--accent); }
/* "vast" is een LABEL, geen knop: geen rand, geen slotje, niets om op te
   tikken. Het slotje dat hier stond las als een knop die niets deed. */
.fz-day-cycle-times__fixed {
  flex: 0 0 auto;
  font-size: 12px;
  font-weight: 600;
  color: var(--accent);
}
.fz-day-cycle-times__fix {
  flex: 0 0 auto;
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  padding: 7px 12px;
  min-height: 32px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}
.fz-day-cycle-times__fix:hover { background: var(--line-2); }
.fz-day-cycle-times__fix:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.fz-day-cycle-times__hint {
  margin: 0;
  font-size: 12px;
  line-height: 1.45;
  color: var(--muted);
}

/* The same card BEFORE there is an anchor: one question instead of two empty
   time fields. The grid is dropped — a single question is not three columns. */
.fz-day-cycle-page__controls--ask {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
}
.fz-dc-ask__q {
  margin: 0;
  font-size: 16px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--ink);
}
/* DAGRITME-ELKE-ZIN-ZEGT-WAT-JE-DOET — de regel die zegt wat de app met je
   antwoord DOET. Hij hoort bij de vraag erboven, niet bij het invulvak
   eronder: de omhullende flex zet 12px tussen alles, en op gelijke afstand
   zou hij van niemand zijn. Daarom trekt hij zichzelf naar de vraag toe. */
.fz-dc-ask__why {
  margin: -6px 0 0;
  color: var(--ink-2);
  font-size: var(--text-caption);
  line-height: 1.35;
}
.fz-dc-ask__row { display: flex; align-items: center; gap: 10px; }
.fz-dc-ask__inp {
  flex: 1 1 auto;
  min-width: 0;
  font-family: inherit;
  font-size: 20px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 10px 12px;
  text-align: center;
}
.fz-dc-ask__inp:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring-accent); }
.fz-dc-ask__set {
  flex: 0 0 auto;
  font-family: inherit;
  font-size: 15px;
  font-weight: 700;
  color: var(--on-accent);
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: var(--radius-md);
  padding: 12px 22px;
  cursor: pointer;
}
.fz-dc-ask__swap {
  align-self: flex-start;
  background: transparent;
  border: 0;
  padding: 0;
  font-family: inherit;
  font-size: 13px;
  color: var(--muted);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.fz-dc-ask__swap:hover { color: var(--ink); }

/* Phase cards — one card per phase (Evening / Sleep / Morning). Thin
   colored left-bar accent extends the full height of the card so the
   eye picks up the phase at a glance without parsing the small-caps
   header text. SLEEP card additionally takes the existing
   --day-cycle-sleep-bg darker surface from v0.9.466 so the night band
   stays a deliberate visual separator. */
.fz-day-cycle-page__list {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.fz-dc-now-line {
  position: absolute;
  left: 0;
  right: 0;
  height: 1.5px;
  /* semi-transparent line so underlying text stays readable; dot uses full accent */
  background: color-mix(in srgb, var(--accent) 40%, transparent);
  pointer-events: none;
  z-index: 10;
}
.fz-dc-now-line::before {
  content: '';
  position: absolute;
  left: 4px;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent);
}
.fz-day-cycle-page__remaining { font-size: 11px; font-weight: 600; color: var(--accent); background: var(--accent-light); border-radius: var(--radius-pill); padding: 2px 8px; white-space: nowrap; }
/* DAGRITME-ONDERIN-STAAT-KLAAR — deze pil staat in de DOM vóór het slot met de
   ⋮, want daar hoort hij te lezen: eerst je stand, dan het menu. In beeld is hij
   met 160 px te breed om die rij te delen, dus hij krijgt een eigen regel: een
   flex-basis van 100% kan met niemand een regel delen, en `order: 1` zet hem
   onder de titelrij in plaats van erboven. De leesorde blijft daarmee gelijk aan
   de DOM-orde — alleen de regelafbreking verandert.
   Het slot moet dan zelf rechts gaan staan: zonder het potlood dat er eerst
   achter stond heeft de titel niets meer om tegenaan te duwen. */
.fz-day-cycle-page__head .fz-day-cycle-page__remaining {
  order: 1;
  flex: 1 1 100%;
  align-self: flex-start;
}
.fz-day-cycle-page__head .fz-day-cycle-page__head-slot { margin-left: auto; }
.fz-day-cycle-page__remaining--done { color: var(--muted); background: transparent; font-weight: 400; }
.fz-day-cycle-card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
  padding-left: 3px;
}
.fz-day-cycle-card::before {
  content: '';
  position: absolute;
  top: 0; bottom: 0; left: 0;
  width: 3px;
  background: var(--accent);
}
.fz-day-cycle-card--evening::before { background: var(--day-cycle-evening-accent); }
.fz-day-cycle-card--sleep::before   { background: var(--day-cycle-sleep-accent); }
.fz-day-cycle-card--morning::before { background: var(--day-cycle-morning-accent); }
.fz-day-cycle-card--sleep {
  background: var(--day-cycle-sleep-bg);
}
.fz-day-cycle-card__head {
  display: flex;
  align-items: center;
  min-height: 36px;
  padding: 10px 16px 6px;
}
.fz-day-cycle-card__lbl {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}
.fz-day-cycle-card__rows {
  display: flex;
  flex-direction: column;
}

/* CHECK-IN-TOONT-JE-DAGRITME-BOVENAAN — een dichtgeklapt blok. De kop mét teller
   blijft staan: '0/3' is een samenvatting, en dát is waarom het blok dicht mag.
   Alleen de rijen gaan weg. Het hele kopje is de knop (een chevron van 16px is
   op een telefoon geen doelwit); de chevron draagt de stand. */
.fz-day-cycle-card--collapsed .fz-day-cycle-card__rows {
  display: none;
}
.fz-day-cycle-card--collapsed .fz-day-cycle-card__head {
  padding-bottom: 10px;
}
.fz-day-cycle-page--embedded .fz-day-cycle-card__head {
  cursor: pointer;
}
.fz-day-cycle-card__fold {
  background: transparent;
  border: 0;
  margin-left: 10px;
  padding: 6px 0 6px 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  cursor: pointer;
}
.fz-day-cycle-card__fold svg {
  transform: rotate(90deg);
  transition: transform var(--dur-fast) var(--ease-standard);
}
.fz-day-cycle-card__fold[aria-expanded="true"] svg {
  transform: rotate(-90deg);
}

/* ═══════════ DAGRITME-SLAAP-IS-EEN-VAST-BLOK ═══════════
   Sleep is not a task card between two other task cards — it is THE BLOCK
   between the evening list and the morning list. So it reads differently:
   sunk into the page instead of raised off it, no checkbox, no drag handle,
   no row menu. It shows what the cascade computed and asks exactly one thing.
   No score, no colour, no verdict — a short night is information, not a fail. */
.fz-day-cycle-sleep {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 12px 16px 14px;
  background: var(--day-cycle-sleep-bg);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-lg);
}
.fz-day-cycle-sleep__head { display: flex; align-items: center; min-height: 20px; }
.fz-day-cycle-sleep__lbl {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}
.fz-day-cycle-sleep__plan {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px;
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
}
.fz-day-cycle-sleep__planned { font-size: 13px; font-weight: 400; color: var(--muted); }
.fz-day-cycle-sleep__ask {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 2px;
}
.fz-day-cycle-sleep__ask-lbl { font-size: 14px; color: var(--ink); }
.fz-day-cycle-sleep__hours {
  width: 72px;
  min-height: 44px;
  padding: 8px 10px;
  font: inherit;
  font-size: 16px; /* iOS zooms anything smaller on focus */
  font-weight: 600;
  text-align: center;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}
.fz-day-cycle-sleep__unit { font-size: 14px; color: var(--muted); margin-left: -2px; }
.fz-day-cycle-sleep__nosleep {
  margin-left: auto;
  min-height: 44px;
  padding: 8px 4px;
  background: transparent;
  border: 0;
  font: inherit;
  font-size: 13px;
  color: var(--muted);
  text-decoration: underline;
  cursor: pointer;
}
.fz-day-cycle-sleep__nosleep.is-on { color: var(--ink); font-weight: 600; }
/* The watch reads ALONG, it does not correct: plain text weight, no badge, no
   colour. Tapping adopts the number as your own answer. */
.fz-day-cycle-sleep__watch {
  align-self: flex-start;
  min-height: 32px;
  padding: 4px 0;
  background: transparent;
  border: 0;
  font: inherit;
  font-size: 13px;
  color: var(--muted);
  text-align: left;
  cursor: pointer;
}
/* DAGRITME-DE-DOE-STAND-IS-OM-AF-TE-VINKEN — de gesloten rij draagt drie dingen:
   vinkje, naam, tijd. Het budget is 56 px op 390 px breed: 12+12 px padding laat
   32 px inhoud over, en de tijdkolom (16,8 + 13,2) past daar met 2 px over in.
   Alles wat je zelden verandert leeft in .fz-day-cycle__detail, die als vierde
   kind op een EIGEN regel valt — vandaar flex-wrap. row-gap blijft 0, want de
   detaillade zet haar eigen marge; een gap van 12 px zou ook de gesloten rij
   raken zodra een naam ooit wrapt. */
.fz-day-cycle__row {
  position: relative; /* draagt het pijlpuntje van de volgende stap */
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  column-gap: 12px;
  row-gap: 0;
  min-height: 56px;
  padding: 12px 16px;
  border-top: 1px solid var(--line-2);
}
.fz-day-cycle__content {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  cursor: pointer;
}
/* Rechterkolom: tijd boven, duur klein eronder. Rechts uitgelijnd zodat negen
   stappen onder elkaar één kolom cijfers vormen in plaats van een rafelrand. */
.fz-day-cycle__when {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.fz-day-cycle__when-time {
  display: flex;
  align-items: baseline;
  gap: 4px;
  font-size: 14px;
  line-height: 1.2;
}
.fz-day-cycle__ti-start { color: var(--ink); font-weight: 500; }
.fz-day-cycle__ti-actual { color: var(--ink); font-weight: 600; }
.fz-day-cycle__ti-was   { color: var(--muted); font-size: 11px; font-style: italic; }
.fz-day-cycle__ti-dur   { color: var(--muted); font-size: 11px; line-height: 1.2; }
/* DAGRITME-EEN-GEWOONTE-HEEFT-EEN-LOOPTIJD — de looptijd is één klein grijs
   woord onder de duur, in dezelfde kolom. Geen pil, geen kleur: een afspraak met
   jezelf is geen waarschuwing, en een gekleurd label zou negen rijen in een
   stoplicht veranderen. */
.fz-day-cycle__ti-until { color: var(--muted); font-size: 11px; line-height: 1.2; }
/* De vraag op de dag ná de looptijd. Zij vervangt de tijdkolom en valt op een
   EIGEN regel over de volle breedte — vandaar flex-basis 100% met een eigen
   marge, precies zoals de detaillade: de rij houdt row-gap 0, dus alles wat een
   eigen regel pakt zet zelf zijn ruimte. `hidden` zetten op de tijdkolom werkt
   hier niet (display:flex overrulet het), daarom wordt de tijd niet verborgen
   maar simpelweg niet gebouwd. */
.fz-day-cycle__ask {
  flex: 0 0 100%;
  width: 100%;
  margin-top: 8px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.fz-day-cycle__ask-q {
  font-size: 13px;
  line-height: 1.35;
  color: var(--ink);
  font-weight: 500;
}
.fz-day-cycle__ask-acts {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.fz-day-cycle__ask-btn {
  font: inherit;
  font-size: 13px;
  line-height: 1.2;
  padding: 8px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
.fz-day-cycle__ask-btn--yes {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.fz-day-cycle__ask-btn[disabled] { opacity: 0.55; cursor: default; }
/* Eén dag zichtbaar, daarna weg: "Ingeslepen: <naam>" is een viering, geen
   nieuwe permanente regel in de lijst. */
.fz-day-cycle__ingrained {
  padding: 10px 16px;
  border-top: 1px solid var(--line-2);
  font-size: 13px;
  line-height: 1.35;
  color: var(--muted);
}
/* De bewaarde notitie als één grijze regel onder de naam — afgekapt, nooit
   afgebroken over twee regels: dan is de rij geen rij meer. */
.fz-day-cycle__note-line {
  font-size: 12px;
  line-height: 1.3;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
/* DAGRITME-DE-COACH-GEEFT-EEN-GEWOONTE-MEE — "van Jayy" onder de naam. Exact
   dezelfde maat en kleur als de notitieregel hierboven: herkomst is geen
   status, dus geen badge, geen accentkleur en geen eigen achtergrond. Krijgt
   het wél nadruk, dan leest een coach-stap als belangrijker dan een stap die je
   zelf bedacht — en dat is precies de omkering die dit dagritme niet moet maken. */
.fz-day-cycle__from {
  font-size: 12px;
  line-height: 1.3;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
/* DAGRITME-EEN-MOOI-MOMENT-KIES-JE-ALS-TEGEL — het gekozen woord ('dankbaar')
   onder de naam van de Moment-rij. Dezelfde maat en dezelfde --muted als de
   notitie- en herkomstregel hierboven, om dezelfde reden: het is een aantekening
   bij de rij, niet een keurmerk erop. Géén gouden inkt, ook al is de soortkleur
   goud — een woord in de accentkleur van zijn soort leest als een waarschuwing,
   en de acht woorden zijn juist allemaal even goed. --muted staat in alle acht
   thema's op contrast, een vaste tint zou dat in twee ervan verliezen. */
.fz-day-cycle__moment-word {
  font-size: 12px;
  line-height: 1.3;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
/* De lade. Volle breedte op een eigen regel; alleen zichtbaar als de rij open
   staat, dus hij kost de gesloten lijst geen pixel. */
.fz-day-cycle__detail {
  flex: 0 0 100%;
  width: 100%;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px dashed var(--line-2);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* DAGRITME-HET-STANDAARDRITME-ZEGT-WAT-JE-DOET — de ene regel uitleg onder een
   sjabloonrij: hoe je hem afvinkt en waarom hij er staat. Zelfde rustige grijs
   en zelfde maat als de a14-lezing hieronder, want het is hetzelfde soort
   tekst: iets dat je één keer leest en daarna niet meer hoeft te zien. Geen
   kader, geen icoon, geen kleur — de naam blijft de koptekst van de lade.
   Contrast via --muted, het huistoken dat per thema al op leesbaarheid is
   gezet. Nagemeten over alle negen finishes, --muted tegen --surface én
   --surface-card: laagst 4,60:1 (nacht #7B8291 op #111827 en gauge #898881 op
   #1D2024), hoogst 6,99:1 (cresco-nacht); licht haalt 5,04 (dag) tot 6,24
   (cresco-dag). Alles boven de 4,5:1 van WCAG AA, in licht én donker. */
.fz-day-cycle__detail-why {
  font-size: 12px;
  line-height: 1.4;
  color: var(--muted);
}
.fz-day-cycle__detail-dur {
  display: flex;
  align-items: center;
  gap: 8px;
}
.fz-day-cycle__detail-lbl {
  font-size: 12px;
  color: var(--muted);
}
/* "Gedaan: 12 van de laatste 14 dagen" — een lezing, geen streak. Geen
   percentage en geen kleur: een getal dat naar nul kan springen maakt van een
   gemiste dag een verlies (les 4 uit het dagritme-onderzoek). */
.fz-day-cycle__detail-a14 {
  font-size: 12px;
  line-height: 1.35;
  color: var(--muted);
}
.fz-day-cycle__notes-wrap { min-width: 0; }
.fz-day-cycle__notes {
  display: block;
  cursor: pointer;
  border-radius: var(--radius-sm);
  padding: 2px 0;
  min-height: 18px;
}
.fz-day-cycle__notes-add {
  font-size: 12px;
  color: var(--muted);
}
.fz-day-cycle__notes-text {
  font-size: 12px;
  color: var(--muted);
  word-break: break-word;
  line-height: 1.4;
}
.fz-day-cycle__notes-input {
  font: inherit;
  font-size: 13px;
  width: 100%;
  padding: 4px 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  resize: none;
  min-height: 56px;
}
.fz-day-cycle__notes-input:focus {
  outline: none;
  border-color: var(--accent);
}
.fz-day-cycle-card__rows .fz-day-cycle__row:first-child { border-top: 0; }
.fz-day-cycle__row--done {
  opacity: 0.62;
}
.fz-day-cycle__row--done .fz-day-cycle__name {
  text-decoration: line-through;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--muted);
}
/* ═══════════ DAGRITME-EEN-RIJ-ZEGT-MET-ZIJN-VORM-WAT-JE-DOET ═══════════
   Achttien blinde testers over drie rondes: wie moest LEZEN wat een rij was,
   raadde mis. Wie de vórm zag, had het meteen. Vandaar één grammatica voor
   elke rij, en maar twee woorden erin:

     LINKS  het soort — een gekleurd tintvlak met het icoon erin.
     RECHTS de actie  — een leeg RONDJE betekent "vink mij af", een rond
                        knopje met een PIJL betekent "hier opent iets".

   Nooit allebei. Een vinkje op een rij die een flow opent beloofde in ronde 1
   dat je klaar was terwijl je nog moest beginnen.

   Maatvoering: het tintvlak is 30 px, niet 34. De gesloten rij heeft een
   budget van 56 px (poort 2 van dagritme-doe-stand-is-om-af-te-vinken) en
   houdt na 12+12 px padding 32 px over; 34 px zou dat budget in z'n eentje
   opmaken. 30 px laat 2 px lucht voor afronding op een echt toestel. */
.fz-day-cycle__kind {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 9px;
  color: var(--kind-c, var(--muted));
  /* Het tintvlak is de soortkleur zelf, sterk verdund — zo blijft het icoon
     leesbaar op licht én donker zonder twee losse kleurwaarden per soort. */
  background: color-mix(in srgb, var(--kind-c, var(--muted)) 16%, transparent);
}
.fz-day-cycle__kind svg { width: 18px; height: 18px; }
/* Gewoonte is neutraal: geen kleur claimen voor "gewoon een ding dat je doet". */
.fz-day-cycle__kind[data-kind="habit"] {
  background: color-mix(in srgb, var(--ink) 8%, transparent);
}

/* Het lege rondje. Een systeemvinkje kon dit niet zijn: het tekent een vierkant
   en negeert border-radius op iOS, en 4/4 testers lazen het vierkantje als
   "formulier" in plaats van als "afvinken". */
.fz-day-cycle__cb {
  appearance: none;
  -webkit-appearance: none;
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  margin: 1px 0 0;
  padding: 0;
  border: 2px solid var(--line-2);
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
}
.fz-day-cycle__cb:checked {
  border-color: var(--accent);
  background: var(--accent);
}
/* Het vinkje is getekend, niet gezet: een ::after met twee randen die je een
   kwartslag draait. Geen glyph, dus geen font dat het anders uitlijnt. */
.fz-day-cycle__cb:checked::after {
  content: "";
  display: block;
  width: 7px;
  height: 12px;
  margin: 2px auto 0;
  border: solid var(--on-accent);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.fz-day-cycle__cb:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Het pijlknopje: "er opent iets". Rustig van zichzelf — pas als dit DE
   volgende stap is, vult hij zich met accent (zie --next hieronder). */
.fz-day-cycle__go {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  margin-top: 1px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  background: color-mix(in srgb, var(--ink) 8%, transparent);
  color: var(--ink-2);
  cursor: pointer;
}
.fz-day-cycle__go svg { width: 16px; height: 16px; }
.fz-day-cycle__go--done {
  background: var(--accent);
  color: var(--on-accent);
}
.fz-day-cycle__go:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* DE VOLGENDE STAP — één rij per blok van nu. In ronde 2 las een rand alléén
   als "geselecteerd" (2/3), alsof je er per ongeluk op had gedrukt. Drie
   signalen samen lazen 3/3 als "hier ben je": de rand, een gevulde knop, en
   een pijlpuntje dat de rij aanwijst. */
.fz-day-cycle__row--next {
  background: color-mix(in srgb, var(--accent) 7%, transparent);
  box-shadow: inset 0 0 0 1.5px var(--accent);
  border-radius: 12px;
  /* Het waas hierboven is een DERDE ondergrond, naast --surface en
     --surface-card, en niemand had hem nagemeten. Hij licht de rij op (nacht:
     #0A0F1A wordt rgb(12,31,40)) en dat kost de rustige grijstint precies
     genoeg om onder WCAG AA te zakken: --muted haalt 4,97:1 op --surface van
     nacht, maar 4,38:1 op het waas — en op --surface-card 3,98:1. Gemeten
     2026-10-03 over alle acht afwerkingen: --muted op het waas loopt van
     3,90:1 (volt) tot 5,93:1 (cresco-nacht), dus vijf van de acht zakken
     eronder. Daarom draagt ALLE zachte tekst in deze ene rij de secundaire
     huisinkt in plaats van de grijze: --ink-2 op hetzelfde waas haalt 6,46:1
     (volt) tot 10,23:1 (cresco-nacht). Als token-herdefinitie en niet als
     lijstje selectoren, want een lijstje zwijgt tevreden over elke zachte
     regel die er later bij komt. */
  --muted: var(--ink-2);
}
.fz-day-cycle__row--next .fz-day-cycle__go {
  background: var(--accent);
  color: var(--on-accent);
}
.fz-day-cycle__next-tip {
  position: absolute;
  left: 3px;
  top: 50%;
  width: 0;
  height: 0;
  margin-top: -5px;
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  border-left: 6px solid var(--accent);
}

/* DE TELLER IN DE BLOKKOP — "1/4" met een dun balkje. Een ring begreep 0/7 en
   stipjes 0/3: allebei tonen ze een verhouding, geen getal, en juist het getal
   is wat je wil weten. 3/3 las "1/4 + balkje" meteen goed. */
.fz-day-cycle-card__head { gap: 8px; }
.fz-day-cycle-card__ico { flex: 0 0 auto; display: flex; color: var(--muted); }
.fz-day-cycle-card__ico svg { width: 16px; height: 16px; }
.fz-day-cycle-card__count {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 8px;
}
.fz-day-cycle-card__bar {
  width: 54px;
  height: 6px;
  border-radius: 3px;
  overflow: hidden;
  background: color-mix(in srgb, var(--ink) 10%, transparent);
}
.fz-day-cycle-card__bar-fill {
  height: 100%;
  border-radius: 3px;
  background: var(--accent);
}
.fz-day-cycle-card__num {
  font-size: 14px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}
/* Zodra er iets af is wordt het getal inkt: het blok is begonnen. */
.fz-day-cycle-card__num.is-on { color: var(--ink); }

/* DE '+'-SPOOKRIJ — gestippeld, want hij is er nog niet. Hij staat onderaan
   elk blok en opent hetzelfde tegelblad als het potlood. */
.fz-day-cycle__ghost {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 46px;
  padding: 8px 16px;
  border: 0;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
}
.fz-day-cycle__ghost-ico {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1.5px dashed var(--line-2);
  border-radius: 9px;
}
.fz-day-cycle__ghost-hint { color: var(--muted); }
.fz-day-cycle__ghost:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* ═══════════ DAGRITME-IEDEREEN-BEGINT-MET-EEN-STANDAARDRITME ═══════════
   DE AANBODRIJ 'Stemming' — bovenaan Ochtend en Avond, voor wie al een eigen
   lijst heeft. Gestippeld, want hij is er nog NIET: dezelfde stippellijn als
   de '+'-spookrij, en die betekent in dit blok al "hier mag jij iets". Links
   het echte soortvlakje zodat je ziet wat je krijgt, rechts een '+' en een ×.
   Eén keer aangeboden; de × (of een veeg) zet hem voorgoed uit. */
.fz-day-cycle__offer-wrap { overflow: hidden; }
.fz-day-cycle__offer {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px 12px 4px 4px;
  border: 1.5px dashed var(--line-2);
  border-radius: 12px;
  margin: 6px 12px;
  transition: transform 0.16s ease;
}
.fz-day-cycle__offer-add {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 44px;
  padding: 4px 8px;
  border: 0;
  background: transparent;
  font: inherit;
  text-align: left;
  color: var(--ink);
  cursor: pointer;
}
.fz-day-cycle__offer-txt { min-width: 0; }
.fz-day-cycle__offer-name {
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
}
.fz-day-cycle__offer-sub {
  font-size: 12px;
  color: var(--muted);
}
.fz-day-cycle__offer-plus {
  margin-left: auto;
  flex: 0 0 auto;
  display: flex;
  color: var(--accent);
}
.fz-day-cycle__offer-no {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}
.fz-day-cycle__offer-add:focus-visible,
.fz-day-cycle__offer-no:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* DE EERSTE ZEVEN DAGEN — een ritme dat je niet zelf hebt gebouwd draagt een
   stippelrand, zodat "dit mag je veranderen" uit de VORM komt en niet uit een
   badge (een badge zegt dat het ANDERS is dan jouw lijst, terwijl het juist
   jouw lijst is). Eén rand om het BLOK en niet om elke rij: acht gestippelde
   rijen leest als acht dingen die er nog niet zijn, en ze zijn er wél. */
.fz-day-cycle-card--fresh .fz-day-cycle-card__rows {
  border: 1.5px dashed var(--line-2);
  border-radius: 12px;
}
.fz-day-cycle-card--fresh .fz-day-cycle-card__rows .fz-day-cycle__row:first-child {
  border-top: 0;
}

/* Het slaapblok kreeg als enige stuk van de pagina geen '+'. Eén klein potlood
   zegt "de uren zijn van jou" zonder dat slaap een taak wordt. */
.fz-day-cycle-sleep__edit {
  margin-left: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}
.fz-day-cycle-sleep__edit svg { width: 15px; height: 15px; }
.fz-day-cycle-sleep__edit:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Een meetrij houdt zijn NAAM. Tot nu toe at de knop hem op ('+ Water' waar
   elke andere rij 'Glas water' zei), en daarmee las de rij als een knop in
   plaats van als een stap. */
.fz-day-cycle__name-txt {
  min-width: 0;
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Striking through a chip that shows a number turns the number into noise; the
   dimmed row already carries "done" for a measurement. */
.fz-day-cycle__row--metric.fz-day-cycle__row--done .fz-day-cycle__name {
  text-decoration: none;
}
/* DAGRITME-EEN-CIJFER-VAN-0-TOT-10-VUL-JE-IN-DE-RIJ-IN ─────────────────────────
   Een Cijfer-rij draagt één regel méér dan de andere: de schuif zelf. Het
   rijbudget van 56 px gaat daarom naar 92 px voor DEZE rij — niet door de
   meting te laten vallen, maar door hem te verhogen tot precies wat de band
   kost: 6 px lucht + 12 px baan + 4 px + 12 px cijferrij = 34 px bovenop de 56.
   Die 92 px staat in de poort, niet in een max-height: de rij draagt ook de
   uitgeklapte detaillade, en een harde hoogte zou die dichtknijpen.
   Deze rij heeft geen vinkje (de schuif IS de bedieningsknop), maar hij springt
   ook NIET in zoals een metingrij: het icoon staat in precies de kolom die het
   vinkje kwijt is, dus de namen en tijden liggen vanzelf al op één lijn. */
/* Een doorgestreepte naam boven een ingevuld cijfer leest als "dit klopt niet
   meer"; de gedimde rij zegt al genoeg. Zelfde reden als bij de metingrij. */
.fz-day-cycle__row--scale.fz-day-cycle__row--done .fz-day-cycle__name {
  text-decoration: none;
}
/* Het soort-icoon van een Cijfer-rij zit sinds
   DAGRITME-EEN-RIJ-ZEGT-MET-ZIJN-VORM-WAT-JE-DOET in hetzelfde tintvlak als
   elke andere rij (.fz-day-cycle__kind): maat en kleur komen daarvandaan, dus
   deze svg krijgt hier bewust geen eigen regel meer. De klasse blijft staan in
   de JS als haak voor de poorten die hem aanwijzen. */
/* Het antwoord: een GEVULDE schijf met het getal erin. Gevuld, want een holle
   ring op dezelfde plek las in de UAT als "vink mij af". Leeg = geen schijf. */
.fz-day-cycle__scale-val {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 15px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
/* De band valt op een EIGEN regel over de volle breedte — flex-basis 100% met
   een eigen marge, dezelfde truc als .fz-day-cycle__ask, want de rij houdt
   row-gap 0. Uitgelijnd met de naam erboven: 42 px inspringen vanaf de
   soortkolom (30 px tintvlak + 12 px kolomruimte). */
.fz-day-cycle__scale-band {
  flex: 0 0 calc(100% - 42px);
  width: calc(100% - 42px);
  margin: 6px 0 0 42px;
}
/* De baan. Zes stops, altijd dezelfde zes, ook als de rij al ingevuld is: de
   regenboog is de SCHAAL, niet de uitslag — hij mag dus niet meebewegen met het
   antwoord. touch-action: none, anders scrollt de pagina onder een sleep door. */
.fz-day-cycle__scale-track {
  position: relative;
  height: 12px;
  border-radius: 6px;
  background: linear-gradient(90deg,
    var(--scale-a) 0%,
    var(--scale-b) 22%,
    var(--scale-c) 42%,
    var(--scale-d) 62%,
    var(--scale-e) 80%,
    var(--scale-f) 100%);
  cursor: pointer;
  touch-action: none;
}
.fz-day-cycle__scale-track:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
/* Nog niet ingevuld: de baan staat er wél (anders weet je niet dat je kunt
   schuiven) maar dof, en ZONDER duim. Een holle duim las als een 5. */
.fz-day-cycle__scale-track--empty { opacity: 0.45; }
.fz-day-cycle__scale-thumb {
  position: absolute;
  top: 50%;
  width: 26px;
  height: 26px;
  margin-left: -13px;
  border-radius: 50%;
  background: var(--scale-thumb);
  box-shadow: 0 0 0 3px var(--scale-thumb-ring);
  transform: translateY(-50%);
  pointer-events: none;
}
/* 1 · 5 · 10 eronder: drie ankers, meer heeft een gevoelscijfer niet nodig. */
.fz-day-cycle__scale-ticks {
  display: flex;
  justify-content: space-between;
  margin-top: 4px;
  font-size: 10px;
  line-height: 1.2;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* ═══════════ DAGRITME-DE-GEVOELSBALK-LACHT-MEE (L111, 2026-10-05) ═══════════
   Jayy, over de ingevulde balk: "De 'how do you feel' bar is niet vanzelfsprekend.
   De smiley staat aan de linkerkant, dus ik neem aan dat rood helemaal goed is en
   blauw slecht, als in 'I'm feeling blue'?" Hij las de schaal ACHTERSTEVOREN — en
   dat is geen leesfout maar een ontwerpfout: de regenboog is met opzet géén
   oordeel (zie --scale-a hierboven), dus de baan zelf kan niet vertellen welke
   kant goed is, en het enige gezicht op het scherm stond links vastgepind.
   Het antwoord: het rij-icoon wordt een hartje (het ONDERWERP, niet de uitslag),
   en de duim wordt het gezicht dat met je antwoord meebeweegt. Alleen bij
   Stemming — bij een zelfbedacht onderwerp als 'Stress' betekent 10 juist slecht
   en zou een stralende smiley liegen (zie isMoodTopic in de JS). */

/* De 36px duim hangt 12px boven en 12px onder de 12px baan uit. Die lucht wordt
   hier EXPLICIET gereserveerd in plaats van de duim over de naam en de cijfers
   heen te laten lopen: 6px van de band + 6px hier = 12px boven, en de cijferrij
   zakt naar 12px zodat de onderrand van de duim er precies tegenaan komt. Een
   gevoelsrij is daarmee hoger dan een gewone Cijfer-rij; dat is de prijs van een
   gezicht dat groot genoeg is om op een telefoon een mond in te zien. */
.fz-day-cycle__scale-track--mood { margin-top: 6px; }
.fz-day-cycle__scale-track--mood + .fz-day-cycle__scale-ticks { margin-top: 12px; }
/* De eindgezichtjes staan IN de cijferrij, naast de 1 en de 10. Daarom worden de
   drie ankers flexregels: anders zou een 22px rondje de 10 van zijn plek duwen. */
.fz-day-cycle__scale-ticks span {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
/* Verdrietig naast de 1, superblij naast de 10 — de legenda van de schaal. Ze
   staan er ook als de rij nog LEEG is, en juist dan doen ze hun werk: een lege
   baan heeft geen duim (een holle duim las in de UAT als "er staat al een 5") en
   zegt dus uit zichzelf niets over de richting. Wit vlak met een dunne grijze
   ring, want zonder ring zwemt een wit rondje weg in de kaart eronder.
   Het schijfje IS de svg — zie scaleEndFace() voor waarom er geen span om zit. */
.fz-day-cycle__scale-end {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--scale-thumb);
  box-shadow: 0 0 0 1.5px var(--muted);
}
/* De duim met een gezicht erin: 36px in plaats van 26px, dus -18px in plaats van
   -13px. De donkere ring blijft 3px en blijft --scale-thumb-ring: die haalt 3:1
   tegen alle zes stops, ook tegen de gele in het midden. */
.fz-day-cycle__scale-thumb--face {
  width: 36px;
  height: 36px;
  margin-left: -18px;
  align-items: center;
  justify-content: center;
  display: flex;
}
.fz-day-cycle__face { display: block; }
/* Donkere lijnen op een wit vlak in BEIDE afwerkingen. De duim is met opzet
   afwerking-invariant wit (zie --scale-thumb), dus het gezicht erop mag dat ook
   zijn: een mond die in de donkere afwerking wit wordt, verdwijnt in de duim.
   Bewust GEEN overgang op left of op het gezicht — het gezicht moet onder je
   vinger staan, niet 90ms erachteraan, en dus is er ook niets wat
   prefers-reduced-motion hoeft uit te zetten. */
.fz-day-cycle__face-ln {
  fill: none;
  stroke: var(--scale-thumb-ring);
  stroke-width: 2.2;
  stroke-linecap: round;
}
.fz-day-cycle__face-fill { fill: var(--scale-thumb-ring); }
/* Het tongetje bij 9-10 in de laagste stop van de baan: het enige gekleurde
   streepje in het gezicht, en het verschil tussen "lacht" en "schreeuwt". */
.fz-day-cycle__face-tongue {
  fill: none;
  stroke: var(--scale-a);
  stroke-width: 2.2;
  stroke-linecap: round;
}
/* Het rondje met het getal krijgt bij Stemming de kleur van de BAAN op die
   stand. De UAT las een accentgroen rondje met een '3' erin als "goed gedaan" —
   het rondje IS het antwoord, dus het mag niet in een andere taal spreken dan de
   baan eronder. De kleur wordt uit dezelfde zes stops GESNEDEN in plaats van in
   een tweede lijst te worden overgetypt: dezelfde gradient op 10000% breed, en
   --scale-at (door de JS gezet) schuift het zichtbare procentje naar de goede
   plek. Eén lijst kleuren, dus geen lijst die na één ship uit de pas loopt.
   De inkt is de donkere ringkleur, en daar gaat een witte sluier (--scale-tint)
   over de kleur heen: een rij MET een antwoord is een `--row--done` en staat op
   opacity 0.62, en 62% van de volle oranje stop zet dat cijfer op 4,4:1. De
   sluier is de bovenste van twee lagen; de onderste is de gesneden regenboog,
   dus de tint blijft per stand verschillen — bleek-roze bij een 1 en bleek-groen
   bij een 8. Verbleken, niet vervangen. */
.fz-day-cycle__scale-val--mood {
  background-image:
    linear-gradient(0deg, var(--scale-tint), var(--scale-tint)),
    linear-gradient(90deg,
      var(--scale-a) 0%,
      var(--scale-b) 22%,
      var(--scale-c) 42%,
      var(--scale-d) 62%,
      var(--scale-e) 80%,
      var(--scale-f) 100%);
  background-size: 100% 100%, 10000% 100%;
  background-position: 0 0, var(--scale-at, 50%) 0;
  color: var(--scale-thumb-ring);
}
.fz-day-cycle__name {
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
  min-width: 0;
  line-height: 1.3;
  /* Allow up to 2 lines, then ellipsis. -webkit-line-clamp + display:-webkit-box
     is supported across iOS Safari + Chrome + FF (with the prefixed property). */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
  overflow-wrap: anywhere;
}
/* DAY-CYCLE-REFINEMENTS (v0.9.466) — reusable H+M duration input.
   Named .fz-time-duration-input so the same pattern can be lifted into
   other surfaces that take minute-precision durations (set timers,
   habit reminders) without re-deriving the layout. Internal storage
   stays minutes; the two inputs are pure UI. v0.9.484 polish:
   symmetric 40px-wide inputs, centered text, --radius-md, calm focus. */
.fz-time-duration-input {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.fz-time-duration-input__h,
.fz-time-duration-input__m {
  font: inherit;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  width: 40px;
  min-height: 36px;
  padding: 6px 4px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
  text-align: center;
  transition: border-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard);
  /* Hide spinner buttons (Chrome/Safari + Firefox). */
  -moz-appearance: textfield;
}
.fz-time-duration-input__h:focus,
.fz-time-duration-input__m:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--ring-accent);
}
.fz-time-duration-input__h::-webkit-outer-spin-button,
.fz-time-duration-input__h::-webkit-inner-spin-button,
.fz-time-duration-input__m::-webkit-outer-spin-button,
.fz-time-duration-input__m::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.fz-time-duration-input__suf {
  font-size: 12px;
  color: var(--muted);
  margin-right: 6px;
}
.fz-time-duration-input__suf:last-child { margin-right: 0; }
/* DAYCYCLE-EDIT-INPUT-FIXES — the edit sheet's h/m fields must clear the 44px
   Apple HIG / WCAG 2.5.5 tap-target floor and stay >=16px font so iOS never
   tries (and, under user-scalable=no, fails) to zoom the field on focus. Scoped
   to the sheet so the denser main-page duration cell keeps its compact size. */
.fz-day-cycle-sheet__dur .fz-time-duration-input__h,
.fz-day-cycle-sheet__dur .fz-time-duration-input__m {
  min-width: 44px;
  min-height: 44px;
  font-size: 16px;
  padding: 6px;
}

/* Time-range cell — stacks vertically. Line 1 (primary): start → deadline
   in primary ink. Line 2 (secondary, only when the deadline shifted):
   "was XX:XX" in muted tone. v0.9.484 — replaces the wrapping-flex layout
   that crammed up to four pieces of data onto one line. */
.fz-day-cycle__dl {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  min-width: 0;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  text-align: right;
}
.fz-day-cycle__dl-primary {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.2;
}
.fz-day-cycle__dl-was {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  font-size: 12px;
  line-height: 1.2;
  color: var(--muted);
  opacity: 0.7;
}
.fz-day-cycle__dl-was-lbl {
  font-size: 10px;
  text-transform: lowercase;
  letter-spacing: 0.04em;
}
.fz-day-cycle__dl-time {
  color: var(--ink);
  font-weight: 500;
}
.fz-day-cycle__dl-start {
  color: var(--muted);
  font-weight: 500;
}
.fz-day-cycle__dl-arrow {
  color: var(--muted);
  font-weight: 400;
}
.fz-day-cycle__dl-orig {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.fz-day-cycle__dl-actual {
  font-weight: 600;
  color: var(--ink);
}

/* Slippage badge — calm pill in its own grid column so it aligns across
   rows in the phase. Late = soft indigo bg + indigo fg; early = soft
   green bg + green fg. Pill stays monotone (no gradient). v0.9.484. */
.fz-day-cycle__slip-col {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  min-height: 18px;
}
.fz-day-cycle__slip {
  font-size: 11px;
  font-weight: 600;
  padding: 3px 8px;
  border-radius: var(--radius-pill);
  font-variant-numeric: tabular-nums;
  background: var(--slip-late-bg);
  color: var(--slip-late-fg);
  white-space: nowrap;
}
.fz-day-cycle__slip--late {
  background: var(--slip-late-bg);
  color: var(--slip-late-fg);
}
.fz-day-cycle__slip--early {
  background: var(--slip-early-bg);
  color: var(--slip-early-fg);
}

/* Narrow phones (iPhone SE / older Android): tighten gap. */
@media (max-width: 380px) {
  /* column-gap, geen `gap`: de shorthand zou row-gap:0 overschrijven en de
     detaillade 8 px van haar rij duwen op precies de smalste schermen. */
  .fz-day-cycle__row { column-gap: 8px; padding: 10px 12px; }
  /* DAY-CYCLE-SUBTASK-VISUAL-NESTING — base gutter shrinks to 12px here, so
     +20px indent on small screens (vs +24px elsewhere). */
  .fz-day-cycle__row--child { padding-left: 32px; }
  /* De kolomruimte krimpt van 12 naar 8, dus de schuifbaan en de spookrij
     schuiven mee: 30 px tintvlak + 8 px = 38 px. */
  .fz-day-cycle__scale-band {
    flex-basis: calc(100% - 38px);
    width: calc(100% - 38px);
    margin-left: 38px;
  }
  .fz-day-cycle__ghost { gap: 8px; padding: 8px 12px; }
}

/* ═══════════════ DAYCYCLE-EDIT-FULLSCREEN-REBUILD (v0.9.1273) ═══════════════
   Full-screen /day-cycle/edit editor. One scrolled list of rows w/ persistent
   grab handles + immediate-drag reorder, inline-editable names, a duration pill
   that opens the single-task bottom sheet, sticky footer (primary add + running
   total), an undo snackbar on delete + a retry banner on failed save. Tokens
   only (Dag/Nacht safe); accent surfaces use --on-accent; ≥44px tap targets;
   respects env(safe-area-inset-bottom). */
.fz-dce {
  display: flex;
  flex-direction: column;
  min-height: calc(100dvh - env(safe-area-inset-top) - env(safe-area-inset-bottom));
  color: var(--ink);
}
.fz-dce__head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 4px 12px;
  position: sticky;
  top: 0;
  z-index: 4;
  background: var(--bg, var(--surface));
}
.fz-dce__back,
.fz-dce__more-btn {
  flex: 0 0 auto;
  background: transparent;
  border: 0;
  color: var(--ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard);
}
.fz-dce__back:hover,
.fz-dce__more-btn:hover { background: var(--line-2); }
.fz-dce__back svg,
.fz-dce__more-btn svg { width: 20px; height: 20px; }
.fz-dce__titlewrap { flex: 1 1 auto; min-width: 0; }
.fz-dce__title {
  margin: 0;
  font-size: 20px;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
}
.fz-dce__saved {
  font-size: 12px;
  color: var(--muted);
  min-height: 15px;
  line-height: 15px;
}

.fz-dce__body {
  flex: 1 1 auto;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 4px 4px 16px;
}
.fz-dce__phase { display: flex; flex-direction: column; gap: 6px; }
.fz-dce__phase-lbl {
  margin: 0 0 2px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}
.fz-dce__list { display: flex; flex-direction: column; gap: 6px; }
.fz-dce__phase-empty {
  padding: 12px;
  font-size: 13px;
  color: var(--muted);
  text-align: center;
}
/* Eén grijze zin, geen blokkade: informatie over wat de meeste mensen volhouden,
   niet een app die beslist wat jij aankan. */
.fz-dce__phase-hint {
  margin-top: 6px;
  padding: 0 4px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--muted);
}
/* Ingeslepen stappen: een klein kopje onderaan, buiten de lijst maar binnen
   bereik — je kunt je vergist hebben. */
.fz-dce__ingrained-list { display: flex; flex-direction: column; gap: 6px; }
.fz-dce__ingrained-row {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 6px 12px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-md);
  background: var(--surface);
}
.fz-dce__ingrained-name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 14px;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fz-dce__ingrained-back {
  flex: 0 0 auto;
  min-height: 36px;
  padding: 6px 12px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.fz-dce__ingrained-back[disabled] { opacity: 0.55; cursor: default; }
.fz-dce__phase-add { display: flex; gap: 8px; margin-top: 4px; }
.fz-dce__phase-btn {
  flex: 1 1 auto;
  min-height: 40px;
  background: transparent;
  border: 1px dashed var(--line-2);
  border-radius: var(--radius-md);
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.fz-dce__phase-btn:hover { background: var(--line-2); color: var(--ink); }
/* DAGRITME-DE-EDITOR-KIEST-UIT-ZEVEN-TEGELS — waar drie knoppen de breedte
   deelden staat nu één '+'. Die mag NIET over de hele regel uitrekken: een
   knop van 100% breed leest als "volgende stap", en dit is een toevoeg-plus.
   DAGRITME-ONDERIN-STAAT-KLAAR — er staat nu een woord naast dat plusje ("Voeg
   toe aan avond"), want dit is sinds deze fase de ENIGE toevoeg-deur. Dus geen
   vierkantje van 48px meer maar inhoudsbreed, en de 20px-plus is een echt icoon
   geworden zodat de letters op de gewone 13px van de knop blijven staan. */
.fz-dce__phase-btn--one {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 0 14px;
  line-height: 1;
}
.fz-dce__phase-btn--one svg { width: 16px; height: 16px; flex: 0 0 auto; }

/* DAGRITME-SLAAP-IS-EEN-VAST-BLOK — the sleep phase has no list in the editor:
   no add buttons, no handle, no row menu. One sentence and one stepper, because
   the only thing you may decide about sleep is how long you want it to be. */
.fz-dce__sleep {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 56px;
  padding: 10px 12px;
  background: var(--day-cycle-sleep-bg);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-lg);
}
.fz-dce__sleep-lbl { flex: 1 1 auto; font-size: 15px; font-weight: 600; color: var(--ink); }
.fz-dce__sleep-step { flex: 0 0 auto; display: flex; gap: 6px; }
.fz-dce__sleep-btn {
  width: 44px;
  min-height: 44px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  color: var(--ink);
  font: inherit;
  font-size: 18px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
}
.fz-dce__sleep-btn:hover { background: var(--line-2); }

/* Row: swipe-wrapper (opaque row over parked Duplicate/Delete actions). */
.fz-dce__rowwrap {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-lg);
  transition: transform var(--dur-base) var(--ease-standard);
}
.fz-dce__rowwrap.is-dragging {
  overflow: visible;
  transition: none;
  z-index: 5;
}
.fz-dce__swipe-actions {
  position: absolute;
  inset: 0 0 0 auto;
  display: flex;
  align-items: stretch;
}
.fz-dce__swipe-btn {
  width: 66px;
  border: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--on-accent);
  cursor: pointer;
  pointer-events: none;
}
.fz-dce__swipe-btn svg { width: 20px; height: 20px; }
.fz-dce__swipe-btn--dup { background: var(--accent); color: var(--on-accent); }
.fz-dce__swipe-btn--del { background: var(--danger); color: var(--on-danger); }
.fz-dce__rowwrap.is-revealed .fz-dce__swipe-btn { pointer-events: auto; }

.fz-dce__row {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 52px;
  padding: 4px 6px;
  position: relative;
  z-index: 1;
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-lg);
  transition: transform var(--dur-base) var(--ease-standard);
}
.fz-dce__rowwrap.is-dragging .fz-dce__row {
  transform: scale(1.02);
  box-shadow: var(--elev-2);
}
.fz-dce__rowwrap--child { margin-left: 24px; }
@media (max-width: 380px) { .fz-dce__rowwrap--child { margin-left: 16px; } }

.fz-dce__handle {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  background: transparent;
  border: 0;
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: grab;
  touch-action: none;
  border-radius: var(--radius-md);
}
.fz-dce__handle:active { cursor: grabbing; }
.fz-dce__collapse {
  flex: 0 0 auto;
  width: 32px;
  height: 44px;
  background: transparent;
  border: 0;
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.fz-dce__collapse svg { width: 16px; height: 16px; transition: transform var(--dur-base) var(--ease-standard); }
.fz-dce__collapse.is-collapsed svg { transform: rotate(-90deg); }

.fz-dce__main { flex: 1 1 auto; min-width: 0; }
.fz-dce__name {
  width: 100%;
  min-height: 40px;
  background: transparent;
  border: 0;
  color: var(--ink);
  font: inherit;
  font-size: 15px;
  padding: 4px 2px;
}
.fz-dce__name:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-radius: var(--radius-md);
}
.fz-dce__metric-chip {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: 4px 12px;
  border-radius: var(--radius-pill);
  background: var(--line-2);
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
}
.fz-dce__field-err {
  font-size: 12px;
  color: var(--danger);
  padding: 2px 2px 0;
}

/* COACH-HET-HELE-DAGRITME-VAN-JE-KLANT-OP-EEN-PAGINA — het herkomst-label in de
   rij ("van jou" / "van Sanne"). Onder de naam en niet ernaast: naast de naam
   zou het op 390 px met het invoerveld om dezelfde ruimte vechten en de duur-pil
   van de rij af duwen. Stil van kleur — het is een feit, geen waarschuwing. */
.fz-dce__origin {
  display: block;
  padding: 2px 2px 0;
  font-size: 11px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--muted);
}
/* Een stap van een COLLEGA staat op slot: het naamveld is readonly. Zonder deze
   regel zie je een normaal invoerveld dat niets doet als je erin typt. */
.fz-dce__rowwrap--peer .fz-dce__name {
  background: var(--line-2);
  color: var(--muted);
  cursor: default;
}

.fz-dce__dur-pill {
  flex: 0 0 auto;
  min-height: 36px;
  min-width: 56px;
  padding: 6px 12px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line-2);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}
.fz-dce__dur-pill:hover { background: var(--line-2); }
.fz-dce__dur-derived {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 13px;
  color: var(--muted);
}
.fz-dce__derived-tag {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 2px 6px;
  border-radius: var(--radius-pill);
  background: var(--line-2);
  /* DAGRITME-ELKE-ZIN-ZEGT-WAT-JE-DOET — stond op --muted. Dat was goed genoeg
     voor het ene vlagwoord "afgeleid": je hoefde het niet te lezen, alleen te
     zien dat er iets stond. Deze chip draagt sinds deze fase een ZIN die je wél
     moet lezen ("valt binnen Douchen" — hij noemt de stap waar de tijd uit
     komt), en --muted op --line-2 haalt gemeten 4,08:1 op 10px: onder de 4,5:1
     die WCAG 1.4.3 voor kleine tekst vraagt. --ink-2 is het token voor
     secundaire tekst en haalt op dezelfde achtergrond 6,2:1 (licht) en 8,2:1
     (donker). Alleen de kleur: de vorm van de chip verandert niet. */
  color: var(--ink-2);
}
.fz-dce__more {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  background: transparent;
  border: 0;
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  border-radius: var(--radius-md);
}
.fz-dce__more:hover { background: var(--line-2); color: var(--ink); }
.fz-dce__more svg { width: 18px; height: 18px; }

/* Sticky footer: primary done + running total.
   DAGRITME-ONDERIN-STAAT-KLAAR — hier zat .fz-dce__add ("+ TOEVOEGEN"). Dezelfde
   plek, dezelfde maat, dezelfde accentkleur: alleen de belofte veranderde van
   "er komt iets bij" naar "ik ben klaar". Daarom is dit geen nieuwe maatvoering
   maar letterlijk de oude regel onder een nieuwe naam. */
.fz-dce__foot {
  position: sticky;
  bottom: 0;
  z-index: 4;
  display: flex;
  /* De balk draagt naast de knop en het totaal ook twee regels die een HELE
     regel willen (.fz-dce__retry en .fz-dce__coachnote, beide flex-basis 100%).
     Zonder flex-wrap persen die zich in dezelfde rij en krimpt de knop. */
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  padding: 10px 4px calc(10px + env(safe-area-inset-bottom, 0px));
  background: var(--bg, var(--surface));
  border-top: 1px solid var(--line-2);
}
.fz-dce__done {
  flex: 1 1 auto;
  min-height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  color: var(--on-accent);
}
.fz-dce__done svg { width: 18px; height: 18px; }
/* Terwijl de laatste save nog loopt zegt de knop "Opslaan…". Hij mag dan niet
   tweemaal af te drukken zijn, maar hij mag ook niet wégvallen: een knop die
   grijs wordt en van plaats schuift leest als een fout. */
.fz-dce__done[disabled] { opacity: 0.72; cursor: default; }
.fz-dce__total {
  flex: 0 0 auto;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}
/* COACH-HET-HELE-DAGRITME-VAN-JE-KLANT-OP-EEN-PAGINA — de regel onder de
   voetbalk in het coach-paneel. flex-basis 100% zodat hij de toevoeg-knop en de
   totaalteller niet uit elkaar duwt maar eronder valt, net als de slotzin in het
   stapblad. Alleen tekst: er valt niets te bevestigen. */
.fz-dce__coachnote {
  flex: 1 1 100%;
  margin: 0;
  font-size: 12px;
  line-height: 1.4;
  color: var(--muted);
}
/* DAGRITME-SLAAP-IS-EEN-VAST-BLOK — the quiet bottom danger zone
   (.fz-dce__danger / .fz-dce__delete, DAYCYCLE-DELETE-AND-REINTERVIEW) is gone.
   "Delete day cycle" now sits in the overflow menu top-right, styled by
   .fz-dce__page-menu-opt--danger. */

/* An author `display:flex` beats the UA `[hidden]` rule, so guard both the
   retry banner and the snackbar explicitly or they'd paint while hidden. */
.fz-dce__retry[hidden],
.fz-dce__snackbar[hidden] { display: none; }

/* Retry banner (failed PUT).
   DAGRITME-ONDERIN-STAAT-KLAAR — dit was een zwevende strook (position: fixed,
   bottom: 84px, z-index 120) die precies op de vaste onderbalk landde en de
   knop daar onbereikbaar maakte. Sinds die balk de ENIGE uitgang draagt woont
   de strook erin: eerste rij van de balk (order: -1 + een hele regel), dus
   altijd BOVEN Klaar en nooit erover. */
.fz-dce__retry {
  order: -1;
  flex: 1 1 100%;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  border-radius: var(--radius-lg);
  background: var(--danger);
  color: var(--on-danger);
  box-shadow: var(--elev-2);
}
.fz-dce__retry-msg { flex: 1 1 auto; font-size: 14px; }
.fz-dce__retry-btn {
  flex: 0 0 auto;
  min-height: 36px;
  padding: 6px 14px;
  border: 0;
  border-radius: var(--radius-md);
  background: var(--on-danger);
  color: var(--danger);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

/* Undo snackbar (every delete). */
.fz-dce__snackbar {
  position: fixed;
  left: 50%;
  transform: translateX(-50%) translateY(8px);
  bottom: calc(84px + env(safe-area-inset-bottom, 0px));
  z-index: 130;
  width: min(92%, 520px);
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  border-radius: var(--radius-lg);
  background: var(--ink);
  color: var(--surface);
  box-shadow: var(--elev-2);
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-standard), transform var(--dur-base) var(--ease-standard);
}
.fz-dce__snackbar.is-open { opacity: 1; transform: translateX(-50%) translateY(0); }
.fz-dce__snackbar-msg { flex: 1 1 auto; font-size: 14px; }
.fz-dce__snackbar-btn {
  flex: 0 0 auto;
  min-height: 36px;
  padding: 6px 14px;
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--accent);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

/* Popover menus (metric picker + page overflow). */
.fz-dce__metric-picker,
.fz-dce__page-menu {
  position: fixed;
  /* Beide menus hangen aan <body>, niet in de sheet die ze opent, dus hun
     z-index moet boven ELKE sheet liggen waaruit ze geopend kunnen worden:
     .fz-dce-tasksheet (150) én .mo-overlay (200) — de toevoeg-sheet van
     DAGRITME-DE-EDITOR-KIEST-UIT-ZEVEN-TEGELS is een .mo-overlay, en op 160
     viel de metingkiezer daar onder het blad: de knop leek dood. */
  z-index: 210;
  min-width: 200px;
  max-width: 280px;
  padding: 6px;
  border-radius: var(--radius-lg);
  background: var(--surface);
  border: 1px solid var(--line-2);
  box-shadow: var(--elev-3);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.fz-dce__metric-picker-hint {
  padding: 6px 10px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}
.fz-dce__metric-picker-empty { padding: 10px; font-size: 13px; color: var(--muted); }
.fz-dce__metric-picker-opt,
.fz-dce__page-menu-opt {
  min-height: 44px;
  padding: 10px 12px;
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
}
.fz-dce__metric-picker-opt:hover,
.fz-dce__page-menu-opt:hover { background: var(--line-2); }
/* DAGRITME-JE-EIGEN-GEWICHT-STAP-STAAT-ER-ELKE-DAG — een meting die al in je
   ritme staat blijft LEESBAAR in de lijst (je wil zien dát hij er is), maar is
   gedoofd en doet niets. Weghalen zou de vraag "waar is Gewicht?" opnieuw
   stellen, en dat is precies de verwarring die deze fase opruimt. */
.fz-dce__metric-picker-opt.is-held {
  cursor: default;
  color: var(--muted);
}
.fz-dce__metric-picker-opt.is-held:hover { background: transparent; }
.fz-dce__metric-picker-opt-sub {
  display: block;
  margin-top: 2px;
  font-size: 11px;
  color: var(--muted);
}
/* DAGRITME-SLAAP-IS-EEN-VAST-BLOK — destructive lives in this menu now; it is
   separated by a hairline so a mis-tap on "Restore" cannot land on it. */
.fz-dce__page-menu-opt--danger {
  margin-top: 4px;
  padding-top: 12px;
  border-top: 1px solid var(--line-2);
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  color: var(--danger);
}

/* ── Single-task bottom sheet (the one legitimate sheet use) ── */
/* In-place edit mode: the editor surface is hosted inside the day-cycle player
   body, so it must not claim its own viewport height or its own scroller —
   the controls above it have to scroll away with the list. */
.fz-dce--embedded { min-height: 0; }
.fz-dce--embedded .fz-dce__body {
  overflow-y: visible;
  flex: 0 0 auto;
  padding-left: 0;
  padding-right: 0;
}
.fz-dce--embedded .fz-dce__foot { padding-left: 0; padding-right: 0; }
.fz-day-cycle-page__head-slot {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.fz-day-cycle-page__head-slot:empty { display: none; }
/* DAGRITME-ONDERIN-STAAT-KLAAR — hier stonden .fz-day-cycle-page__edit.is-active
   en .fz-day-cycle-page__edit-label: het potlood dat in de bewerkstand een
   accent-pil met het woord "Klaar" werd. Die pil is weg (één Klaar, en wel de
   grote onderin), dus deze twee regels verfden niets meer. */

/* DAGRITME-DE-EDITOR-KIEST-UIT-ZEVEN-TEGELS — de toevoeg-sheet. Eén deur naar
   een blok: zeven tegels in drie kolommen, één voorbeeldregel eronder, één knop
   onderaan. Het blad hergebruikt de .mo-* sheet-basis (zelfde grip, zelfde
   opkomst, zelfde sluit-kruis als elk ander blad in de app), dus hier staat
   alleen wat ERIN zit — geen tweede sheet-mechaniek. */
.fz-dce-tiles .mo-body { display: flex; flex-direction: column; gap: 16px; }
.fz-dce-tiles__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}
.fz-dce-tiles__tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 96px;
  padding: 12px 6px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-lg);
  background: transparent;
  color: var(--ink);
  font: inherit;
  cursor: pointer;
}
.fz-dce-tiles__tile.is-on {
  border-color: var(--accent);
  background: var(--accent-subtle);
}
.fz-dce-tiles__tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.fz-dce-tiles__ico { display: flex; line-height: 0; }
.fz-dce-tiles__word { font-size: 13px; font-weight: 600; text-align: center; }
/* Gedimd MET een slotje, nooit weggehaald: een tegel die verdwijnt leest als
   "die zit niet meer in de app" in plaats van "dit blok heeft er al één". */
.fz-dce-tiles__tile.is-locked { opacity: 0.4; cursor: default; }
.fz-dce-tiles__lock { position: absolute; top: 6px; right: 6px; color: var(--muted); line-height: 0; }

.fz-dce-tiles__prev { display: flex; flex-direction: column; gap: 6px; }
.fz-dce-tiles__prev-lbl {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}
.fz-dce-tiles__row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 56px;
  padding: 10px 12px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-md);
  background: var(--surface);
}
.fz-dce-tiles__row-ico { flex: 0 0 auto; display: flex; line-height: 0; }
.fz-dce-tiles__row-mid {
  /* De recept- en schemakiezer hangen absoluut onder hun knop; zonder deze
     positioneringsbasis vallen ze terug op het blad zelf. */
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
/* DAGRITME-ELKE-ZIN-ZEGT-WAT-JE-DOET — 'Mooi moment' is de enige tegel die
   niets vraagt en niets meet, dus de enige die een voorbeeld nodig heeft van
   wat je er 's avonds mee doet. De 6px van de omhullende kolom is hier precies
   goed: de regel staat onder het naamveld waar hij bij hoort. */
.fz-dce-tiles__why {
  color: var(--ink-2);
  font-size: var(--text-caption);
  line-height: 1.35;
}
.fz-dce-tiles__name {
  width: 100%;
  min-height: 40px;
  padding: 8px 10px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-md);
  background: var(--bg, var(--surface));
  color: var(--ink);
  font: inherit;
  font-size: 15px;
}
.fz-dce-tiles__name:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.fz-dce-tiles__attach {
  align-self: flex-start;
  max-width: 100%;
  min-height: 40px;
  padding: 8px 12px;
  /* Streepjesrand = "hier moet nog iets in"; hij wordt dicht zodra je gekozen
     hebt, dezelfde taal als de bijlage-knoppen in het taakblad. */
  border: 1px dashed var(--line);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fz-dce-tiles__attach.is-set { border-style: solid; border-color: var(--accent); color: var(--ink); }
.fz-dce-tiles__attach--sub { font-size: 13px; font-weight: 500; }
/* De twee kiezers in dit blad klappen OMHOOG: .mo-body knipt zijn overflow af
   en de voorbeeldregel is het laatste wat erin staat — naar beneden zou de
   lijst half buiten het blad vallen. */
.fz-dce-tiles .fz-dce__recipe-picker,
.fz-dce-tiles .fz-dce__workout-picker {
  top: auto;
  bottom: calc(100% + 4px);
  max-height: 200px;
}
/* De regenboogbaan is hier een plaatje van wat je krijgt, geen invoer. */
.fz-dce-tiles__row .fz-day-cycle__scale-track { pointer-events: none; }
/* DAGRITME-DE-GEVOELSBALK-LACHT-MEE — de twee eindgezichtjes onder het
   voorbeeld, alleen als het onderwerp 'Stemming' is. Ze staan op dezelfde
   uiteinden als in de echte rij: verdrietig links, superblij rechts. */
.fz-day-cycle__scale-ends {
  display: flex;
  justify-content: space-between;
  margin-top: 6px;
  pointer-events: none;
}
.fz-day-cycle__scale-ends[hidden] { display: none; }
.fz-dce-tiles__dur {
  flex: 0 0 auto;
  min-height: 40px;
  padding: 8px 12px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.fz-dce-tiles__foot {
  flex: 0 0 auto;
  padding: 12px 16px;
  border-top: 1px solid var(--line-2);
}
.fz-dce-tiles__add { width: 100%; min-height: 48px; }
.fz-dce-tiles__add:disabled { opacity: 0.45; cursor: default; }

.fz-dce-tasksheet {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  z-index: 150;
}
.fz-dce-tasksheet__card {
  background: var(--surface);
  color: var(--ink);
  width: 100%;
  max-width: 560px;
  max-height: 92vh;
  border-top-left-radius: var(--radius-lg);
  border-top-right-radius: var(--radius-lg);
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 8px 16px calc(16px + env(safe-area-inset-bottom, 0px));
  gap: 8px;
}
.fz-dce-tasksheet__grabber {
  align-self: center;
  width: 40px;
  height: 4px;
  border-radius: var(--radius-sm);
  background: var(--line-2);
  margin: 4px 0 8px;
}
.fz-dce-tasksheet__head {
  display: flex;
  align-items: center;
  gap: 8px;
}
.fz-dce-tasksheet__title { margin: 0; font-size: 16px; font-weight: 700; flex: 1 1 auto; }
.fz-dce-tasksheet__label {
  flex: 1 1 auto;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
}
.fz-dce-tasksheet__close {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  background: transparent;
  border: 0;
  font-size: 24px;
  line-height: 1;
  color: var(--ink);
  cursor: pointer;
}
.fz-dce-tasksheet__name,
.fz-dce-tasksheet__note {
  width: 100%;
  min-height: 44px;
  padding: 10px 12px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-md);
  background: var(--bg, var(--surface));
  color: var(--ink);
  font: inherit;
  font-size: 15px;
}
.fz-dce-tasksheet__note { resize: vertical; min-height: 72px; }
.fz-dce-tasksheet__name:focus-visible,
.fz-dce-tasksheet__note:focus-visible,
.fz-dce-tasksheet__dur-field:focus-visible,
.fz-dce-tasksheet__subname:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.fz-dce-tasksheet__dur { display: flex; flex-direction: column; gap: 6px; }
.fz-dce-tasksheet__chips { display: flex; gap: 8px; flex-wrap: wrap; }
.fz-dce-tasksheet__chip {
  min-width: 48px;
  min-height: 40px;
  padding: 6px 14px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.fz-dce-tasksheet__chip.is-active {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}
.fz-dce-tasksheet__dur-field {
  width: 100%;
  min-height: 44px;
  padding: 10px 12px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-md);
  background: var(--bg, var(--surface));
  color: var(--ink);
  font: inherit;
  font-size: 15px;
}
.fz-dce-tasksheet__dur-read { font-size: 13px; color: var(--muted); }
/* DAGRITME-EEN-GEWOONTE-HEEFT-EEN-LOOPTIJD — de looptijd hergebruikt de
   duur-chips: hetzelfde blok, dezelfde maat, één regel eronder de datum. Twee
   verschillende chip-vormen in één blad zou suggereren dat het twee soorten
   keuzes zijn, en dat zijn het niet. */
.fz-dce-tasksheet__until { display: flex; flex-direction: column; gap: 6px; }
.fz-dce-tasksheet__until-read { font-size: 13px; color: var(--muted); min-height: 18px; }
.fz-dce-tasksheet__subs { display: flex; flex-direction: column; gap: 6px; }
.fz-dce-tasksheet__sublist { display: flex; flex-direction: column; gap: 6px; }
.fz-dce-tasksheet__subrow { display: flex; align-items: center; gap: 8px; }
.fz-dce-tasksheet__subname {
  flex: 1 1 auto;
  min-height: 44px;
  padding: 10px 12px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-md);
  background: var(--bg, var(--surface));
  color: var(--ink);
  font: inherit;
  font-size: 14px;
}
.fz-dce-tasksheet__subdel {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  background: transparent;
  border: 0;
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.fz-dce-tasksheet__subdel svg { width: 18px; height: 18px; }
.fz-dce-tasksheet__submetric {
  flex: 1 1 auto;
  min-height: 44px;
  padding: 10px 12px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-md);
  background: var(--surface-2, var(--surface));
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
  display: flex;
  align-items: center;
}
.fz-dce-tasksheet__addsubrow { display: flex; flex-wrap: wrap; gap: 8px; }
.fz-dce-tasksheet__addsub {
  align-self: flex-start;
  min-height: 40px;
  padding: 8px 14px;
  border: 1px dashed var(--line-2);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.fz-dce-tasksheet__acts {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 4px;
}
.fz-dce-tasksheet__act {
  flex: 1 1 auto;
  min-height: 44px;
  padding: 10px 12px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.fz-dce-tasksheet__act:hover { background: var(--line-2); }
.fz-dce-tasksheet__act--danger {
  color: var(--danger);
  border-color: var(--danger);
}
/* DAGRITME-DE-COACH-GEEFT-EEN-GEWOONTE-MEE — de zin die in de plaats van de
   verwijderknop komt. Bewust géén knopvorm: geen rand, geen minimale 44 px, geen
   cursor. Een uitgeschakelde knop nodigt uit tot tikken en beloont dat met
   niets; een zin belooft niets. flex-basis 100% zodat hij de rij knoppen niet
   in de lengte uitrekt maar er netjes onder valt. */
.fz-dce-tasksheet__locked {
  flex: 1 1 100%;
  margin: 0;
  font-size: 13px;
  line-height: 1.4;
  color: var(--muted);
}

/* Edit-mode sheet — full-dimmed copy + history pattern
   (feedback_reuse_sheet_ui_not_minimal_widgets). */
.fz-day-cycle-sheet {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  z-index: 100;
}
.fz-day-cycle-sheet__card {
  background: var(--surface);
  color: var(--ink);
  width: 100%;
  max-width: 560px;
  max-height: 88vh;
  border-top-left-radius: var(--radius-lg);
  border-top-right-radius: var(--radius-lg);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  position: relative; /* anchors the in-sheet discard confirm overlay */
}
.fz-day-cycle-sheet__head {
  display: flex;
  align-items: center;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line-2);
}
.fz-day-cycle-sheet__title {
  margin: 0;
  font-size: 15px;
  font-weight: 600;
  flex: 1 1 auto;
}
.fz-day-cycle-sheet__close {
  background: transparent;
  border: 0;
  font-size: 22px;
  line-height: 1;
  color: var(--ink);
  cursor: pointer;
  padding: 4px 8px;
}
.fz-day-cycle-sheet__body {
  overflow-y: auto;
  padding: 12px 16px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.fz-day-cycle-sheet__phase {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.fz-day-cycle-sheet__phase-lbl {
  margin: 0;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}
.fz-day-cycle-sheet__list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
/* DAY-CYCLE-TASK-EDITOR-DRAG-AND-SWIPE — each row sits in a positioned
   wrapper so a swipe-left can slide the (opaque) row over a Delete action
   parked at the right edge. The ≡ handle owns the long-press drag-reorder. */
.fz-day-cycle-sheet__rowwrap {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-md);
  transition: transform var(--dur-base) var(--ease-standard); /* sibling shift while another row drags */
}
.fz-day-cycle-sheet__rowwrap.is-dragging {
  overflow: visible; /* let the lifted row + its shadow escape the clip */
  transition: none;  /* the grabbed row tracks the finger, no easing */
  z-index: 5;
}
.fz-day-cycle-sheet__del {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: 88px;
  border: 0;
  border-radius: var(--radius-md);
  background: var(--danger);
  color: var(--on-danger);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  pointer-events: none; /* only tappable once the row is swiped open */
}
.fz-day-cycle-sheet__rowwrap.is-revealed .fz-day-cycle-sheet__del { pointer-events: auto; }
.fz-day-cycle-sheet__row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-areas:
    "handle name ctl"
    "handle notes dur";
  gap: 6px 8px;
  align-items: start;
  position: relative;
  z-index: 1;
  background: var(--surface); /* opaque so it hides the Delete action beneath */
  transition: transform var(--dur-base) var(--ease-standard); /* swipe snap-back */
}
.fz-day-cycle-sheet__rowwrap.is-dragging .fz-day-cycle-sheet__row {
  transform: scale(1.02);
  box-shadow: var(--elev-2);
}
.fz-day-cycle-sheet__handle {
  grid-area: handle;
  align-self: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  min-height: 30px;
  padding: 0;
  background: transparent;
  border: 0;
  color: var(--ink-2);
  cursor: grab;
  touch-action: none; /* the handle handles its own gesture */
}
.fz-day-cycle-sheet__handle:active { cursor: grabbing; }
.fz-day-cycle-sheet__handle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-md);
}
@media (prefers-reduced-motion: reduce) {
  /* Drag + swipe still work functionally; drop the lift-scale and the
     snap-back spring so motion-sensitive users get instant state changes. */
  .fz-day-cycle-sheet__rowwrap,
  .fz-day-cycle-sheet__row { transition: none; }
  .fz-day-cycle-sheet__rowwrap.is-dragging .fz-day-cycle-sheet__row {
    transform: none;
    box-shadow: var(--ring-accent);
  }
}
.fz-day-cycle-sheet__name {
  grid-area: name;
  font: inherit;
  font-size: 13px;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
  min-width: 0;
}
.fz-day-cycle-sheet__notes {
  grid-area: notes;
  font: inherit;
  font-size: 13px;
  padding: 5px 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
  resize: none;
  width: 100%;
  min-height: 52px;
}
.fz-day-cycle-sheet__notes:focus { outline: none; border-color: var(--accent); }
.fz-day-cycle-sheet__dur { grid-area: dur; align-self: center; }
.fz-day-cycle-sheet__ctl { grid-area: ctl; }
.fz-day-cycle-sheet__ctl {
  display: inline-flex;
  gap: 4px;
}
.fz-day-cycle-sheet__btn {
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 4px 8px;
  font-size: 13px;
  color: var(--ink);
  cursor: pointer;
  min-width: 28px;
}
.fz-day-cycle-sheet__btn:disabled { opacity: 0.4; cursor: default; }
.fz-day-cycle-sheet__add {
  align-self: flex-start;
  background: transparent;
  border: 1px dashed var(--line);
  border-radius: var(--radius-md);
  padding: 6px 12px;
  font-size: 12px;
  color: var(--ink-2);
  cursor: pointer;
}
.fz-day-cycle-sheet__foot {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 12px 16px;
  /* DAYCYCLE-EDIT-INPUT-FIXES — keep the footer above the home-indicator /
     keyboard inset so Save/Cancel stay reachable. */
  padding-bottom: calc(12px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line-2);
}
.fz-day-cycle-sheet__cancel,
.fz-day-cycle-sheet__save {
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  padding: 8px 14px;
  border-radius: var(--radius-md);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
.fz-day-cycle-sheet__save {
  background: var(--accent);
  color: var(--on-accent);
  border-color: transparent;
}
/* DAYCYCLE-EDIT-INPUT-FIXES — per-row visible delete signifier (swipe stays as
   the power-user shortcut). Danger-tinted icon button in the ctl area. */
.fz-day-cycle-sheet__btn--del {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 34px;
  min-height: 34px;
  padding: 4px;
  color: var(--danger);
  border-color: var(--line-2);
}
.fz-day-cycle-sheet__btn--del:active { background: var(--line-2); }
/* DAYCYCLE-EDIT-INPUT-FIXES — themed discard confirm (replaces window.confirm)
   overlaying the card when Cancel/close is hit with unsaved edits. */
.fz-day-cycle-sheet__confirm {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  z-index: 20;
}
.fz-day-cycle-sheet__confirm-box {
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-lg);
  padding: 18px;
  width: 100%;
  max-width: 320px;
}
.fz-day-cycle-sheet__confirm-msg {
  margin: 0 0 16px;
  font-size: 14px;
  font-weight: 600;
}
.fz-day-cycle-sheet__confirm-btns {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}
.fz-day-cycle-sheet__confirm-discard {
  background: var(--danger);
  color: var(--on-danger);
  border-color: transparent;
}

/* DAYCYCLE-METRIC-TASKS-AND-SUBTASKS — editor extras + per-row metric/
   subtask chrome. Tokens-only (no hex) so Dag/Nacht both stay legible. */
.fz-day-cycle-sheet__row--child { padding-left: 20px; border-left: 2px solid var(--line-2); margin-left: 4px; }
.fz-day-cycle-sheet__metric-chip {
  grid-area: name;
  display: inline-flex;
  align-items: center;
  padding: 6px 10px;
  border: 1px dashed var(--accent);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--accent);
  font-size: 13px;
  font-weight: 600;
}
.fz-day-cycle-sheet__add-row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.fz-day-cycle-sheet__add--metric {
  color: var(--accent);
  border-color: var(--accent);
  border-style: dashed;
}
.fz-day-cycle-sheet__btn--sub {
  color: var(--accent);
  border-color: var(--accent);
  font-size: 11px;
  padding: 2px 6px;
}
.fz-day-cycle-sheet__metric-picker {
  position: fixed;
  z-index: 1000;
  min-width: 200px;
  max-height: 60vh;
  overflow: auto;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  box-shadow: var(--elev-2);
}
.fz-day-cycle-sheet__metric-picker-hint,
.fz-day-cycle-sheet__metric-picker-empty {
  font-size: 11px;
  color: var(--muted);
  padding: 6px 8px;
}
.fz-day-cycle-sheet__metric-picker-opt {
  font: inherit;
  font-size: 13px;
  text-align: left;
  background: transparent;
  border: none;
  padding: 8px 10px;
  border-radius: var(--radius-md);
  cursor: pointer;
  color: var(--ink);
}
.fz-day-cycle-sheet__metric-picker-opt:hover { background: var(--accent-light); color: var(--accent); }

/* Page-side row chrome */
/* DAY-CYCLE-SUBTASK-VISUAL-NESTING — subtasks read as subordinate: indented
   +24px past the parent's 16px gutter, a 1px accent rail grouping them to the
   parent, smaller title + meta, and smaller h/m inputs. Parents untouched. */
.fz-day-cycle__row--child {
  padding-left: 40px;
  border-left: 1px solid var(--line);
  margin-left: 4px;
}
.fz-day-cycle__row--child .fz-day-cycle__name {
  font-size: 13px;
  line-height: 1.35;
}
.fz-day-cycle__row--child .fz-day-cycle__when-time { font-size: 12px; }
.fz-day-cycle__row--child .fz-day-cycle__ti-dur,
.fz-day-cycle__row--child .fz-day-cycle__note-line,
.fz-day-cycle__row--child .fz-day-cycle__ti-was { font-size: 10px; }
.fz-day-cycle__row--child .fz-time-duration-input__h,
.fz-day-cycle__row--child .fz-time-duration-input__m,
.fz-day-cycle__row--child .fz-time-duration-input__suf { font-size: 11px; }
.fz-day-cycle__name--metric { display: inline-flex; align-items: center; gap: 6px; }
.fz-day-cycle__metric-btn {
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  padding: 6px 12px;
  border-radius: var(--radius-md);
  border: none;
  background: var(--accent);
  color: var(--on-accent);
  cursor: pointer;
}
.fz-day-cycle__metric-btn--filled {
  background: var(--accent-light);
  color: var(--accent);
}
/* DIARY-SLEEP-ROW-SKIPONLY-AND-WEARABLE-AUTOFILL */
.fz-day-cycle__metric-btn--nosleep {
  background: transparent;
  color: var(--muted);
  text-decoration: line-through;
}
/* DAGRITME-EEN-RIJ-ZEGT-MET-ZIJN-VORM-WAT-JE-DOET — de rij draagt zijn naam nu
   zelf, dus dit knopje toont alleen nog de wáárde. Leeg is dan een streepje,
   net als bij een Cijfer-rij: een gevulde accentpil zonder getal erin beloofde
   dat er al iets stond. */
.fz-day-cycle__metric-btn--empty {
  background: transparent;
  color: var(--muted);
  font-weight: 700;
  padding: 6px 4px;
}
/* DAYCYCLE-RECIPE-TILE-LOGS-TO-NUTRITION — recipe tile in the player's name
   slot. Deliberately NOT .fz-day-cycle__metric-btn: a recipe has no
   filled/nosleep status states, and a long recipe name must be able to
   ellipsise inside a narrow (possibly indented) row. */
.fz-day-cycle__name--recipe { display: inline-flex; align-items: center; min-width: 0; max-width: 100%; }
.fz-day-cycle__recipe-btn {
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  padding: 6px 12px;
  border-radius: var(--radius-md);
  border: 1px solid var(--accent);
  background: transparent;
  color: var(--accent);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  max-width: 100%;
}
.fz-day-cycle__recipe-btn-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fz-day-cycle__recipe-btn--logged {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.fz-day-cycle__recipe-check { width: 14px; height: 14px; flex: 0 0 auto; }

.fz-day-cycle__recipesheet {
  position: fixed; inset: 0; z-index: 1200;
  background: rgba(0, 0, 0, 0.45);
  display: flex; align-items: flex-end; justify-content: center;
}
.fz-day-cycle__recipesheet-card {
  width: 100%; max-width: 560px;
  background: var(--surface);
  color: var(--ink);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  padding: 8px 16px calc(16px + env(safe-area-inset-bottom));
  max-height: 88vh; overflow-y: auto;
}
.fz-day-cycle__recipesheet-grab {
  width: 40px; height: 4px; border-radius: 2px;
  background: var(--line); margin: 4px auto 12px;
}
.fz-day-cycle__recipesheet-head { display: flex; align-items: center; gap: 8px; }
.fz-day-cycle__recipesheet-title { flex: 1; margin: 0; font-size: 17px; font-weight: 700; }
.fz-day-cycle__recipesheet-close {
  font: inherit; font-size: 22px; line-height: 1;
  min-width: 44px; min-height: 44px;
  background: none; border: none; color: var(--muted); cursor: pointer;
}
.fz-day-cycle__recipesheet-notes { margin: 10px 0; font-size: 13px; color: var(--ink-2); white-space: pre-wrap; }
.fz-day-cycle__recipesheet-items { margin: 10px 0; padding-left: 18px; }
.fz-day-cycle__recipesheet-item { font-size: 13px; margin-bottom: 4px; }
.fz-day-cycle__recipesheet-loading,
.fz-day-cycle__recipesheet-empty { padding: 16px 0; font-size: 13px; color: var(--muted); }
.fz-day-cycle__recipesheet-servings {
  display: flex; align-items: center; gap: 10px; margin: 12px 0;
}
.fz-day-cycle__recipesheet-servlabel { flex: 1; font-size: 13px; font-weight: 600; }
.fz-day-cycle__recipesheet-step {
  font: inherit; font-size: 18px; line-height: 1;
  min-width: 44px; min-height: 44px;
  border: 1px solid var(--line); border-radius: var(--radius-md);
  background: transparent; color: var(--ink); cursor: pointer;
}
.fz-day-cycle__recipesheet-servval { min-width: 44px; text-align: center; font-weight: 700; }
.fz-day-cycle__recipesheet-log {
  width: 100%; min-height: 48px;
  font: inherit; font-size: 15px; font-weight: 700;
  border: none; border-radius: var(--radius-md);
  background: var(--accent); color: var(--on-accent); cursor: pointer;
}
.fz-day-cycle__recipesheet-log:disabled { opacity: 0.5; cursor: default; }

/* undo bar — the player has no snackbar of its own (the editor's is scoped
   to .fz-dce__). */
.fz-day-cycle__undobar {
  position: fixed; z-index: 1300;
  left: 16px; right: 16px; bottom: calc(80px + env(safe-area-inset-bottom));
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px;
  border-radius: var(--radius-md);
  background: var(--ink); color: var(--bg);
  font-size: 13px;
}
.fz-day-cycle__undobar-msg { flex: 1; }
.fz-day-cycle__undobar-btn {
  font: inherit; font-weight: 700;
  background: none; border: none; color: inherit;
  text-decoration: underline; cursor: pointer;
  min-height: 44px;
}

/* recipe attachment row + picker in the editor task sheet */
.fz-dce-tasksheet__recipe { display: flex; align-items: center; gap: 8px; position: relative; }
.fz-dce-tasksheet__recipe-btn {
  flex: 1; min-width: 0; text-align: left;
  font: inherit; font-size: 14px;
  min-height: 44px; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: var(--radius-md);
  background: transparent; color: var(--muted); cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fz-dce-tasksheet__recipe-btn.is-set { color: var(--ink); font-weight: 600; }
.fz-dce-tasksheet__recipe-clear {
  min-width: 44px; min-height: 44px;
  background: none; border: none; color: var(--muted); cursor: pointer;
}
.fz-dce__recipe-picker {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 20;
  max-height: 260px; overflow-y: auto;
  border: 1px solid var(--line); border-radius: var(--radius-md);
  background: var(--surface);
  box-shadow: var(--elev-2);
}

/* DAYCYCLE-WORKOUT-TILE-OPENS-GYM — workout attachment row + picker, and the
   player tile. The tile borrows the recipe tile's shape (same name slot, same
   ellipsis behaviour) but stays a plain outlined chip: opening the Gym tab is a
   navigation, so it has no logged/filled state to paint. */
.fz-day-cycle__name--workout { display: inline-flex; align-items: center; min-width: 0; max-width: 100%; }
.fz-day-cycle__workout-btn {
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  padding: 6px 12px;
  border-radius: var(--radius-md);
  border: 1px solid var(--accent);
  background: transparent;
  color: var(--accent);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  max-width: 100%;
}
.fz-day-cycle__workout-btn-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fz-day-cycle__workout-icon { width: 14px; height: 14px; flex: 0 0 auto; }

.fz-dce-tasksheet__workout { display: flex; align-items: center; gap: 8px; position: relative; }
.fz-dce-tasksheet__workout[hidden] { display: none; }
.fz-dce-tasksheet__workout-btn {
  flex: 1; min-width: 0; text-align: left;
  font: inherit; font-size: 14px;
  min-height: 44px; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: var(--radius-md);
  background: transparent; color: var(--muted); cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fz-dce-tasksheet__workout-btn.is-set { color: var(--ink); font-weight: 600; }
.fz-dce-tasksheet__workout-clear {
  min-width: 44px; min-height: 44px;
  background: none; border: none; color: var(--muted); cursor: pointer;
}
.fz-dce__workout-picker {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 20;
  /* The options are plain buttons — without an explicit column they flow
     inline and two schema names end up sharing a line. */
  display: flex; flex-direction: column; gap: 2px; padding: 6px;
  max-height: 260px; overflow-y: auto;
  border: 1px solid var(--line); border-radius: var(--radius-md);
  background: var(--surface);
  box-shadow: var(--elev-2);
}

.fz-day-cycle__metric-src {
  margin-left: 8px;
  font-size: 11px;
  font-weight: 500;
  color: var(--muted);
}
.fz-day-cycle__nosleep-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  flex: none;
}
.fz-day-cycle__nosleep-toggle svg {
  width: 20px;
  height: 20px;
}
.fz-day-cycle__nosleep-toggle--active {
  color: var(--accent);
}
.fz-day-cycle__metric-input {
  font: inherit;
  font-size: 14px;
  padding: 6px 8px;
  border: 1px solid var(--accent);
  border-radius: var(--radius-md);
  width: 88px;
  background: var(--surface);
  color: var(--ink);
}
.fz-day-cycle__metric-unit {
  font-size: 12px;
  color: var(--muted);
  margin-left: 4px;
}

/* ─────────────────────────────────────────────────────────────────────
   SETTINGS-PAGE-COMPREHENSIVE-OVERHAUL (v0.9.476)
   IA tiering — Trainer / Personal / Technical / Dev — with drill-in
   sub-pages at /settings/account + /settings/cardio (mirror /goals).
   Tokens only — both Dag (light + blue) + Nacht (dark + lime) presets.
───────────────────────────────────────────────────────────────────── */

/* Drill-in sub-page chrome (Goals/Account/Cardio share this pattern).
   v0.9.490 SETTINGS-PADDING-BLEED — top reduced 16→12px to match the main
   Settings page (.app-main provides the nav-bottom clearance). */
.set-page {
  padding: 12px 16px 16px;
  max-width: 720px;
  margin: 0 auto;
}
.set-page__header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}
.set-page__back {
  width: 36px;
  height: 36px;
  border-radius: var(--radius-md);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.set-page__back svg { width: 18px; height: 18px; }
.set-page__titles { flex: 1; }
.set-page__title {
  font-size: 18px;
  font-weight: 700;
  margin: 0;
  color: var(--ink);
}
.set-page__hint {
  font-size: 12px;
  color: var(--muted);
  margin: 6px 0 12px;
  line-height: 1.4;
}
.set-page__hint--inline { margin: 0 0 8px; }

/* Profile header — tappable drill-in to /settings/account. */
.set-profile--tappable {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 14px;
  text-align: left;
  cursor: pointer;
  color: var(--ink);
  font: inherit;
}
.set-profile--tappable:hover { background: var(--surface-2, var(--surface)); }
.set-profile__chev {
  margin-left: auto;
  color: var(--muted);
  font-size: 22px;
  line-height: 1;
}

/* Drill-in row pattern — used by Account, Cardio settings, Goals,
   Edit goals, etc. The chevron sits at the right; whole row taps. */
.set-row--nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  min-height: 44px;
  padding: 12px 14px;
  background: transparent;
  border: 0;
  text-align: left;
  font: inherit;
  color: var(--ink);
  cursor: pointer;
  text-decoration: none;
}
.set-row--nav:hover { background: var(--surface-2, var(--surface)); }
.set-row__chev {
  color: var(--muted);
  font-size: 20px;
  line-height: 1;
  margin-left: 12px;
}

/* Loading row placeholder for async sections (Diary metrics). */
.set-row--loading .set-row__label {
  color: var(--muted);
  font-style: italic;
}

/* Per-section Restore-defaults button — calm inline link tone. */
.set-reset-row {
  display: block;
  width: 100%;
  background: transparent;
  border: 0;
  border-top: 1px solid var(--line);
  padding: 12px 14px;
  text-align: left;
  font: inherit;
  font-size: 13px;
  color: var(--muted);
  cursor: pointer;
}
.set-reset-row:hover { color: var(--ink); }

/* Cardio drill-in page — Restore defaults large button at bottom. */
.set-reset-section {
  margin: 16px 0 24px;
  display: flex;
  justify-content: center;
}
.set-reset-section__btn {
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  padding: 10px 18px;
  border-radius: var(--radius-md);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}

/* Global Restore all defaults — danger row at the very bottom. */
.set-group--danger-soft .set-card {
  border-color: var(--danger, var(--line));
}
.set-row--danger-action {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  min-height: 44px;
  padding: 12px 14px;
  background: transparent;
  border: 0;
  text-align: left;
  font: inherit;
  cursor: pointer;
}
.set-row__label--danger { color: var(--danger, var(--ink)); font-weight: 600; }

/* Trainer section consolidated card. */
.set-group--trainer .set-card { border-color: var(--accent); }

/* Note-style hint underneath toggles (Diary metrics Mood line). */
.set-group__hint--note {
  margin-top: 12px;
  border-top: 1px solid var(--line);
  padding-top: 10px;
}

/* App-version value tappable (easter egg trigger). */
.set-row__value--tappable {
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

/* Developer section — hidden by default; the easter egg sets the body
   class which scopes the reveal (CSS-only fallback if JS misses). */
.set-group--dev .set-card { border-style: dashed; }

/* Account drill-in page — disclosure value preview + inline form bits. */
.set-disclose__value {
  margin-left: auto;
  color: var(--muted);
  font-size: 13px;
  padding-right: 8px;
}
.set-disclose__note {
  margin: 8px 0 0;
  font-size: 12px;
  color: var(--muted);
}
.set-account-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 10px 0;
}
.set-account-field__label {
  font-size: 12px;
  color: var(--muted);
  font-weight: 600;
}
.set-account-input {
  font: inherit;
  font-size: 14px;
  padding: 10px 12px;
  border-radius: var(--radius-md);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  min-height: 44px;
}
.set-account-input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Inline confirmation states (display name save, email/password save). */
.set-inline-status {
  font-size: 12px;
  color: var(--muted);
  margin-left: 8px;
  align-self: center;
}
.set-inline-status.is-ok { color: var(--ok, var(--accent)); }
.set-inline-status.is-err { color: var(--danger, var(--ink)); }
.set-inline-error {
  font-size: 13px;
  color: var(--danger, var(--ink));
  padding: 8px 10px;
  border-radius: var(--radius-md);
  background: var(--surface-2, var(--surface));
  margin-bottom: 8px;
}
.set-inline-confirm {
  font-size: 13px;
  color: var(--ok, var(--accent));
  padding: 0 2px;
  min-height: 0;
  transition: min-height var(--dur-base) var(--ease-standard);
}
.set-inline-confirm.is-on {
  min-height: 24px;
  padding: 6px 10px;
  background: var(--surface-2, var(--surface));
  border-radius: var(--radius-md);
  margin: 6px 0;
}

/* Page-level destructive action buttons on /settings/account. */
.set-page-action {
  width: 100%;
  min-height: 48px;
  font: inherit;
  font-weight: 600;
  border-radius: var(--radius-md);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
.set-page-action--danger {
  color: var(--danger, var(--ink));
  border-color: var(--danger, var(--line));
}

/* TESTIMONIAL-1-SCHEMA-KLANTFLOW — the client's recommendation panel on
   /settings/account. The quote block is deliberately styled as the finished
   article rather than as a form preview: it is what the coach's public page
   will show, so it must not read like a draft. Tokens only — both presets
   inherit, no hardcoded colors. */
.set-ct-area {
  width: 100%;
  min-height: 120px;
  resize: vertical;
  line-height: 1.45;
}
.set-ct-name {
  font: inherit;
  padding: 8px 14px;
  min-height: 40px;
  border-radius: var(--radius-md);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
.set-ct-name[aria-pressed="true"] {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}
.set-ct-quote {
  margin: 0;
  padding: var(--space-4) var(--space-5);
  border-left: 3px solid var(--accent);
  background: var(--surface-2, var(--surface));
  border-radius: var(--radius-md);
}
.set-ct-quote__body {
  margin: 0;
  color: var(--ink);
  line-height: 1.5;
  white-space: pre-wrap;
}
.set-ct-quote__name {
  margin-top: 6px;
  color: var(--section-label);
  font-size: 0.86rem;
}

/* TDEE-CALCULATOR-SETTINGS-AND-ONBOARDING — Settings 'Body & energy' card.
   The five inputs render as full-width labelled rows; the live TDEE preview
   line + the muted tracker-override note pin to the bottom of the card.
   Both Dag + Nacht presets inherit via tokens; no hardcoded colors. */
.set-be-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: var(--space-4) var(--space-5);
}
.set-be-field__label {
  font-size: 14px;
  color: var(--ink-2);
  font-weight: 500;
}
.set-be-field__input {
  width: 100%;
  min-height: 44px;
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 0 12px;
  box-sizing: border-box;
}
.set-be-field__input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.set-be-field__select {
  padding-right: 32px;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-2) 50%),
                    linear-gradient(135deg, var(--ink-2) 50%, transparent 50%);
  background-position: calc(100% - 18px) 50%, calc(100% - 13px) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  -webkit-appearance: none;
  appearance: none;
}
.set-be-field--radio {
  align-items: flex-start;
}
.set-be-radio-group {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.set-be-radio {
  font: inherit;
  font-weight: 500;
  min-height: 40px;
  padding: 0 18px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
.set-be-radio.is-on {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}
.set-be-radio:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.set-be__preview {
  margin: 0;
  padding: var(--space-4) var(--space-5) 6px;
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
}
.set-be__tracker-note {
  margin: 0;
  padding: 0 var(--space-5) var(--space-4);
  font-size: 13px;
  color: var(--muted);
}

/* TDEE-CALCULATOR-SETTINGS-AND-ONBOARDING — full-screen new-profile takeover.
   Mounts inside the existing .auth shell so it inherits the notch-aware
   ladder + max-width frame. Five field grid stacks 1-column on mobile; the
   primary "Save & continue" + ghost "Skip for now" buttons sit at the foot. */
.onboarding__grid {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-bottom: var(--space-6);
}
.onboarding__field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.onboarding__label {
  font-size: 14px;
  color: var(--ink-2);
  font-weight: 500;
}
.onboarding__input {
  width: 100%;
  min-height: 44px;
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 0 12px;
  box-sizing: border-box;
}
.onboarding__input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.onboarding__select {
  padding-right: 32px;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-2) 50%),
                    linear-gradient(135deg, var(--ink-2) 50%, transparent 50%);
  background-position: calc(100% - 18px) 50%, calc(100% - 13px) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  -webkit-appearance: none;
  appearance: none;
}
.onboarding__radio-group {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.intake-goal__explainer {
  margin-top: var(--space-2);
  font-size: 0.9em;
  color: var(--muted);
  line-height: 1.4;
  min-height: 1.4em;
}
.onboarding__radio {
  font: inherit;
  font-weight: 500;
  min-height: 40px;
  padding: 0 18px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
.onboarding__radio.is-on {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}
.onboarding__radio:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* ONBOARDING-THREE-STEP-PROFILE — three panels, one visible at a time. */
.onboarding__dots {
  display: flex;
  gap: 8px;
  justify-content: center;
  margin-bottom: var(--space-2);
}
.onboarding__dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--line);
}
.onboarding__dot.is-on {
  background: var(--accent);
}
.onboarding__step-label {
  text-align: center;
  font-size: 13px;
  color: var(--muted);
  margin: 0 0 var(--space-4);
}
.onboarding__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 12px;
}
.onboarding__summary-text {
  color: var(--ink);
}
.onboarding__link {
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--accent);
  background: none;
  border: 0;
  padding: 0;
  text-decoration: underline;
  cursor: pointer;
}
.onboarding__override {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: var(--space-3);
}
/* INTAKE-DIETARY-PREFERENCES-AND-USER-AI-CONTEXT — Voorkeuren block. Generic,
   shared by the onboarding form and the Settings Body & energy card. */
.diet-subhead {
  margin: var(--space-3) 0 var(--space-1);
  font-size: 0.95em;
  font-weight: 600;
  color: var(--ink);
}
.diet-help {
  margin: 0 0 var(--space-1);
  font-size: 0.85em;
  color: var(--muted);
}
.diet-chips {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.diet-chip {
  font: inherit;
  font-weight: 500;
  min-height: 36px;
  padding: 0 14px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
.diet-chip.is-on {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}
.diet-chip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.diet-dislike-chips:empty {
  display: none;
}
.diet-dislike-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--ink);
  font-size: 0.9em;
}
.diet-dislike-chip__rm {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--muted);
  font-size: 1.15em;
  line-height: 1;
  padding: 0;
}
.onboarding__actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.onboarding--stepped .onboarding__actions {
  flex-direction: row;
}
.onboarding--stepped > .onboarding__btn--muted {
  margin-top: 10px;
}
.onboarding__btn {
  width: 100%;
  min-height: 48px;
}
.onboarding__btn--muted {
  background: transparent;
  color: var(--muted);
}

/* SEQUENCER-V2-AUTOREGULATION-SIGNAL (v0.9.503) — bell + pop-up +
   alertstaat panel. Mounted at the parent-app level above the Strength
   iframe. Token-driven; the bell colour follows --accent-orange (Dag +
   Nacht both define this token). The pulse uses a pseudo-element with
   opacity + scale transitions so no rgba hex literals are needed. */
.fz-autoreg-bellrow {
  display: flex; justify-content: flex-end; align-items: center;
  padding: var(--space-2) var(--space-3) 0;
}
.fz-autoreg-bell {
  position: relative;
  background: transparent; border: 0;
  padding: 6px; color: var(--ink);
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 36px; min-height: 36px;
  border-radius: var(--radius-md); cursor: pointer;
}
.fz-autoreg-bell svg { width: 22px; height: 22px; }
.fz-autoreg-bell.is-active { color: var(--accent-orange); }
.fz-autoreg-bell.is-active::after {
  content: ''; position: absolute; top: 8px; right: 8px;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--accent-orange);
  opacity: 1;
  animation: fzAutoregPulse var(--dur-pulse) var(--ease-standard) infinite;
}
@keyframes fzAutoregPulse {
  0%   { transform: scale(1);   opacity: 0.85; }
  70%  { transform: scale(2.6); opacity: 0;    }
  100% { transform: scale(2.6); opacity: 0;    }
}
@media (prefers-reduced-motion: reduce) {
  .fz-autoreg-bell.is-active::after { animation: none; }
}

.fz-autoreg-overlay {
  position: fixed; inset: 0; z-index: 9300;
  background: var(--surface-muted);
  display: flex; align-items: flex-end; justify-content: center;
  opacity: 0; transition: opacity var(--dur-base) var(--ease-standard);
}
.fz-autoreg-overlay.is-open { opacity: 1; }
.fz-autoreg-overlay > * {
  background: var(--surface);
  width: 100%; max-width: 520px;
  border-top-left-radius: var(--radius-lg); border-top-right-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-4) var(--space-5);
  box-shadow: var(--elev-up);
  transform: translateY(20px);
  transition: transform var(--dur-base) var(--ease-standard);
  max-height: 90vh; overflow-y: auto;
}
.fz-autoreg-overlay.is-open > * { transform: translateY(0); }

.fz-autoreg-sheet__title,
.fz-alertstaat__title {
  margin: 0 0 var(--space-1);
  font-size: 18px; font-weight: 700; color: var(--ink);
}
.fz-autoreg-sheet__lede,
.fz-alertstaat__lede {
  margin: 0 0 var(--space-4); color: var(--muted); font-size: 13px;
}
.fz-autoreg-row { margin-bottom: var(--space-4); }
.fz-autoreg-row__label {
  display: flex; justify-content: space-between; align-items: center;
  font-size: 13px; font-weight: 600; color: var(--ink-2);
  margin-bottom: var(--space-1);
}
.fz-autoreg-row__value { color: var(--ink); font-variant-numeric: tabular-nums; }
.fz-autoreg-row__slider {
  width: 100%; -webkit-appearance: none; appearance: none;
  height: 6px; border-radius: var(--radius-pill); background: var(--line);
  outline: none;
}
.fz-autoreg-row__slider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--surface);
  cursor: pointer;
}
.fz-autoreg-row__slider::-moz-range-thumb {
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--surface);
  cursor: pointer;
}
.fz-autoreg-row__textarea {
  width: 100%; min-height: 64px; resize: vertical;
  background: var(--surface-bg); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3); font: inherit;
}
.fz-autoreg-actions {
  display: flex; gap: var(--space-2); margin-top: var(--space-3);
}
.fz-autoreg-btn {
  flex: 1 1 0; padding: 12px 14px; font-size: 15px; font-weight: 600;
  border-radius: var(--radius-md); cursor: pointer;
  border: 1px solid var(--line);
  background: var(--surface); color: var(--ink);
}
.fz-autoreg-btn--primary {
  background: var(--accent); color: var(--on-accent); border-color: var(--accent);
}
.fz-autoreg-btn--close {
  margin-top: var(--space-3);
  width: 100%;
}
/* DELOAD-IK-VOEL-ME-WEER-GOED — "Het gaat weer goed met me" is a sentence,
   not a word; it gets its own full-width line instead of a squeezed third
   column next to "Niet nu" and "Start deloadweek". */
.fz-autoreg-actions { flex-wrap: wrap; }
.fz-autoreg-btn--fine {
  flex: 1 1 100%;
  width: 100%;
  margin-top: var(--space-2);
}

.fz-alertstaat__section-h {
  font-size: 11px; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--muted);
  margin: var(--space-4) 0 var(--space-2);
}
.fz-alertstaat__rows {
  border: 1px solid var(--line); border-radius: var(--radius-md);
  overflow: hidden; background: var(--surface);
}
.fz-alertstaat__row {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--line);
}
.fz-alertstaat__row:last-child { border-bottom: 0; }
.fz-alertstaat__row-name { font-weight: 600; font-size: 14px; color: var(--ink); }
.fz-alertstaat__row-count {
  font-size: 13px; color: var(--muted); font-variant-numeric: tabular-nums;
}
.fz-alertstaat__row.is-hot .fz-alertstaat__row-count {
  color: var(--accent-orange); font-weight: 600;
}
.fz-alertstaat__row.is-force .fz-alertstaat__row-count {
  color: var(--danger); font-weight: 700;
}
.fz-alertstaat__trend {
  display: flex; align-items: center; gap: var(--space-2);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-md); padding: var(--space-3) var(--space-4);
  font-size: 14px; color: var(--ink-2);
}
.fz-alertstaat__trend strong { color: var(--ink); font-weight: 700; }
.fz-alertstaat__cta {
  margin-top: var(--space-4); width: 100%;
  padding: 14px; font-size: 15px; font-weight: 700; border-radius: var(--radius-md);
  background: var(--accent-orange); color: var(--on-accent); border: 0; cursor: pointer;
}
.fz-alertstaat__cta--stub {
  background: transparent; color: var(--accent-orange);
  border: 1px dashed var(--accent-orange);
}
.fz-alertstaat__cta-sub {
  display: block; font-size: 11px; font-weight: 600; margin-top: 2px;
  letter-spacing: 0.04em; text-transform: uppercase;
}
.fz-alertstaat__cta.is-pressed { opacity: 0.6; }

/* SEQUENCER-V3-DELOAD-WEEK-OVERLAY (v0.9.504) — chrome rendered above
   the Strength iframe (and on the sequencer Plan-paneel) while an
   active deload-week covers today. Token-driven; the orange tokens
   (--accent-orange / --accent-orange-light) were added with the Periodisering paneel and are
   present in both the Nacht :root and the [data-theme-preset=
   "blue-day"] Dag override so nothing here needs a theme-conditional
   rule. Hex literals are absent on purpose — css-hex-lint forbids
   them outside :root. */
.fz-deload-eyebrow {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 8px 12px;
  background: var(--accent-orange-light);
  color: var(--accent-orange);
  font-size: 12px; font-weight: 700;
  letter-spacing: 0.04em; text-transform: uppercase;
}
.fz-deload-eyebrow svg { width: 14px; height: 14px; }

.fz-deload-cancel-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px;
  background: var(--accent-orange);
  color: var(--on-accent);
  border: 0; border-radius: var(--radius-pill);
  font-size: 12px; font-weight: 700;
  letter-spacing: 0.02em; cursor: pointer;
}
.fz-deload-cancel-btn:disabled { opacity: 0.6; cursor: default; }
.fz-deload-cancel-btn svg { width: 12px; height: 12px; }

.fz-deload-chip {
  display: inline-flex; align-items: center;
  padding: 2px 7px;
  background: var(--accent-orange-light);
  color: var(--accent-orange);
  border-radius: var(--radius-pill);
  font-size: 10px; font-weight: 700;
  letter-spacing: 0.04em; text-transform: uppercase;
  margin-left: 4px;
}

/* Forced-deload modal — non-dismissable, blocks the Strength surface
   until the user starts the deload. Higher z-index than the bell-
   panel overlay (9300) so a stray bell tap can't surface a panel
   above it. */
.fz-forced-deload-overlay {
  z-index: 9400;
}
.fz-forced-deload-sheet {
  border-top-left-radius: var(--radius-lg); border-top-right-radius: var(--radius-lg);
}

/* NAV-TAB-ORDER-EDITABLE-V1 (v0.9.510) — Settings → Navigatie card.
   Vertical list of every known tab; drag-handle on the right OR a
   350ms long-press anywhere on the row picks up the drag. Visual
   layout mirrors a .set-row so the per-tab affordance feels native
   to the rest of the Settings surface, but the row itself is
   absolutely-positioned-friendly (we translate during drag) so the
   .set-card > * + *::before divider rule still draws between rows. */
.set-navorder {
  display: flex;
  flex-direction: column;
  padding: 4px 0;
  touch-action: pan-y;
}
.set-navorder__row {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 56px;
  padding: 12px var(--space-4);
  background: var(--surface);
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
  transition: transform var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard);
  will-change: transform;
}
.set-navorder__row.is-dragging {
  cursor: grabbing;
  z-index: 2;
  box-shadow: var(--elev-2);
  transition: none;
}
body.navorder-dragging { cursor: grabbing; }
.set-navorder__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px; height: 32px;
  color: var(--ink);
  flex: 0 0 32px;
}
.set-navorder__icon svg { width: 22px; height: 22px; }
/* SETTINGS-TAB-ORDER-LIST-POLISH (v0.9.853) — de photographic nav-icons
   (`.tabbar__btn__photo`) worden in de bottombar 64px + scale + grayscale-
   inactive gerenderd; in deze Settings-list-context is er geen active/inactive
   state, dus context-specifieke override: 32px, vol kleur, geen transform. */
.set-navorder__icon .tabbar__btn__photo {
  width: 32px;
  height: 32px;
  object-fit: contain;
  filter: none;
  transform: none;
}
/* SETTINGS-TAB-ORDER-I18N-AND-RESTORE-DEDUPE — the Hub row uses the emblem
   avatar (`.tabbar__btn__avatar`), which is 64px + grayscale in the bottombar.
   Normalise it to the same 32px, full-colour box as the four photo-icons so all
   five list rows align on one baseline. */
.set-navorder__icon .tabbar__btn__avatar {
  width: 32px;
  height: 32px;
  object-fit: contain;
  filter: none;
  transform: none;
  background: transparent;
  padding: 0;
}
.set-navorder__label {
  color: var(--ink);
  font-size: 15px;
  flex: 1 1 auto;
}
.set-navorder__handle {
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  align-items: stretch;
  gap: 4px;
  width: 28px; height: 28px;
  padding: 6px 4px;
  border-radius: var(--radius-md);
  cursor: grab;
  flex: 0 0 auto;
  touch-action: none;
}
.set-navorder__handle:active { cursor: grabbing; background: var(--surface-muted); }
.set-navorder__bar {
  display: block;
  height: 2px;
  border-radius: var(--radius-sm);
  background: var(--section-label);
}
.set-navorder__reset {
  display: block;
  width: 100%;
  background: none;
  border: 0;
  padding: 12px var(--space-4);
  text-align: left;
  color: var(--accent);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
}
.set-navorder__reset:hover { color: var(--accent); text-decoration: underline; }

/* ──────── NUTRITION-MICRONUTRIENTS (migration 0098) ────────
   Token-only paint so Dag (light/blauw) and Nacht (dark/lijmgroen) inherit
   without overrides. No emoji glyphs; the disclosure indicator is a CSS
   triangle replacing the default summary marker. Approved mocks:
   _MOCK_nutrition_micronutrients_edit_v1_dag.html (collapsible edit panel)
   _MOCK_nutrition_micronutrients_display_v1_dag.html (read-only display block) */

/* Collapsible edit panel on the food-edit / manual-entry sheets. */
.fz-micro {
  margin: var(--space-2) 0 var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg);
  overflow: hidden;
}
.fz-micro > summary::-webkit-details-marker { display: none; }
.fz-micro > summary { list-style: none; }
.fz-micro__head {
  display: flex; align-items: center; gap: var(--space-2);
  padding: 12px 14px;
  cursor: pointer;
  color: var(--ink);
  font-family: inherit;
}
.fz-micro__head:hover { background: var(--surface-muted); }
.fz-micro__caret {
  width: 10px; height: 10px;
  border-right: 2px solid var(--ink-2);
  border-bottom: 2px solid var(--ink-2);
  transform: rotate(-45deg); transition: transform var(--dur-base) var(--ease-standard);
  margin-right: 2px;
}
.fz-micro[open] .fz-micro__caret { transform: rotate(45deg); }
.fz-micro__title { flex: 1 1 auto; font-size: 14px; font-weight: 600; }
.fz-micro__sub {
  font-size: 11px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--muted);
}
.fz-micro__body {
  padding: 4px 14px 14px;
  background: var(--surface);
  border-top: 1px solid var(--line);
}
.fz-micro__group-h {
  font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted);
  margin: 14px 0 8px;
}
.fz-micro__group-h:first-child { margin-top: 8px; }
.fz-micro__grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
}
.fz-micro__fld { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.fz-micro__fld label {
  font-size: 11px; font-weight: 600; color: var(--ink-2);
  display: flex; justify-content: space-between; align-items: baseline;
}
.fz-micro__fld label .u {
  font-size: 10px; font-weight: 500; color: var(--muted);
}
.fz-micro__fld input {
  border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface);
  padding: 8px 10px; font-size: 14px; color: var(--ink); font-family: inherit;
  min-width: 0; box-sizing: border-box;
}
.fz-micro__fld input:focus { outline: none; border-color: var(--accent); }

/* Read-only display block on the DB-sourced food scaler sheet. */
.fz-micro-disp { margin-top: var(--space-3); }
.fz-micro-disp__h {
  font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted);
  margin: var(--space-3) 0 6px;
}
.fz-micro-disp__h:first-child { margin-top: 0; }
.fz-micro-disp__rows {
  display: grid; grid-template-columns: 1fr 1fr; gap: 6px 18px;
}
.fz-micro-disp__row {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: 6px 0;
  border-bottom: 1px dashed var(--line-2);
  font-size: 13px;
  min-width: 0;
}
.fz-micro-disp__row .k { color: var(--ink-2); }
.fz-micro-disp__row .v { font-weight: 600; color: var(--ink); white-space: nowrap; }
.fz-micro-disp__row .v .u {
  font-weight: 500; color: var(--muted); margin-left: 2px; font-size: 11px;
}
.fz-micro-disp__row.is-null .v { color: var(--muted); font-weight: 500; }
.fz-micro-disp__foot {
  margin-top: 10px; font-size: 11px; color: var(--muted); font-style: italic;
}

/* GOAL-DIRECTION-CONFIG (v0.9.546) — /settings/goals sub-page. Mirrors the
   /settings/cardio chrome (set-page__header + set-group + set-card) and
   builds two new patterns on top: a 4-pill mode picker and per-metric rows
   with a 3-state direction toggle + 1 or 2 value inputs. Token-driven, no
   hex literals (css-hex-lint clean) and accent buttons pair --accent with
   --on-accent so dark + bright accents stay legible (lint rule g). */
.gd-mode-pills {
  display: flex; flex-wrap: wrap; gap: 6px;
  padding: var(--space-2) var(--space-4) var(--space-4);
}
.gd-mode-pill {
  flex: 1 1 calc(50% - 6px); min-width: 0;
  padding: 10px 12px;
  background: var(--surface-muted);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  font: inherit; font-size: 13px; font-weight: 600;
  cursor: pointer;
  text-align: center;
  transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
}
.gd-mode-pill:hover { border-color: var(--line-2); }
.gd-mode-pill.is-on {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}
.gd-mode-pill.is-disabled {
  opacity: 0.55; cursor: not-allowed;
}
.gd-mode-pill:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.gd-coach-lock-note {
  margin: var(--space-2) var(--space-4) var(--space-3);
  font-size: 12px; color: var(--muted); line-height: 1.4;
}

.gd-metric-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
  padding: 12px var(--space-4);
}
.gd-metric-row.is-locked { opacity: 0.55; }
.gd-metric-row__head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 8px;
}
.gd-metric-row__label {
  font-size: 14px; font-weight: 600; color: var(--ink);
}
.gd-metric-row__unit {
  font-size: 11px; color: var(--muted); letter-spacing: 0.02em;
  margin-left: auto;
}

/* INTAKE-AND-SETTINGS-MACROS auto-calculate — per-metric reset-to-auto icon
   (recomputes one macro from the profile) + the page-level "calculated" hint. */
.gd-metric-reset {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0; margin-left: 8px;
  border: 1px solid var(--line, var(--border)); border-radius: 50%;
  background: var(--surface); color: var(--muted); cursor: pointer;
  flex: 0 0 auto;
}
.gd-metric-reset svg { width: 14px; height: 14px; }
.gd-metric-reset:hover { color: var(--accent); border-color: var(--accent); }
.gd-metric-reset:active { transform: scale(0.94); }
.gd-autocalc-hint {
  margin: var(--space-2) var(--space-4) var(--space-3);
  font-size: 12px; color: var(--accent); line-height: 1.4;
}

/* GOAL-METRIC-PER-ELEMENT-TOGGLE — direction pill + on/off switch on one row,
   switch sits to the RIGHT of the pill. When the metric is OFF the row gets
   .is-metric-off, which dims the pill + value inputs (but keeps them editable
   — off only means "don't count toward the day score"). Token-only. */
.gd-dir-row {
  display: flex; align-items: center; gap: 10px;
}
.gd-dir-row .gd-dir-toggle { flex: 1 1 auto; min-width: 0; }
.gd-metric-onoff {
  flex: 0 0 auto;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  max-width: 78px;
}
.gd-metric-onoff__cap {
  font-size: 10px; line-height: 1.2; color: var(--muted);
  text-align: center;
}
.gd-metric-row.is-metric-off .gd-dir-toggle,
.gd-metric-row.is-metric-off .gd-value-input { opacity: 0.45; }
.gd-metric-toggle-hint {
  margin: 0 var(--space-4) var(--space-2);
  font-size: 12px; color: var(--muted); line-height: 1.4;
}

.gd-dir-toggle {
  display: flex; gap: 6px;
}
.gd-dir-toggle__tile {
  flex: 1 1 0; min-width: 0;
  padding: 8px 10px;
  background: var(--surface-muted);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  font: inherit; font-size: 12px; font-weight: 500;
  cursor: pointer;
  text-align: center;
  transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
}
.gd-dir-toggle__tile:hover { border-color: var(--line-2); }
.gd-dir-toggle__tile.is-on {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}
.gd-dir-toggle__tile[disabled] { cursor: not-allowed; opacity: 0.6; }
.gd-dir-toggle__tile:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.gd-value-input {
  display: flex; align-items: center; gap: 8px;
}
.gd-value-input__field {
  flex: 1 1 0; min-width: 0;
  padding: 10px 12px;
  background: var(--surface-muted);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  font: inherit; font-size: 14px;
  -webkit-appearance: none; appearance: none;
}
.gd-value-input__field:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}
.gd-value-input__field[disabled] { cursor: not-allowed; opacity: 0.6; }
.gd-value-input__sep {
  color: var(--muted); font-size: 14px; padding: 0 2px;
}

/* ─── INTAKE-SOURCE-OF-TRUTH-AND-SIGNUP-MIN (v0.9.559) ─────────────────────
   Single canonical Intake card on the Settings landing. 4 collapsible
   sub-sections (A: Body & energy, B: Nutrition targets, C: Behavioral
   targets, D: Fitness tracker) under a "Profile X% complete" progress
   header. All token-driven; no hard-coded hex (css-hex-lint enforced).
   Lives at the bottom of app.css so it overrides any earlier .set-card
   rule that would otherwise repaint a nested .set-card inside a sub-section
   body (sub-A + sub-D mount full standalone sections that carry their own
   .set-card child). */
.set-group--intake { /* spacing inherits from .set-group */ }
.intake-card {
  /* base inherits from .set-card (background, padding, divider rule) */
}
.intake-card__head {
  padding: var(--space-3) var(--space-4) var(--space-2) var(--space-4);
}
.intake-card__head-title {
  margin: 0 0 var(--space-1) 0;
  color: var(--ink);
  font-weight: 600;
  font-size: 15px;
}
.intake-card__progress {
  margin: var(--space-2) 0 var(--space-1) 0;
  color: var(--accent);
  font-weight: 600;
  font-size: 14px;
  font-variant-numeric: tabular-nums;
}
.intake-card__pips {
  margin: 0;
  color: var(--muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  line-height: 1.5;
}
/* Collapsible sub-section — uses <details>/<summary>. Visual divider is
   supplied by the generic .set-card > * + *::before rule which already
   inset-strokes between cards children. */
.intake-card__section {
  background: transparent;
}
.intake-card__section[open] {
  background: var(--surface-muted);
}
.intake-card__section-sum {
  list-style: none;
  cursor: pointer;
  padding: var(--space-3) var(--space-4);
  display: flex; align-items: center; gap: var(--space-3);
  user-select: none;
}
.intake-card__section-sum::-webkit-details-marker { display: none; }
.intake-card__section-sum::after {
  content: '›';
  color: var(--muted);
  font-size: 16px;
  transform: rotate(90deg);
  transition: transform var(--dur-fast) var(--ease-standard);
  margin-left: var(--space-2);
}
.intake-card__section[open] > .intake-card__section-sum::after {
  transform: rotate(270deg);
}
.intake-card__section-title {
  flex: 1 1 auto;
  color: var(--ink);
  font-weight: 600;
  font-size: 14px;
}
.intake-card__section-pip {
  color: var(--muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: var(--surface);
  border: 1px solid var(--line);
}
.intake-card__section-body {
  padding: 0 0 var(--space-2) 0;
}
/* Suppress the nested <section>'s own h2 + outer margin when it's hosted
   inside a sub-section body — the summary already carries the title. */
.intake-card__section-body > .set-group {
  margin-top: 0;
}
.intake-card__section-body > .set-group > .set-group__title {
  display: none;
}
.intake-card__section-body > .set-group > .set-card {
  border-radius: 0;
  margin: 0;
}
.intake-card__section-edit {
  padding: var(--space-2) var(--space-4) var(--space-3) var(--space-4);
}
.intake-card__section-edit .set-row {
  border-radius: var(--radius-md);
  background: var(--surface);
  border: 1px solid var(--line);
  padding: var(--space-3) var(--space-4);
  display: flex; align-items: center; gap: var(--space-3);
  cursor: pointer;
}
.intake-card__section-edit .set-row__label {
  color: var(--accent);
  font-weight: 600;
}
.intake-card__section-edit .set-row__chev {
  color: var(--muted);
  margin-left: auto;
}
/* Restant van de inline-bewerkers; sinds DOELEN-KRIJGEN-EEN-EIGEN-DEUR draagt
   de Intake-accordeon alleen nog de samenvatting + de verwijzing. */
.intake-card__section-editor {
  padding: 0 var(--space-4) var(--space-3) var(--space-4);
}

/* DOELEN-KRIJGEN-EEN-EIGEN-DEUR — /settings/goals draagt de twee bestaande
   bewerkers ONGEWIJZIGD; hun eigen paginakop zou hier een tweede titel en een
   tweede terugpijl geven, dus die is de enige chrome die we onderdrukken. */
.fz-goals-slot .set-page__header,
.fz-goals-slot .goals-header { display: none; }
.fz-goals-slot .set-page,
.fz-goals-slot .goals-page {
  padding: 0;
  max-width: none;
  margin: 0;
}
.fz-goals-origin:empty { display: none; }
.fz-goals-origin__card {
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-3);
}
.fz-goals-origin__badge {
  margin: 0 0 4px;
  font-size: 12px; font-weight: 700; letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--accent);
}
.fz-goals-origin__line {
  margin: 0; font-size: 14px; line-height: 1.5; color: var(--ink);
}
.fz-goals-origin__warn {
  margin: 6px 0 0; font-size: 13px; line-height: 1.5; color: var(--muted);
}
.fz-goals-origin__plan { padding: 0; margin-top: var(--space-2); }
.intake-card__section-edit .set-row[aria-expanded="true"] .set-row__chev {
  transform: rotate(90deg);
}
/* COACH-GOAL-STRUCTURE-V2 — the goal your coach agreed with you, read-only,
   above sub-section A. Empty means no element at all, not an empty state. */
.intake-goal {
  padding: 0 var(--space-4);
}
.intake-goal:empty { display: none; }
.intake-goal .intake-row + .intake-row {
  border-top: 1px solid var(--line);
}
.intake-goal__quote {
  margin: 0 0 var(--space-2);
  padding-left: var(--space-3);
  border-left: 2px solid var(--accent);
  color: var(--ink);
  font-size: 14px; line-height: 1.5;
}

/* Sub-section B + C list rows — read-only label-value pair. */
.intake-list {
  padding: 0 var(--space-4);
}
.intake-list .intake-row + .intake-row {
  border-top: 1px solid var(--line);
}
.intake-row {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) 0;
}
.intake-row__label {
  flex: 1 1 auto;
  color: var(--ink);
  font-size: 14px;
}
.intake-row__value {
  color: var(--section-label);
  font-variant-numeric: tabular-nums;
  font-size: 14px;
}
.intake-row--mode {
  padding: var(--space-3) var(--space-4) var(--space-1) var(--space-4);
  margin: 0;
  color: var(--accent);
  font-weight: 600;
  font-size: 13px;
}

/* ─── Boodschappen + eigen voorraad — WEG 2026-10-04 ─── */
/* VOEDING-BOODSCHAPPEN-EN-VOORRAAD-GAAN-UIT-KOKEN-BOEKT-NIETS-MEER-AF:
   950 lines of .nut-groc* / .restock-suggestion* / .pantry-* / .grocery-edit*
   / .unit-dropdown rules stood here. Nothing renders them any more, so they
   are gone rather than left as dead weight in every download. Archived
   outside the repo (tag archief/boodschappen-en-voorraad). The PAN keeps its
   own rules (.nut-ric__weigh and friends) — a cooked dish still runs out. */

/* INVITE-ONLY-SYSTEM (v0.9.602) — Settings Uitnodigingen + admin panel.
   Token-based; no hex literals so Dag + Nacht presets stay consistent. */
.fz-invite-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px 0;
}
.fz-invite-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 8px 12px;
  font-size: 13px;
  color: var(--ink);
  background: var(--surface);
}
.fz-invite-chip__code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  flex: 1 1 auto;
  color: var(--ink);
}
.fz-invite-chip__copy,
.fz-invite-chip__revoke {
  background: transparent;
  border: 0;
  padding: 4px 8px;
  font-size: 13px;
  cursor: pointer;
  border-radius: var(--radius-sm);
}
.fz-invite-chip__copy {
  background: var(--accent);
  color: var(--on-accent);
}
.fz-invite-chip__revoke {
  color: var(--ink-2);
}
.fz-invite-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--on-accent);
  font-size: 12px;
  font-weight: 600;
}
.fz-invite-bonus {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px 0 0 0;
}
.fz-invite-bonus__cta {
  align-self: flex-start;
}
.fz-invite-progress {
  height: 4px;
  border-radius: var(--radius-sm);
  background: var(--line-2);
  overflow: hidden;
}
.fz-invite-progress__fill {
  height: 100%;
  background: var(--accent);
  transition: width var(--dur-base) var(--ease-standard);
}
.fz-admin-invites__table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
  color: var(--ink);
}
.fz-admin-invites__table th,
.fz-admin-invites__table td {
  text-align: left;
  padding: 6px 8px;
  border-bottom: 1px solid var(--line);
}
.fz-admin-invites__table th {
  color: var(--ink-2);
  font-weight: 600;
}
.fz-admin-user-row {
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 8px 0;
}
.fz-admin-user-row .form-input {
  flex: 0 0 140px;
}
.fz-invite-status {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  font-size: 11px;
  font-weight: 600;
  color: var(--ink-2);
  background: var(--line-2);
  text-transform: lowercase;
}
.fz-invite-status--used {
  background: var(--accent);
  color: var(--on-accent);
}
.fz-invite-status--revoked,
.fz-invite-status--expired {
  background: var(--line);
  color: var(--ink-2);
}

/* CHAT-1-BRICKS-TO-SETTINGS (v0.9.625) — Chat tab upsell panel for
   non-premium accounts. Premium-pitch surface; copy is the design.
   Premium accounts see .placeholder (the binnenkort state); only the
   upsell needs its own treatment. Token-only colours per the css-hex
   lint (no hardcoded literals outside :root). */
.chat-upsell {
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-6) var(--space-5);
  background: var(--surface);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.chat-upsell__eyebrow {
  display: inline-block;
  align-self: flex-start;
  padding: 2px 10px;
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--on-accent);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.chat-upsell__title {
  margin: 0;
  font-size: 24px;
  font-weight: 300;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.chat-upsell__lede {
  margin: 0;
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink-2);
}
.chat-upsell__bullets {
  list-style: none;
  padding: 0;
  margin: var(--space-2) 0 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.chat-upsell__bullet {
  position: relative;
  padding-left: 18px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink-2);
}
.chat-upsell__bullet::before {
  content: "";
  position: absolute;
  left: 0;
  top: 9px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
}
.chat-upsell__note {
  margin: var(--space-3) 0 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted);
}

/* ═══════════════════════════════════════════════════════════════════════════
   CHAT-LOOK-FINISH-AWARE-AND-NAV-CHAT-BUTTON (v0.9.668) — premium chat surface,
   finish-AWARE. The V1 (v0.9.662) shell SHAPE is unchanged — only the colour
   vocabulary swaps from finish-invariant brand-moment hex to the theme tokens,
   so each finish renders the same premium chat in its OWN personality: Sassu
   Dag = light premium, Nacht unchanged, gauge/linen/volt/pulse each their own.
   Every paint reads a --chat-* alias that resolves to a theme token, so a
   Settings finish-switch recolours the chat end-to-end. No raw hex remains —
   the V1 css-hex-lint brand-moment exemption is gone.
   ═══════════════════════════════════════════════════════════════════════════ */
.chat {
  --chat-bg: var(--bg);
  --chat-surface: var(--surface);
  --chat-surface-2: var(--surface-2, var(--surface));
  --chat-ink: var(--ink);
  --chat-dim: var(--muted);
  --chat-accent: var(--accent);
  --chat-on-accent: var(--on-accent);
  /* CHAT-OVERZICHT-EEN-LIJST-KLEUR-EN-VINDBAAR — the SECOND chat hue: talking to
     a PERSON. --chat-accent stays the AI colour (cyan in Nacht, blue in Dag), so
     the human hue borrows the semantic amber token, which reads clearly apart
     from the accent in both presets. Token, not hex — a finish switch still
     recolours the whole drawer. --on-accent (never a hardcoded white) is the ink
     on top; it is the near-black both presets use for a filled control. */
  --chat-human: var(--warning);
  --chat-on-human: var(--on-accent);
  --chat-line: var(--line);
  --chat-status-on: var(--accent);
  --chat-status-off: var(--muted);

  /* CHAT-TAB-STARTS-NEW-AND-KEEPS-BOTTOM-NAV-VISIBLE — the shell ends where the
     bottom nav begins instead of covering it, so chat is a tab like any other
     rather than a trap you can only leave via the topbar ×. The inset is a
     variable because body.kb-open slides the tabbar off-screen (see the A11Y-8
     S23 rule): when the keyboard is up the strip is not there to be avoided,
     and reclaiming it is what keeps the sticky input row reachable on iOS. */
  --chat-nav-inset: calc(var(--tabbar-h) + var(--safe-bottom));
  /* The nav strip below the shell already clears the home indicator, so every
     bottom padding INSIDE chat must collapse to zero or the safe area is paid
     for twice. It comes back only in the kb-open case, where the nav is gone
     and the shell is flush with the viewport edge again. */
  --chat-safe-bottom: 0px;

  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: var(--chat-nav-inset);
  z-index: 50;
  display: flex;
  flex-direction: column;
  background: var(--chat-bg);
  color: var(--chat-ink);
}
body.kb-open .chat {
  --chat-nav-inset: 0px;
  --chat-safe-bottom: var(--safe-bottom);
}

/* ── Topbar: hamburger · brand (emblem + wordmark + status dot) · spacer ──── */
.chat__topbar {
  display: grid;
  grid-template-columns: 40px 1fr 40px;
  align-items: center;
  gap: 8px;
  padding: calc(8px + var(--safe-top)) 12px 8px;
  background: var(--chat-surface);
  border-bottom: 1px solid var(--chat-line);
}
.chat__menu {
  appearance: none;
  background: transparent;
  border: 0;
  margin: 0;
  padding: 8px;
  width: 40px;
  height: 40px;
  color: var(--chat-dim);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-md);
}
/* Author display:inline-flex beats the [hidden] attribute — the back arrow is
   hidden on the list view, so it needs the explicit guard. */
.chat__menu[hidden] { display: none; }
.chat__menu:hover { color: var(--chat-ink); }
.chat__menu svg { width: 22px; height: 22px; }
/* ── brand-horse-head — S3 right-profile head, fixed ice-blue silhouette on a
   constant navy plate, clipped to a true circle. The ::before paints
   --horse-glyph (finish-invariant ice-blue) masked to the horse alpha; the
   wrapper supplies the navy plate. Both tokens are brand-moment fixed palette —
   no finish recolouring, matching the auth-lockup brand treatment. A no-mask
   fallback swaps to the full-colour PNG. Used in chat topbar (sm) + Settings
   profile avatar (md). */
.brand-horse-head {
  display: inline-block;
  position: relative;
  background-color: var(--horse-plate);
  border-radius: 50%;
  overflow: hidden;
  flex: 0 0 auto;
}
.brand-horse-head--sm { width: 28px; height: 28px; }
.brand-horse-head--md { width: 48px; height: 48px; }
.brand-horse-head::before {
  content: '';
  position: absolute;
  inset: 0;
  background-color: var(--horse-glyph);
  -webkit-mask: url('/assets/img/brand/horse-head-alpha-512.png') center / cover no-repeat;
          mask: url('/assets/img/brand/horse-head-alpha-512.png') center / cover no-repeat;
}
@supports not ((-webkit-mask: url('x')) or (mask: url('x'))) {
  .brand-horse-head::before {
    background-image: url('/assets/img/brand/horse-head-512.png');
    background-size: cover;
    background-position: center;
    background-color: transparent;
    -webkit-mask: none;
            mask: none;
  }
}
.chat__brand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-width: 0;
}
/* CHAT-NAAM-BOVENIN-NAAST-DE-STIP — truncates rather than pushing the 40px bell
   out of the grid track. Empty (and so invisible) on the AI surface. */
.chat__peer-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--chat-ink);
  font-size: 0.9rem;
  font-weight: 600;
}
/* CHAT-DE-NAAM-BOVENIN-OPENT-HET-DOSSIER — for a coach the name is a button to
   that client's file. It must read exactly like the span it replaces, so the
   button chrome is stripped and only the chevron marks it as a door. */
.chat__peer-name.is-tappable {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  max-width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
.chat__peer-name-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chat__peer-name-chev {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  color: var(--chat-dim);
}
.chat__peer-name-chev svg { width: 15px; height: 15px; }
.chat__peer-name.is-tappable:hover .chat__peer-name-chev { color: var(--chat-ink); }
.chat__peer-name.is-tappable:active { filter: brightness(0.96); }
/* Wordmark via alpha-mask (same recolor trick as .auth__brand): paints
   --chat-ink clipped to the letterform alpha. The 'sassu' text stays in the
   DOM (transparent) so AT still announces the brand. 80×11 ≈ the 7.5:1 ratio. */
.chat__wordmark {
  width: 80px;
  height: 11px;
  overflow: hidden;
  color: transparent;
  user-select: none;
  background: var(--chat-ink);
  -webkit-mask: url('/assets/img/brand/wordmark-alpha.png') center / contain no-repeat;
  mask: url('/assets/img/brand/wordmark-alpha.png') center / contain no-repeat;
}
.chat__status-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--chat-status-on);
  flex: 0 0 auto;
}
.chat__status-dot--off { background: var(--chat-status-off); }
.chat__topbar-spacer { width: 40px; }
/* Notification bell (CHAT-SPLIT-DIALOGUE-DRAWER-FROM-NOTIFICATION-BELL) — holds
   the right edge the close × used to, so it stays the mirror of .chat__menu and
   the brand block keeps its optical centre. Subtle hit-target hint on
   hover/active, same as the hamburger. `position:relative` anchors the bubble. */
.chat__notif-bell {
  appearance: none;
  background: transparent;
  border: 0;
  margin: 0;
  padding: 8px;
  width: 40px;
  height: 40px;
  color: var(--chat-ink);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-md);
  justify-self: end;
  position: relative;
}
.chat__notif-bell[hidden] { display: none; }
.chat__notif-bell:hover,
.chat__notif-bell:active { background: var(--chat-line); }
.chat__notif-bell svg { width: 24px; height: 24px; }
/* Count bubble — reuses the SAME --badge-bg / --on-badge pair as .tabbar__badge
   so the two counts read as one family per finish, and tabular-nums + line-height
   1 for the geometric centring the tabbar badge learned the hard way (v0.9.318). */
.chat__notif-badge {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  background: var(--badge-bg);
  color: var(--on-badge);
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}
.chat__notif-badge[hidden] { display: none; }

/* ── Notification sheet (bell) ─────────────────────────────────────────────── */
/* Bottom-sheet, matching the mood-chat / add-food idiom rather than inventing a
   bell-anchored dropdown that would be cramped at 320px. Fixed like .chat__drawer
   and therefore re-applies the same nav inset. */
.chat__notifsheet {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: var(--chat-nav-inset, 0px);
  z-index: 61;
}
.chat__notifsheet[hidden] { display: none; }
.chat__notifsheet__backdrop {
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--chat-bg) 70%, transparent);
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-standard);
}
.chat__notifsheet--open .chat__notifsheet__backdrop { opacity: 1; }
.chat__notifsheet__panel {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: 70%;
  display: flex;
  flex-direction: column;
  background: var(--chat-surface);
  border-top: 1px solid var(--chat-line);
  border-radius: var(--radius-lg, 16px) var(--radius-lg, 16px) 0 0;
  transform: translateY(100%);
  transition: transform var(--dur-base) var(--ease-standard);
  box-shadow: 0 -2px 18px color-mix(in srgb, var(--chat-bg) 60%, transparent);
}
.chat__notifsheet--open .chat__notifsheet__panel { transform: translateY(0); }
.chat__notifsheet__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--chat-line);
}
.chat__notifsheet__title {
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--chat-ink);
}
.chat__notifsheet__close {
  appearance: none;
  background: transparent;
  border: 0;
  margin: 0;
  padding: 8px;
  width: 40px;
  height: 40px;
  color: var(--chat-ink);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-md);
}
.chat__notifsheet__close:hover,
.chat__notifsheet__close:active { background: var(--chat-line); }
.chat__notifsheet__close svg { width: 22px; height: 22px; }
.chat__notifsheet__list {
  flex: 1;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding-bottom: var(--safe-bottom, 0px);
}

/* ── Conversation list (CHAT-LIST-FIRST-CONTACTS-THEN-CHAT) ────────────────── */
/* Was a slide-in drawer; it is now the tab's LANDING VIEW — full-screen, no
   backdrop, no transform. Fixed (viewport-relative) despite sitting inside the
   fixed .chat shell, so it needs the same nav inset applied again; the variable
   is inherited from .chat. */
/* In flow, NOT fixed: it fills the tab body under the topbar, so the notification
   bell and the status dot stay reachable while the list is up. `display: flex`
   would beat the [hidden] attribute, hence the explicit guard. */
.chat__drawer {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  z-index: 1;
}
.chat__drawer[hidden] { display: none; }
.chat__drawer__panel {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: var(--chat-surface);
}
/* On the list view the chat surface itself is put away — only the topbar (which
   owns the notification bell + status dot) and the list stay. */
.chat--list .chat__messages,
.chat--list .chat__attach-strip,
.chat--list .chat__input { display: none; }
/* CHAT-LIST-FIRST-FIX-NAV-ENTRY-AND-RESPONSIVE-ROWS — the list is now the
   full-width landing view, not a ~320px overlay panel, and the inherited row
   styling does not survive that width change: a row's background is the SAME
   token as the panel it sits on, which reads as a card inside a narrow overlay
   but as loose, oversized raw text once it spans the whole screen with nothing
   marking where one row ends and the next begins. At full width the rows
   therefore get an explicit hairline separator and a proper gutter instead of a
   card. Typography is deliberately NOT touched — the compact drawer sizes are
   the intended look. Scoped to .chat--list so the overlay-drawer geometry is
   untouched if any path still opens it. */
.chat--list .chat__drawer__panel { width: 100%; max-width: 100%; }
.chat--list .chat__drawer__list {
  overflow-x: hidden;
  padding-left: 0;
  padding-right: 0;
  /* The separator IS the row boundary; a gap on top of it doubles it up. */
  gap: 0;
}
.chat--list .chat__drawer__row {
  max-width: 100%;
  border-radius: 0;
  border-bottom: 1px solid color-mix(in srgb, var(--chat-line) 70%, transparent);
}
.chat--list .chat__drawer__row:last-child { border-bottom: 0; }
.chat--list .chat__drawer__row-main {
  max-width: 100%;
  /* Aligns the text column with the header title and the New-chat buttons. */
  padding: 12px 14px;
  /* NOTE: the opaque background is load-bearing — it is what covers the
     absolutely-positioned swipe action layer (rename / delete) until the row is
     actually swiped. Making it transparent leaves those buttons on screen. */
}
/* Truncation contract. The base rules already satisfy this today (a flex item
   with overflow:hidden resolves min-width:auto to 0), which is precisely why it
   is worth pinning at full width: what must hold is the GUARANTEE, not the
   particular rule that currently provides it. */
.chat--list .chat__drawer__row-body,
.chat--list .chat__drawer__row-titlerow,
.chat--list .chat__drawer__row-metarow { min-width: 0; max-width: 100%; }
.chat--list .chat__drawer__row-title,
.chat--list .chat__drawer__row-snippet {
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The trailing time column must never be the thing that squeezes the name. */
.chat--list .chat__drawer__row-time { flex: 0 0 auto; white-space: nowrap; }
.chat__drawer__empty-action--primary {
  background: var(--accent);
  color: var(--on-accent);
  border: 0;
  border-radius: 999px;
  padding: 10px 18px;
  font-weight: 600;
  cursor: pointer;
}
.chat__drawer__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  /* No --safe-top: the list sits under the chat topbar, which already clears it. */
  padding: 10px 14px;
  border-bottom: 1px solid var(--chat-line);
}
/* CHAT-DRAWER-SINGLE-HEADER-STRIP-AND-AI-CHAT-IDENTITY — in the LIST view the
   topbar and this header were two stacked bars over the same 1px line. The list
   header becomes the ONE strip (status dot · Chats … kebab · Select · bell) and
   the now-empty topbar goes, so the reclaimed height goes to the list. The chat
   view is untouched. */
.chat--list .chat__topbar { display: none; }
.chat--list .chat__drawer__header {
  padding-top: calc(10px + var(--safe-top));
}
.chat--list .chat__drawer__title { flex: 1 1 auto; min-width: 0; }
.chat--list .chat__drawer__header > .chat__status-dot { margin-right: 2px; }
.chat--list .chat__drawer__header-actions .chat__notif-bell {
  width: 34px;
  height: 34px;
  padding: 6px;
}
.chat--list .chat__drawer__header-actions .chat__notif-bell svg { width: 21px; height: 21px; }
.chat__drawer__title {
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--chat-ink);
}
.chat__drawer__new,
/* EEN-QR-EN-JULLIE-ZIJN-VRIENDEN — the My-QR button borrows this button's whole
   shape but keeps its own class: two chat specs address the AI and client buttons
   as `.chat__drawer__new--client` and `.chat__drawer__new:not(...--client)`, and a
   third member of that family would make both locators ambiguous. */
.chat__drawer__qrbtn {
  appearance: none;
  margin: 12px 14px;
  padding: 11px 14px;
  min-height: 44px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  border: 1px solid var(--chat-line);
  border-radius: var(--radius-lg);
  background: var(--chat-accent);
  color: var(--chat-on-accent);
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
}
/* The author `display:flex` above beats the UA `[hidden]` rule, so the
   trainer-only client button needs an explicit guard or every member sees it. */
.chat__drawer__new[hidden] { display: none; }
/* CHAT-OVERZICHT-EEN-LIJST-KLEUR-EN-VINDBAAR — this button used to be a quiet
   grey tint so it would not compete with "Chat with AI". It is now the CARRIER of
   the human hue: the same --chat-human that fills every person icon in the list
   below, so the button and the rows it produces read as one family at a glance.
   Both buttons are therefore filled and equally loud — on purpose, the two are
   peers, not primary and secondary. */
.chat__drawer__new--client {
  margin-top: 0;
  background: var(--chat-human);
  color: var(--chat-on-human);
}
/* Outline, not fill: showing your code is a quieter act than starting a chat,
   and two more filled blocks in a row would flatten the drawer's hierarchy. */
.chat__drawer__qrbtn {
  margin-top: 0;
  background: transparent;
  color: var(--chat-human);
  border-color: var(--chat-human);
}
.chat__drawer__new-icon { display: inline-flex; }
.chat__drawer__new-icon svg { width: 18px; height: 18px; }
/* VRIENDEN-EEN-SEINTJE-ALS-HET-EVEN-MINDER-GAAT — the door to the support-signal
   sheet. The drawer already carries two FILLED blocks (Chat with AI, New client
   chat) and one OUTLINED one (My QR); a fourth bordered block would flatten that
   hierarchy into a wall of buttons. So this one is a quiet text row: reachable
   at a glance, but never competing with the two things you came here to do. */
.chat__drawer__supsigbtn {
  appearance: none;
  margin: 0 14px 10px;
  padding: 6px 2px;
  min-height: 36px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  border: 0;
  background: transparent;
  color: var(--chat-dim, #8a8a8a);
  font-family: inherit;
  font-size: 0.82rem;
  text-align: left;
  cursor: pointer;
}
.chat__drawer__supsigbtn:hover { color: var(--chat-ink); }
.chat__drawer__supsigbtn svg { width: 16px; height: 16px; }
/* FRIENDSHIP-CHAT-SEARCH-AND-LISTS-UI — find-a-user search + the named groups
   (Clients / Friends / Search results) the list is now divided into. */
/* EEN-BOEKJE-MET-AL-JE-MENSEN — the search field now shares a row with the
   contacts button, so the horizontal margin moved from the field to the bar. */
.chat__drawer__searchbar {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 14px 8px;
  flex-shrink: 0;
}
.chat__drawer__searchbar .chat__drawer__search { flex: 1 1 auto; min-width: 0; margin: 0; }
.chat__drawer__lib {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--chat-line);
  border-radius: var(--radius-lg);
  background: transparent;
  color: var(--chat-ink);
  cursor: pointer;
}
.chat__drawer__lib svg { width: 20px; height: 20px; }
.chat__drawer__lib:active { background: color-mix(in srgb, var(--chat-ink) 10%, transparent); }
.ui-dlg--contacts { width: min(420px, 92vw); }
.chat__contacts__filter {
  appearance: none;
  display: block;
  width: 100%;
  margin: 0 0 8px;
  padding: 9px 12px;
  min-height: 40px;
  border: 1px solid var(--chat-line, var(--line));
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--chat-ink, var(--ink)) 6%, transparent);
  color: var(--chat-ink, var(--ink));
  font-size: 0.9rem;
}
.chat__contacts__list {
  max-height: 55vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
/* VRIENDEN-EEN-SEINTJE-ALS-HET-EVEN-MINDER-GAAT — the sheet where you point at
   the friends who may hear that your nutrition is running off-pattern. It
   borrows the contacts sheet's frame and the kebab menu's checkbox idiom
   (.chat__drawer__menu-row + .chat__drawer__menu-box) on purpose: a permission
   screen is the worst possible place to introduce a control the user has never
   seen before. Only the spacing is its own, because every switch here carries a
   sentence of explanation underneath it and the menu rows do not. */
.ui-dlg--supsig { width: min(460px, 94vw); }
.chat__supsig__master { width: 100%; }
/* The master switch is the loudest thing in the sheet: it is the one that
   decides whether ANY signal leaves the account, so it gets the accent tint the
   individual rows below deliberately do not have. */
.chat__supsig__master.chat__drawer__menu-row {
  background: color-mix(in srgb, var(--chat-accent, var(--accent)) 12%, transparent);
  font-weight: 600;
}
.chat__supsig__note {
  margin: 4px 0 10px 36px; /* lines up under the label, not under the box */
  font-size: 0.78rem;
  line-height: 1.35;
  color: var(--chat-dim, #8a8a8a);
}
.chat__supsig__list {
  max-height: 50vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  border-top: 1px solid var(--chat-line, var(--line));
}
.chat__supsig__row {
  padding: 10px 0 6px;
  border-bottom: 1px solid var(--chat-line, var(--line));
}
.chat__supsig__row:last-child { border-bottom: 0; }
.chat__supsig__name {
  padding: 0 8px 2px;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--chat-ink, var(--ink));
}
.chat__supsig__toggle { width: 100%; }
/* Dimmed, NEVER hidden. With the master switch off these rows still have to say
   what the app can do and what you already chose — a control that disappears
   reads as a feature that does not exist, and the next tap goes looking for it
   in Settings. `disabled` (not pointer-events) so the keyboard skips them too. */
.chat__supsig__toggle[disabled] {
  opacity: 0.45;
  cursor: default;
}
.chat__supsig__toggle[disabled]:hover { background: transparent; }
.chat__drawer__search {
  appearance: none;
  margin: 0 14px 8px;
  padding: 9px 12px;
  min-height: 40px;
  flex-shrink: 0;
  border: 1px solid var(--chat-line);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--chat-ink) 6%, transparent);
  color: var(--chat-ink);
  font-size: 0.9rem;
}
.chat__drawer__search::placeholder { color: var(--chat-dim, #8a8a8a); }
.chat__drawer__group {
  padding: 12px 8px 4px;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--chat-dim, #8a8a8a);
}
/* Results sit UNDER the list rather than inside it, so they get their own
   scroll budget instead of pushing the friends list out of view. */
.chat__drawer__results {
  flex: 0 1 auto;
  min-height: 0;
  max-height: 45%;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 0 8px;
  border-top: 1px solid var(--chat-line);
}
.chat__drawer__results[hidden] { display: none; }
/* A search hit is NOT a chat you can open — only its button is interactive. */
.chat__drawer__row--search {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 8px;
}
/* NOT .chat__drawer__row-action — that name is taken by the swipe-revealed
   rename/delete layer (position:absolute, further down this file). */
.chat__drawer__row-cta {
  appearance: none;
  flex: 0 0 auto;
  margin-left: auto;
  padding: 6px 12px;
  min-height: 32px;
  border: 1px solid var(--chat-line);
  border-radius: var(--radius-lg);
  background: var(--chat-accent);
  color: var(--chat-on-accent);
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
}
/* "Verstuur verzoek" is the ONE thing a search row can do, so it keeps the
   accent alone. A pointer elsewhere (accept in the bell) is a quieter button —
   two filled buttons in one list would read as two equal offers. */
.chat__drawer__row-cta--quiet {
  background: color-mix(in srgb, var(--chat-ink) 8%, transparent);
  color: var(--chat-ink);
}
.chat__drawer__row-cta:disabled {
  background: color-mix(in srgb, var(--chat-ink) 8%, transparent);
  color: var(--chat-dim, #8a8a8a);
  cursor: default;
}
.chat__drawer__list {
  flex: 1;
  /* min-height:0 lets this flex child actually shrink inside the column panel
     so overflow-y:auto engages instead of the list pushing past the panel. */
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 0 8px calc(10px + var(--chat-safe-bottom));
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.chat__drawer__empty {
  padding: 18px 14px;
  /* CHAT-DRAWER-RESILIENT-INIT — defensive: if --chat-dim is ever undefined or
     too low-contrast, fall back to a readable muted grey so the empty/loading
     text is never invisible against the dark drawer. min-height reserves space
     so the loader dot is always a visible proof-of-life. */
  color: var(--chat-dim, #8a8a8a);
  opacity: 0.9;
  min-height: 2em;
  font-size: 0.9rem;
  text-align: center;
}
.chat__drawer__empty--error {
  color: var(--danger, #c4453d);
  opacity: 1;
  font-style: italic;
}
/* CHAT-OVERZICHT-EEN-LIJST-KLEUR-EN-VINDBAAR — the retry beside "your chats with
   people could not be loaded". A failure the member can act on, not just read. */
.chat__drawer__retry {
  appearance: none;
  display: block;
  margin: 8px auto 0;
  padding: 6px 14px;
  min-height: 36px;
  border: 1px solid currentColor;
  border-radius: var(--radius-lg);
  background: transparent;
  color: inherit;
  font-size: 0.85rem;
  font-style: normal;
  font-weight: 600;
  cursor: pointer;
}
.chat__drawer__row {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-md);
  /* flex-shrink:0 — rows are flex children of the column list; without this the
     default flex-shrink:1 squeezes every row toward zero height once there are
     more than a few chats, collapsing titles while the absolutely-positioned
     swipe-action layer survives (the reported "blank drawer, swipe still works"). */
  flex: 0 0 auto;
}
.chat__drawer__row--active { background: color-mix(in srgb, var(--chat-accent) 16%, transparent); }
.chat__drawer__row-action {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 128px;
  display: flex;
  align-items: stretch;
  justify-content: flex-end;
}
.chat__drawer__rename-btn {
  appearance: none;
  border: 0;
  width: 64px;
  background: color-mix(in srgb, var(--chat-accent) 12%, var(--chat-surface));
  color: var(--chat-ink);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.chat__drawer__rename-btn svg { width: 19px; height: 19px; }
.chat__drawer__rename-input {
  position: absolute;
  z-index: 2;
  inset: 0;
  width: 100%;
  border: 1px solid var(--chat-accent);
  border-radius: var(--radius-md);
  background: var(--chat-surface);
  color: var(--chat-ink);
  padding: 11px 12px;
  font-size: 0.92rem;
  font-family: inherit;
}
.chat__drawer__rename-input:focus { outline: none; }
.chat__drawer__paywall {
  margin: 8px 6px;
  padding: 18px 16px;
  border: 1px solid var(--chat-line);
  border-radius: var(--radius-lg);
  background: var(--chat-surface);
  text-align: center;
}
.chat__drawer__paywall-title {
  margin: 0 0 6px;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--chat-ink);
}
.chat__drawer__paywall-lede {
  margin: 0 0 14px;
  font-size: 0.85rem;
  /* Full contrast (--ink), NOT --muted: the lede was invisible in Nacht when
     it inherited the intentionally low-contrast dim token. */
  color: var(--chat-ink);
}
.chat__drawer__paywall-cta {
  appearance: none;
  border: 0;
  border-radius: var(--radius-md);
  padding: 10px 22px;
  background: var(--chat-accent);
  color: var(--chat-on-accent);
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
}
.chat__drawer__archive {
  appearance: none;
  border: 0;
  width: 64px;
  height: 100%;
  background: color-mix(in srgb, var(--chat-accent) 22%, var(--chat-surface));
  color: var(--chat-ink);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.chat__drawer__archive svg { width: 20px; height: 20px; }
.chat__drawer__archive--confirm { width: 124px; padding: 0 6px; }
.chat__drawer__archive-confirm { font-size: 0.72rem; font-weight: 600; line-height: 1.1; }
.chat__drawer__row-main {
  position: relative;
  z-index: 1;
  appearance: none;
  border: 0;
  width: 100%;
  text-align: left;
  background: var(--chat-surface);
  color: var(--chat-ink);
  padding: 11px 12px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  cursor: pointer;
  transition: transform var(--dur-base) var(--ease-standard);
}
.chat__drawer__row-main:hover { background: var(--chat-surface-2); }
/* CONNECTOR-COACH-VRAAGT-TOESTEMMING — "Bekijk" onder de toestemmingsvraag.
   .chat__drawer__row is position:relative en GEEN flexdoos, dus een broer van
   .chat__drawer__row-main (width:100%) komt er automatisch onder te staan in
   plaats van ernaast — precies goed: op 320px zou een knop naast de twee regels
   de tekst wegdrukken. Rechts uitgelijnd en de stille vorm, niet de accentvorm:
   dit is een deur naar de uitleg, geen "ja". Een echte toestemming wordt op het
   toestemmingsscherm gegeven, naast de zin die hem uitlegt. */
.chat__drawer__row-view {
  appearance: none;
  position: relative;
  z-index: 1;
  display: block;
  margin: 0 12px 10px auto;
  padding: 6px 12px;
  min-height: 32px;
  border: 1px solid var(--chat-line);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--chat-ink) 8%, transparent);
  color: var(--chat-ink);
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
}
.chat__drawer__row-title {
  font-size: 0.92rem;
  color: var(--chat-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* CHAT-GELEZEN-LEEST-LICHTER — unread carries the weight; read rows keep
   full --chat-ink colour. Dimming read titles made them unreadable in Nacht
   (see the warning above) and was reverted. */
.chat__drawer__row-main--unread .chat__drawer__row-title { font-weight: 650; }
.chat__drawer__row-time {
  font-size: 0.74rem;
  color: var(--chat-dim);
}

/* ── Unified client message threads (COACH-CHAT-CLIENT-THREADS-UNIFIED) ────── */
.chat__drawer__row-main--client,
.chat__drawer__row-main--ai {
  flex-direction: row;
  align-items: center;
  gap: 10px;
}
/* AI rows carry the sparkle in the very slot the person icon holds on human
   rows, so "who am I talking to" is answerable without reading the title. */
.chat__drawer__row-main--ai .chat__drawer__row-time { align-self: flex-start; }
.chat__drawer__row-icon {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--chat-accent) 20%, var(--chat-surface));
  color: var(--chat-accent);
}
/* The human hue, matching the "Chat with a client" button above the list. This
   is what replaced the "Clients"/"Friends" group headers: with AI and people now
   interleaved on activity, colour is the only thing left that says who a row is
   with, so it has to hold on its own. */
.chat__drawer__row-icon--human {
  background: color-mix(in srgb, var(--chat-human) 22%, var(--chat-surface));
  color: var(--chat-human);
}
.chat__drawer__row-icon svg { width: 17px; height: 17px; }
.chat__drawer__row-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.chat__drawer__row-titlerow {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.chat__drawer__row-titlerow .chat__drawer__row-title { flex: 0 1 auto; }
.chat__drawer__row-tag {
  flex: 0 0 auto;
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  /* Only human rows carry a tag (Client / Coach / Friend), so it follows the
     human hue rather than the AI accent. */
  color: var(--chat-human);
  background: color-mix(in srgb, var(--chat-human) 15%, transparent);
  padding: 1px 6px;
  border-radius: var(--radius-md);
}
.chat__drawer__row-unread {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--chat-accent);
}
/* CHAT-CLIENT-COACH-THREAD-AND-PUSH — the member's pinned Coach thread sits at the
   top of the drawer; a subtle rail marks it apart from AI chats. The rail follows
   the human hue now that the row sorts INTO the mixed list instead of pinning. */
.chat__drawer__row--coach {
  box-shadow: inset 3px 0 0 var(--chat-human);
}
.chat__drawer__row-snippet {
  font-size: 0.76rem;
  color: var(--chat-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* VRIENDEN-WIE-JE-QR-SCANT-KAN-METEEN-MET-JE-PRATEN — "Say hi" sits in the
   snippet slot but is an invitation, not a sentence anybody wrote. Italic keeps
   that difference readable at a glance without inventing a second colour. */
.chat__drawer__row-snippet--sayhi { font-style: italic; }
.chat__drawer__row-main--client .chat__drawer__row-time { align-self: flex-start; }

/* ── Notification centre (CHAT-SCREEN-BECOMES-THE-NOTIFICATION-CENTRE) ─────── */
/* The drawer is ONE time-ordered list: AI chats, client threads and inbox
   notifications. Everything below only ADDS the notification row + the category
   filter — every row keeps the .chat__drawer__row-* geometry defined above. */
.chat__drawer__header-actions {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 2px;
}
.chat__drawer__kebab {
  appearance: none;
  position: relative;
  background: transparent;
  border: 0;
  padding: 6px;
  width: 34px;
  height: 34px;
  color: var(--chat-dim);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-md);
}
/* Same trap as .chat__drawer__new[hidden]: the author display:inline-flex above
   beats the UA [hidden] rule, so the trainer-only kebab needs this guard or
   every member sees a filter for notifications they can never receive. */
.chat__drawer__kebab[hidden] { display: none; }
.chat__drawer__kebab:hover { color: var(--chat-ink); }
.chat__drawer__kebab svg { width: 20px; height: 20px; }
/* Accent pip = "a filter is on". The filter bar under the header carries the
   words; this is the at-a-glance marker on the control itself. */
.chat__drawer__kebab--on { color: var(--chat-accent); }
.chat__drawer__kebab--on::after {
  content: '';
  position: absolute;
  top: 4px;
  right: 4px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--chat-accent);
}
.chat__drawer__menu {
  position: absolute;
  z-index: 4;
  top: calc(48px + var(--safe-top));
  right: 8px;
  width: min(208px, calc(100% - 16px));
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: var(--chat-surface-2);
  border: 1px solid var(--chat-line);
  border-radius: var(--radius-lg);
  box-shadow: 0 8px 24px color-mix(in srgb, var(--chat-bg) 70%, transparent);
}
/* Third instance of the .chat__drawer__new[hidden] trap in this file: the author
   display:flex above beats the UA [hidden] rule. The menu is create/remove'd, so
   nothing relies on this today — it is here so the next builder who reaches for
   `hidden` on the popover doesn't ship an un-closable filter menu. */
.chat__drawer__menu[hidden] { display: none; }
.chat__drawer__menu-title {
  padding: 6px 8px 2px;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--chat-dim);
}
.chat__drawer__menu-row {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--chat-ink);
  font-family: inherit;
  font-size: 0.88rem;
  text-align: left;
  padding: 8px;
  min-height: 40px;
  display: flex;
  align-items: center;
  gap: 10px;
  border-radius: var(--radius-md);
  cursor: pointer;
}
.chat__drawer__menu-row:hover { background: color-mix(in srgb, var(--chat-ink) 8%, transparent); }
.chat__drawer__menu-box {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  border-radius: 5px;
  border: 1.5px solid var(--chat-line);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--chat-on-accent);
}
.chat__drawer__menu-box svg { width: 13px; height: 13px; }
.chat__drawer__menu-box--on { border-color: var(--chat-accent); background: var(--chat-accent); }
/* A hidden category must never read as "nothing happened". This bar states what
   is being held back and offers the way out, in the header, above the list. */
.chat__drawer__filterbar {
  flex-shrink: 0;
  margin: 0 14px 8px;
  padding: 7px 10px;
  display: flex;
  align-items: center;
  gap: 8px;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--chat-accent) 12%, transparent);
  font-size: 0.74rem;
  color: var(--chat-ink);
}
.chat__drawer__filterbar[hidden] { display: none; }
.chat__drawer__filterbar-text { flex: 1 1 auto; min-width: 0; }
.chat__drawer__filterbar-reset {
  appearance: none;
  flex: 0 0 auto;
  border: 0;
  background: transparent;
  color: var(--chat-accent);
  font-family: inherit;
  font-size: 0.74rem;
  font-weight: 700;
  padding: 2px;
  cursor: pointer;
}
/* A notification is not a person: the round icon drops the accent tint so a
   client thread and a booking request stay tellable apart at a glance. */
.chat__drawer__row-icon--notif {
  background: color-mix(in srgb, var(--chat-ink) 10%, transparent);
  color: var(--chat-dim);
}
.chat__drawer__row-tag--notif {
  color: var(--chat-dim);
  background: color-mix(in srgb, var(--chat-ink) 10%, transparent);
}
/* A notification carries four facts (who / what / detail / when) where a thread
   carries three, and at 320px a right-hand time COLUMN wraps "3 min geleden"
   onto two lines and squeezes the name down to "Mari…". So the time rides the
   end of the name line and the type chip drops to the detail line: two full-width
   lines instead of three cramped columns. */
.chat__drawer__row-time--inline {
  margin-left: auto;
  padding-left: 6px;
  white-space: nowrap;
}
.chat__drawer__row-metarow {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.chat__drawer__empty-action {
  appearance: none;
  display: block;
  margin: 10px auto 0;
  min-height: 40px;
  padding: 0 14px;
  border: 1px solid var(--chat-line);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--chat-accent);
  font-family: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
}
/* CHAT-TERUGLEZEN-EN-GEEN-VERDWENEN-BERICHTEN — the "load older" door at the top
   of an open client thread. */
.chat__older {
  display: flex;
  justify-content: center;
  padding: 4px 0 10px;
}
.chat__older-btn {
  appearance: none;
  min-height: 34px;
  padding: 0 14px;
  border: 1px solid var(--chat-line);
  border-radius: 999px;
  background: transparent;
  color: var(--chat-accent);
  font-family: inherit;
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
}
.chat__older-btn:disabled {
  opacity: 0.6;
  cursor: default;
}
/* Detail view for a notification with no destination of its own. Reuses the
   .chat__picker full-panel overlay shell; only the content block is new. */
.chat__detail {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 14px 14px calc(14px + var(--chat-safe-bottom));
  display: flex;
  flex-direction: column;
  gap: 10px;
}
/* .chat__detail is a flex column, so a chip would stretch edge-to-edge without
   this — it must hug its own text. */
.chat__detail__chip { align-self: flex-start; }
.chat__detail__meta { font-size: 0.74rem; color: var(--chat-dim); }
.chat__detail__title { font-size: 1rem; font-weight: 600; color: var(--chat-ink); }
.chat__detail__body {
  margin: 0;
  font-size: 0.88rem;
  line-height: 1.5;
  color: var(--chat-ink);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.chat__detail__cta {
  appearance: none;
  margin-top: 4px;
  min-height: 44px;
  padding: 0 14px;
  border: 0;
  border-radius: var(--radius-lg);
  background: var(--chat-accent);
  color: var(--chat-on-accent);
  font-family: inherit;
  font-size: 0.92rem;
  font-weight: 600;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.chat__detail__cta svg { width: 17px; height: 17px; }
.chat__detail__link { font-size: 0.85rem; color: var(--chat-accent); text-decoration: none; }

/* NOTIF-REPLY-BY-CHAT-EVERYWHERE-REPLY-BY-EMAIL-IS — de twee antwoordkanalen op
   één regel. .chat__detail is een flex-kolom, dus zonder deze wrapper zouden ze
   stapelen. Geen nieuw knopidioom: beide kinderen blijven .chat__detail__link. */
.chat__detail__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 18px;
  margin-top: 2px;
  min-height: 44px;
}
/* De chat-actie is een in-page <button>, geen mailto-<a>. De UA-knopchrome moet
   eraf, anders leest hij niet als de link ernaast. */
button.chat__detail__link {
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  padding: 0;
  background: none;
  font-family: inherit;
  font-weight: inherit;
  line-height: inherit;
  cursor: pointer;
  text-align: left;
}

/* ── New-message client picker (COACH-CHAT-NEW-THREAD-CLIENT-PICKER) ───────── */
/* Full-panel overlay that slides over the drawer list: header + optional search
   + the alphabetical roster. Reuses the .chat__drawer__row-* row idiom inside. */
.chat__picker {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: flex;
  flex-direction: column;
  background: var(--chat-surface);
  transform: translateX(8%);
  opacity: 0;
  transition: transform var(--dur-base) var(--ease-standard), opacity var(--dur-base) var(--ease-standard);
}
.chat__picker--open { transform: translateX(0); opacity: 1; }
.chat__picker__header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: calc(10px + var(--safe-top)) 12px 10px;
  border-bottom: 1px solid var(--chat-line);
}
.chat__picker__back {
  appearance: none;
  background: transparent;
  border: 0;
  padding: 6px;
  width: 34px;
  height: 34px;
  color: var(--chat-dim);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-md);
}
.chat__picker__back:hover { color: var(--chat-ink); }
.chat__picker__back svg { width: 20px; height: 20px; }
.chat__picker__title {
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--chat-ink);
}
.chat__picker__search {
  appearance: none;
  margin: 10px 12px 4px;
  padding: 9px 12px;
  border: 1px solid var(--chat-line);
  border-radius: var(--radius-md);
  background: var(--chat-surface-2);
  color: var(--chat-ink);
  font-size: 0.9rem;
}
.chat__picker__search::placeholder { color: var(--chat-dim); }
.chat__picker__list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 6px 8px calc(10px + var(--chat-safe-bottom));
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.chat__picker__empty {
  padding: 18px 14px;
  color: var(--chat-dim);
  font-size: 0.9rem;
  text-align: center;
}

.chat__thread-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 4px 0 12px;
  padding: 9px 12px;
  border-radius: var(--radius-lg);
  background: var(--chat-surface-2);
  color: var(--chat-ink);
}
.chat__thread-banner-icon {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--chat-accent) 20%, var(--chat-surface));
  color: var(--chat-accent);
}
.chat__thread-banner-icon svg { width: 17px; height: 17px; }
.chat__thread-banner-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.chat__thread-banner-name { font-size: 0.9rem; font-weight: 600; }
.chat__thread-banner-note { font-size: 0.74rem; color: var(--chat-dim); }
/* CHAT-DE-NAAM-BOVENIN-OPENT-HET-DOSSIER — the coach side lost its banner, so an
   empty client thread would otherwise be a blank screen. */
.chat__thread-empty {
  margin: 14px 0;
  text-align: center;
  font-size: 0.78rem;
  color: var(--chat-dim);
}

/* ── Bulk-select mode (CHAT-CONVERSATIONS-BULK-DELETE) ────────────────────── */
.chat__drawer__select-toggle {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--chat-accent);
  font-size: 0.82rem;
  font-weight: 600;
  font-family: inherit;
  padding: 6px 8px;
  margin-left: auto;
  border-radius: var(--radius-md);
  cursor: pointer;
}
.chat__drawer__select-toggle--on { color: var(--chat-ink); }
/* Same trap as .chat__drawer__new[hidden]: the `display:flex` below beats the UA
   `[hidden]` rule, so bar.hidden=true left the bulk-delete bar parked under the
   history at all times. Nothing renders below the one scroll of chats. */
.chat__drawer__select-bar[hidden] { display: none; }
.chat__drawer__select-bar {
  flex: 0 0 auto;
  padding: 10px 14px calc(12px + var(--chat-safe-bottom));
  border-top: 1px solid var(--chat-line);
  background: var(--chat-surface);
  display: flex;
  gap: 8px;
}
/* Neutral/secondary counterpart to the destructive delete button — outline fill
   from design tokens so it reads as non-destructive in both Dag and Nacht. */
.chat__drawer__select-all {
  appearance: none;
  flex: 1 1 0;
  min-width: 0;
  border: 1px solid var(--chat-line);
  border-radius: var(--radius-lg);
  padding: 12px 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: var(--chat-surface-2);
  color: var(--chat-ink);
  font-size: 0.92rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}
.chat__drawer__select-all-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat__drawer__select-all:disabled { opacity: 0.5; cursor: default; }
.chat__drawer__select-del {
  appearance: none;
  border: 0;
  flex: 1 1 0;
  min-width: 0;
  border-radius: var(--radius-lg);
  padding: 12px 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: var(--chat-accent);
  /* Contrast protocol: text/icon on an accent fill uses the on-accent token,
     never a hardcoded white (fails on the lime Nacht accent). */
  color: var(--chat-on-accent);
  font-size: 0.92rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}
.chat__drawer__select-del svg { width: 18px; height: 18px; }
.chat__drawer__select-del:disabled { opacity: 0.5; cursor: default; }
.chat__drawer__select-del--confirm { background: var(--danger, #c4453d); }
.chat__drawer__row--selectable { display: flex; align-items: center; gap: 10px; padding: 0 4px; cursor: pointer; }
.chat__drawer__row--checked { background: color-mix(in srgb, var(--chat-accent) 14%, transparent); }
.chat__drawer__check {
  appearance: none;
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  margin-left: 8px;
  border: 2px solid var(--chat-dim);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--chat-on-accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.chat__drawer__check svg { width: 14px; height: 14px; }
.chat__drawer__check--on { border-color: var(--chat-accent); background: var(--chat-accent); }
.chat__drawer__row-main--select { background: transparent; cursor: pointer; }
.chat__drawer__row-main--select:hover { background: transparent; }

/* ── Message list ─────────────────────────────────────────────────────────── */
.chat__messages {
  flex: 1;
  overflow-y: auto;
  overflow-anchor: none;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-direction: column;
  padding: 14px 14px 8px;
}
/* CHAT-PERSONAL-GREETING-AND-DUAL-HORSE-BACKGROUND — the Aligi Sassu two-horses
   engraving sits behind the conversation as a faint watermark. Two decisions
   worth knowing:
   (1) It is painted as an ALPHA MASK over --chat-ink, NOT as a background-image.
       logo-emblem-480.webp is opaque (no alpha channel), so using it directly
       would paint a solid navy square that breaks every light finish. A mask
       inherits the ink colour, so it is neutral by construction on dark AND
       light with no per-theme filter tuning.
   (2) It hangs off .chat (the fixed shell), not .chat__messages (the scroller),
       so the watermark stays put while the thread scrolls over it. .chat is
       already position:fixed, so it is the containing block already — do NOT
       add position:relative here, it would override the fixed shell layout.
   Sibling children are left alone on purpose: the drawer is position:fixed with
   z-index 60, and a blanket `.chat > *` rule would flatten it. At 0.05 alpha the
   layer reads as a watermark whether it composites over or under the text. */
.chat::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  opacity: 0.05;
  background-color: var(--chat-ink);
  -webkit-mask: url('/assets/img/brand/logo-emblem-alpha-480.png') center / 60% auto no-repeat;
          mask: url('/assets/img/brand/logo-emblem-alpha-480.png') center / 60% auto no-repeat;
}
@supports not ((-webkit-mask-image: none) or (mask-image: none)) {
  /* No mask support: paint nothing rather than an opaque plate. */
  .chat::before { background-color: transparent; }
}
.chat__empty {
  margin: auto;
  padding: 24px 16px;
  text-align: center;
  max-width: 320px;
}
.chat__empty-title {
  margin: 0 0 8px;
  font-size: 20px;
  font-weight: 300;
  color: var(--chat-ink);
}
.chat__empty-lede {
  margin: 0;
  font-size: 14px;
  line-height: 1.5;
  color: var(--chat-dim);
}

/* ── Bubbles ──────────────────────────────────────────────────────────────── */
.chat__msg {
  margin-top: 4px;
  padding: 10px 14px;
  border-radius: var(--radius-lg);
  color: var(--chat-ink);
  word-wrap: break-word;
  overflow-wrap: anywhere;
}
.chat__msg:first-child { margin-top: 0; }
.chat__msg--user {
  align-self: flex-end;
  max-width: 78%;
  background: var(--chat-surface-2);
}
.chat__msg--assistant {
  align-self: flex-start;
  max-width: 84%;
  background: var(--chat-surface);
}
/* Sequential bubbles from the same role tighten the vertical gap. */
.chat__msg--user + .chat__msg--user,
.chat__msg--assistant + .chat__msg--assistant { margin-top: 2px; }
.chat__msg-text {
  font-size: 14.5px;
  line-height: 1.4;
  white-space: pre-wrap;
}
.chat__msg-text strong { font-weight: 700; }
.chat__msg-text em { font-style: italic; }
/* CHAT-EEN-LINK-IN-EEN-BERICHT-IS-KLIKBAAR — a link inside a human message.
   BOTH bubbles sit on a SURFACE background (--chat-surface / --chat-surface-2),
   not on a filled accent, so --on-accent (the near-black ink of a filled
   control) would be unreadable here: --chat-accent is the token that reads as a
   link on either bubble in both finishes. break-all keeps a long /plus/ link
   from stretching the bubble past its max-width on a phone. */
.chat__msg-text a {
  color: var(--chat-accent);
  text-decoration: underline;
  text-underline-offset: 2px;
  word-break: break-all;
}
.chat__msg-text code {
  font-family: var(--mono, monospace);
  font-size: 0.92em;
  background: var(--surface-2, rgba(127, 127, 127, 0.16));
  padding: 0.05em 0.3em;
  border-radius: var(--radius-sm);
}
.chat__msg-text ul {
  margin: 0.25rem 0;
  padding-left: 1em;
  white-space: normal;
}
.chat__msg-text li {
  margin: 0.25rem 0;
}
/* GFM tables in assistant bubbles only. Block + overflow-x so a wide macro
   table scrolls horizontally on a phone instead of overflowing the bubble. */
.chat__msg--assistant .chat__msg-text .chat__md-table {
  width: 100%;
  border-collapse: collapse;
  margin: 6px 0;
  font-size: 0.86em;
  display: block;
  overflow-x: auto;
}
.chat__msg--assistant .chat__msg-text .chat__md-table th,
.chat__msg--assistant .chat__msg-text .chat__md-table td {
  padding: 4px 8px;
  border: 1px solid var(--chat-line);
  text-align: left;
  white-space: nowrap;
}
.chat__msg--assistant .chat__msg-text .chat__md-table th {
  font-weight: 700;
  background: var(--chat-surface-2);
}
.chat__msg--assistant .chat__msg-text .chat__md-table tbody tr:nth-child(even) {
  background: color-mix(in srgb, var(--chat-line) 30%, transparent);
}
/* CHAT-AI-ANSWER-FEEDBACK-THUMBS — chrome under an AI answer: dim at rest, full
   on hover/focus, so the thumbs never compete with the reply itself. */
.chat__fb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
  opacity: .6;
  transition: opacity .15s ease;
}
.chat__msg:hover .chat__fb,
.chat__fb:focus-within { opacity: 1; }
.chat__fb-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 2px;
  background: transparent;
  border: 0;
  border-radius: 6px;
  color: var(--chat-dim);
  cursor: pointer;
}
.chat__fb-btn svg { width: 16px; height: 16px; }
.chat__fb-btn.is-on  { color: var(--chat-accent); }
.chat__fb-btn.is-off { opacity: .4; }
/* Author display beats the hidden attribute, so restore it explicitly. */
.chat__fb-strip[hidden] { display: none; }
.chat__fb-strip {
  display: flex;
  flex: 1 1 100%;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
.chat__fb-lbl {
  font-size: 12px;
  color: var(--chat-dim);
}
.chat__fb-note {
  flex: 1 1 140px;
  min-width: 0;
  padding: 4px 6px;
  font: inherit;
  font-size: 12px;
  color: inherit;
  background: var(--chat-surface-2);
  border: 1px solid var(--chat-line);
  border-radius: 6px;
}
.chat__fb-send,
.chat__fb-skip {
  padding: 4px 8px;
  font-size: 12px;
  background: transparent;
  border: 1px solid var(--chat-line);
  border-radius: 6px;
  color: var(--chat-accent);
  cursor: pointer;
}
.chat__fb-skip { color: var(--chat-dim); }
.chat__fb-thanks {
  font-size: 12px;
  color: var(--chat-dim);
}
.chat__msg-meta {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
  margin-top: 4px;
}
.chat__msg-time {
  font-size: 11px;
  color: var(--chat-dim);
  font-variant-numeric: tabular-nums;
}
.chat__msg-tick {
  display: inline-flex;
  color: var(--chat-accent);
}
.chat__msg-tick svg { width: 15px; height: 12px; }
/* CHAT-VINKJES-VERSTUURD-AANGEKOMEN-GELEZEN — three states, same metrics.
   In flight is dim (not an achievement yet), landed keeps the accent, read is
   an explicit blue that stays legible on the bubble in Dag and in Nacht. */
.chat__msg-tick--sent { color: var(--chat-dim); }
.chat__msg-tick--delivered { color: var(--chat-accent); }
.chat__msg-tick--read { color: #2f9bdc; }
.chat__thumbs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 6px;
}
.chat__thumb {
  max-width: 160px;
  max-height: 160px;
  border-radius: var(--radius-md);
  display: block;
  object-fit: cover;
}
.chat__file {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  border-radius: var(--radius-pill);
  background: var(--chat-surface-2);
  border: 1px solid var(--chat-line);
  color: var(--chat-dim);
  font-size: 12px;
}
.chat__file svg { width: 14px; height: 14px; }

/* Action proposal card — two-step confirm for chat-proposed mutations
   (CHAT-TOOL-USE-MUTATIONS, v0.9.670). Tokens only, finish-invariant. */
.chat__action-card {
  margin-top: 8px;
  padding: 10px 12px;
  border-radius: var(--radius-lg);
  background: var(--chat-surface-2);
  border: 1px solid var(--chat-line);
}
.chat__action-summary {
  font-size: 13.5px;
  line-height: 1.4;
  color: var(--chat-ink);
  margin-bottom: 10px;
  /* A whole-workout card carries one line per exercise; without this the
     server's newlines collapse and twelve exercises become one wall of text. */
  white-space: pre-line;
}
.chat__action-btns {
  display: flex;
  gap: 8px;
}
.chat__action-btn {
  flex: 1 1 0;
  padding: 9px 12px;
  border-radius: var(--radius-pill);
  font-size: 13.5px;
  font-weight: 600;
  cursor: pointer;
  border: 1px solid var(--chat-line);
  transition: opacity var(--dur-fast) var(--ease-standard);
}
.chat__action-btn:active { opacity: 0.85; }
.chat__action-btn--yes {
  background: var(--chat-accent);
  color: var(--chat-on-accent);
  border-color: var(--chat-accent);
}
.chat__action-btn--no {
  background: transparent;
  color: var(--chat-dim);
}
.chat__action-note {
  font-size: 12.5px;
  color: var(--chat-dim);
  font-style: italic;
}

/* Typing indicator (assistant) */
.chat__typing {
  display: flex;
  gap: 8px;
  align-items: center;
}
/* CHAT-SASSU-DENKT-NA-MET-EEN-PAARDJE-EN-EEN-WOORDJE — one rotating word, no
   pulse. The old chat-status-pulse opacity loop lived on this very element; it
   now fights the 150ms word cross-fade on the child (an animation on the parent
   multiplies with a transition on the child and reads as a flicker), and the
   horse beside it already carries the "still alive" signal. */
.chat__typing-status {
  font-size: 0.8rem;
  color: var(--chat-dim);
  display: inline-flex;
  align-items: baseline;
}
.chat__typing-word {
  transition: opacity 150ms var(--ease-standard);
}
/* Fade only, never a slide: a moving word is harder to read than a dimming one,
   and the swap happens while it is invisible so no letter is ever mid-change. */
.chat__typing-word--out { opacity: 0; }
/* Elapsed seconds, from 10s on. Tabular figures so 9s → 10s does not shift the
   line, and dimmer than the word because it is the quieter of the two. */
.chat__typing-clock {
  opacity: 0.7;
  font-variant-numeric: tabular-nums;
  /* The gap lives here, not as a leading space in the text: .chat__typing-status
     is an inline-FLEX row, so the clock is a flex item and a leading space in it
     collapses away — measured, the separator sat flush against the ellipsis
     ("Planking…· 12s"). A margin on an empty clock costs nothing. */
  margin-left: 4px;
}
.chat__msg-stopped {
  font-size: 0.8rem;
  font-style: italic;
  color: var(--chat-dim);
  margin-top: 2px;
}
/* CHAT-SEND-RESILIENCE-RETRY-AND-FAILED-STATE — a send that exhausted its
   retries. The failure lives on the user's own bubble (tinted + labelled) with
   the Resend control inside it, so the typed text is never thrown away. */
.chat__msg--failed {
  outline: 1px solid var(--danger, #d9534f);
  outline-offset: 0;
}
/* CHAT-VERSTUREN-IS-EERLIJK — in-flight bubble. Muted until the outcome is known,
   so a message on its way never reads as a message that arrived. */
.chat__msg--sending {
  opacity: 0.62;
}
.chat__msg-sending {
  font-size: 0.8rem;
  color: var(--chat-dim);
  margin-top: 2px;
}
.chat__msg-failed {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 4px;
}
.chat__msg-failed-label {
  font-size: 0.8rem;
  color: var(--danger, #d9534f);
}
.chat__msg-resend {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 48px;
  padding: 0 14px;
  border: 1px solid var(--accent);
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}
.chat__msg-resend svg { width: 18px; height: 18px; }
/* CHAT-RESEND-VISIBLE-FEEDBACK-AND-CLASSIFIED-REASON — the tap has to show itself
   BEFORE the outcome is known, and a second tap must be impossible. */
.chat__msg-resend--pending {
  opacity: 0.7;
  cursor: default;
}
.chat__msg-resend--pending svg {
  animation: chat-resend-spin 900ms linear infinite;
}
@keyframes chat-resend-spin {
  to { transform: rotate(360deg); }
}
/* Escalation chip — only after 3 identical failures, so it is absent in the golden
   path. Monochrome + no icon (no-emoji / icon policy). */
.chat__msg-escalate {
  display: inline-flex;
  align-items: center;
  min-height: 48px;
  padding: 0 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--ink-2);
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}
/* CHAT-EEN-BERICHT-BEWERK-JE-EN-STUUR-JE-OPNIEUW — the per-bubble actions row.
   Quiet by default: this is a repair gesture, not a primary action, so it never
   competes with the answer itself. */
.chat__msg-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 2px;
}
.chat__msg--user .chat__msg-actions { justify-content: flex-end; }
.chat__msg-edit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--chat-dim);
  cursor: pointer;
  opacity: 0.75;
}
.chat__msg-edit svg { width: 18px; height: 18px; }
.chat__msg-edit:hover,
.chat__msg-edit:focus-visible { opacity: 1; color: var(--ink-2); }
.chat__msg-regen {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--ink-2);
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
}
.chat__msg-regen svg { width: 16px; height: 16px; }
/* The bubble whose text is sitting in the composer right now. */
.chat__msg--editing {
  outline: 1px dashed var(--accent);
  outline-offset: 0;
  opacity: 0.7;
}
/* "Editing message · ✕" — directly above the composer, where the text is. */
.chat__editbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 6px 14px;
  border-top: 1px solid var(--line);
  background: var(--surface);
}
.chat__editbar[hidden] { display: none; }
.chat__editbar-label {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--ink-2);
}
.chat__editbar-cancel {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--ink-2);
  cursor: pointer;
}
.chat__editbar-cancel svg { width: 18px; height: 18px; }

/* ── CHAT-EEN-BERICHT-BEWERK-JE-OF-HAAL-JE-WEG ────────────────────────────────
   Hold a bubble in a human thread and a small menu appears. Every colour below
   is a --chat-* or a global theme token, so this surface follows Dag and Nacht
   like the rest of the chat (see the note at the top of chat/index.js: these
   tokens are NOT finish-invariant — verify BOTH presets when changing this). */

/* "bewerkt · 16:07" — the quiet word before the clock, never louder than it. */
.chat__msg-edited {
  font-size: 11px;
  color: var(--chat-dim);
  opacity: 0.85;
}
.chat__msg-edited::after { content: "·"; margin-left: 4px; }

/* A message its sender took back. The bubble stays (the conversation keeps its
   shape) but it is plainly no longer a message: italic, faded, no ticks. */
.chat__msg--deleted {
  background: transparent;
  border: 1px dashed var(--line);
}
.chat__msg-text--deleted {
  font-style: italic;
  color: var(--chat-dim);
}

/* The veil is what closes the menu on a tap outside, so it covers the viewport —
   and it stays see-through, because the message the menu is about has to remain
   readable behind it. */
.chat__msgmenu-veil {
  position: fixed;
  inset: 0;
  z-index: 2400;
  background: transparent;
}
.chat__msgmenu {
  position: fixed;
  min-width: 180px;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.28);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.chat__msgmenu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  padding: 0 10px;
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 0.9rem;
  text-align: left;
  cursor: pointer;
}
.chat__msgmenu-item svg { width: 18px; height: 18px; flex: 0 0 auto; }
.chat__msgmenu-item:hover,
.chat__msgmenu-item:focus-visible { background: var(--bg); }
.chat__msgmenu-item--danger { color: var(--danger); }
/* The confirmation asks inside the menu the press already opened — a second
   window would move the question away from the message it is about. */
.chat__msgmenu-ask {
  padding: 8px 10px 4px;
  font-size: 0.8rem;
  color: var(--ink-2);
}

/* ── CHAT-EEN-HARTJE-MET-EEN-DUBBELE-TIK ──
   The six live on TOP of the menu, in a row, because they are the one thing you
   can do to any message — mine or not — while Edit and Delete only exist on half
   of them. A row rather than six more list items: they are one choice, not six
   verbs, and six full-width rows would push the verbs off a short screen. */
.chat__msgmenu-reactions {
  display: flex;
  gap: 2px;
  padding: 2px 2px 6px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 4px;
}
.chat__msgmenu-reaction {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--ink-2);
  cursor: pointer;
}
.chat__msgmenu-reaction svg { width: 20px; height: 20px; }
.chat__msgmenu-reaction:hover,
.chat__msgmenu-reaction:focus-visible { background: var(--bg); }
/* The one already on is marked, not merely highlighted on hover: it is the
   difference between "pick one" and "you picked this one". */
.chat__msgmenu-reaction--picked {
  color: var(--accent);
  background: var(--bg);
}

/* Under the bubble, on the side the bubble does NOT hang from: my own message
   sits right, so its reaction sits bottom-left, and the other way round. That
   keeps the pill toward the middle of the screen, away from the edge, on both
   sides of the conversation. Never a counter — one icon per person, at most. */
.chat__msg-reactions {
  display: flex;
  gap: 3px;
  margin-top: 5px;
  margin-bottom: -2px;
}
.chat__msg--user .chat__msg-reactions { justify-content: flex-start; }
.chat__msg--assistant .chat__msg-reactions { justify-content: flex-end; }
.chat__msg-reaction {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 999px;
  background: var(--chat-surface);
  color: var(--chat-dim);
  animation: chat-reaction-pop 180ms ease-out;
}
.chat__msg--assistant .chat__msg-reaction { background: var(--chat-surface-2); }
.chat__msg-reaction svg { width: 14px; height: 14px; }
.chat__msg-reaction--mine { color: var(--chat-accent); }
@keyframes chat-reaction-pop {
  from { transform: scale(0.6); }
  to   { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .chat__msg-reaction { animation: none; }
}

/* CHAT-SASSU-DENKT-NA-MET-EEN-PAARDJE-EN-EEN-WOORDJE — the three dots are gone;
   the brand horse waits instead, drifting slowly through cool tints.
   The emblem ships as a PNG with an alpha channel and there is no SVG of it.
   That is fine and needs no new asset: used as a MASK the alpha becomes the
   shape and `background-color` becomes the colour, so a bitmap is fully
   recolourable — and an animated background-color is what makes the drift. */
.chat__typing-horse {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  /* Also the reduced-motion and no-color-mix resting colour. */
  background-color: var(--chat-accent);
  -webkit-mask-image: url('/assets/img/brand/horse-head-alpha-512.png');
  mask-image: url('/assets/img/brand/horse-head-alpha-512.png');
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  /* ease-in-out, NOT --ease-standard: that token is a sharp decelerate meant for
     one-shot entrances, and on an 8s infinite loop it lands with a visible jolt
     at every wrap. An ambient drift wants a symmetric curve. */
  animation: chat-horse-drift 8s ease-in-out infinite;
}
/* Four cool stops and one slow breath. Every stop is a THEME token, never the
   brand hex quartet the brief named: app.css outside :root is hex-linted (the
   chat block's own header records that its brand-moment exemption is gone), and
   six finishes ship — a fixed frost-white would have been invisible on Dag's and
   Linen's white, and a fixed ice-blue would be off-palette in Volt and Pulse.
   So each finish drifts through ITS accent: --accent → --accent-2 (the lighter
   sibling every preset defines) → accent deepened toward the finish-invariant
   brand navy → back. Cool and legible in Nacht, legible in Dag, on-palette in
   all six. */
@keyframes chat-horse-drift {
  0%   { background-color: var(--chat-accent); transform: scale(1); }
  33%  { background-color: var(--accent-2); }
  50%  { transform: scale(1.06); }
  66%  { background-color: color-mix(in srgb, var(--chat-accent) 70%, var(--brand-navy)); }
  100% { background-color: var(--chat-accent); transform: scale(1); }
}

/* ── Pending-attachment strip (above the action row) ──────────────────────── */
.chat__attach-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 6px 14px 0;
}
/* `display: flex` above outranks the UA's `[hidden] { display: none }`, so the
   empty strip kept reserving its padding row (and read as VISIBLE to a test).
   CHAT-EEN-FOTO-PLAK-JE-GEWOON-IN-DE-CHAT: hidden means hidden. */
.chat__attach-strip[hidden] { display: none; }
.chat__attach-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 4px 4px 6px;
  border-radius: var(--radius-pill);
  background: var(--chat-surface);
  border: 1px solid var(--chat-line);
  font-size: 12px;
  color: var(--chat-dim);
  max-width: 200px;
}
.chat__attach-chip-thumb {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  object-fit: cover;
}
.chat__attach-chip-icon {
  display: inline-flex;
  width: 28px;
  height: 28px;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--chat-surface-2);
}
.chat__attach-chip-icon svg { width: 16px; height: 16px; }
.chat__attach-chip-name {
  max-width: 120px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chat__attach-chip-rm {
  appearance: none;
  background: transparent;
  border: 0;
  padding: 4px;
  margin: 0;
  min-width: 24px;
  min-height: 24px;
  color: var(--chat-dim);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.chat__attach-chip-rm svg { width: 16px; height: 16px; }

/* CHAT-EEN-FOTO-PLAK-JE-GEWOON-IN-DE-CHAT — 'n of 6' plus the re-open-the-shutter
   button, on its own line under the chips. Existing tokens only, no new colours. */
.chat__attach-meta {
  flex-basis: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 11px;
  color: var(--chat-dim);
}
.chat__attach-again {
  appearance: none;
  background: transparent;
  border: 1px solid var(--chat-line);
  border-radius: var(--radius-pill);
  padding: 3px 10px 3px 8px;
  min-height: 28px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--chat-accent);
  cursor: pointer;
}
.chat__attach-again svg { width: 15px; height: 15px; }

/* Drag-and-drop landing zone: one subtle dashed outline on the composer. The
   border-radius that used to live here is gone — the composer has its own now
   (CHAT-EEN-INVOERBALK-ZOALS-CHATGPT) and this rule would square the pill off the
   moment a file crossed it. An outline follows border-radius by itself. */
.chat__input.chat--dragover {
  outline: 2px dashed var(--chat-accent);
  outline-offset: -3px;
}

/* ── Inside the pill: [+] on the left, mic between the field and send ─────── */
/* CHAT-EEN-INVOERBALK-ZOALS-CHATGPT — the row of three big round buttons above
   the field is gone (bijlage · spreken · foto), and with it the 72px glowing
   push-to-talk circle. Both survivors are bare 34px icon targets sitting ON the
   pill; the comfortable tap area comes from the pill's own padding around them,
   not from a bigger circle. */
.chat__plus,
.chat__mic {
  appearance: none;
  background: transparent;
  border: 0;
  margin: 0;
  padding: 0;
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  cursor: pointer;
  color: var(--chat-ink);
  -webkit-user-select: none;
  user-select: none;
}
.chat__plus svg { width: 22px; height: 22px; }
.chat__plus[disabled] { opacity: 0.4; cursor: not-allowed; }
.chat__mic-icon {
  position: relative;
  display: inline-flex;
  width: 100%;
  height: 100%;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
}
.chat__mic-icon svg { width: 21px; height: 21px; }
.chat__plus:hover,
.chat__mic:hover .chat__mic-icon { background: var(--chat-surface); }
/* Recording fills the small mic in, so the control that started it is visibly
   the control that is holding it. */
.chat__mic--holding .chat__mic-icon,
.chat__mic--recording .chat__mic-icon {
  background: var(--chat-accent);
  color: var(--chat-on-accent);
}
/* Telemetry-only marker — the button stays fully interactive and soft-degrades
   at first press (toast after a real failure), never a silent dead button. */
/* Pulse ring while recording */
.chat__mic-icon::before {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  border: 2px solid var(--chat-accent);
  opacity: 0;
  pointer-events: none;
}
/* Pulse the moment the press lands — independent of capture starting, so the
   user always sees "I heard you" even on slow/denied devices. */
.chat__mic--holding .chat__mic-icon::before,
.chat__mic--recording .chat__mic-icon::before {
  animation: chat-mic-pulse var(--dur-pulse) var(--ease-standard) infinite;
}
@keyframes chat-mic-pulse {
  0%   { opacity: 0.7; transform: scale(0.9); }
  100% { opacity: 0;   transform: scale(1.4); }
}

/* ── Recording waveform — it TAKES the text field's place, never adds a row ── */
.chat__wave {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 40px;
  padding: 0 6px;
  cursor: pointer;
}
/* [hidden] is only a UA display rule and every class with its own `display`
   beats it silently — hence this line and its twin on .chat__busy. */
.chat__wave[hidden] { display: none; }
.chat__wave-bars {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2px;
  height: 24px;
}
.chat__wave-bar {
  flex: 1 1 0;
  min-width: 2px;
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--chat-accent);
  transform: scaleY(0.14);
  transform-origin: center;
}
/* The reduced-motion stand-in; the media query at the foot of this block swaps
   the two. Hidden by default so only one of them is ever on screen. */
.chat__wave-dot { display: none; }
.chat__wave-time {
  flex: 0 0 auto;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--chat-dim);
}

/* ── Transcribe wait — the pill says it is busy instead of going blank ─────── */
.chat__busy {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 40px;
  padding: 0 6px;
  font-size: 13px;
  color: var(--chat-dim);
}
.chat__busy[hidden] { display: none; }
.chat__busy-spinner {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid var(--chat-line);
  border-top-color: var(--chat-accent);
  animation: chat-busy-spin 0.8s linear infinite;
}
@keyframes chat-busy-spin { to { transform: rotate(360deg); } }

/* ── THE PILL (pinned below) ───────────────────────────────────────────────────
   CHAT-EEN-INVOERBALK-ZOALS-CHATGPT — .chat__input WAS a bar the width of the
   screen with a boxed field inside it; it IS the box now. The border moved from
   the field to the row, so [+] · field · mic · send sit in ONE rounded shape and
   the field itself is bare. `position: relative` stays put: .chat__skills-menu
   anchors to it with `bottom: 100%`.
   align-items: flex-end so a grown textarea pushes the pill up from the top and
   the two icons and the send button stay glued to the bottom line, level with
   the last line of text. */
.chat__input {
  position: relative;
  display: flex;
  align-items: flex-end;
  gap: 4px;
  margin: 6px 10px calc(8px + var(--chat-safe-bottom));
  padding: 4px 6px;
  background: var(--chat-surface-2);
  border: 1px solid var(--chat-line);
  /* 24px and NOT --radius-pill (999px) on purpose: at rest the row is 48px high, so
     24px already draws the exact capsule Jayy held up — but the textarea may grow to
     120px, and a 999px radius at that height is a stadium whose inward-curving edges
     would push [+] and the mic off their own corners. A fixed radius stays a capsule
     at one line and becomes a calm rounded rectangle when the message gets long. */
  border-radius: 24px;
  transition: border-color var(--dur-fast, 0.12s) var(--ease-standard, ease);
}
.chat__input:focus-within { border-color: var(--chat-accent); }
.chat__input-field {
  flex: 1 1 auto;
  min-width: 0;
  appearance: none;
  resize: none;
  background: transparent;
  border: 0;
  color: var(--chat-ink);
  font: inherit;
  font-size: 15px;
  line-height: 1.4;
  padding: 9px 4px;
  max-height: 120px;
  min-height: 40px;
  outline: 0;
}
/* Same trap as the attach strip: the rule above has no `display`, but `flex` is
   set by the UA for a textarea in a flex row — and paintComposerMode() hides this
   field by `hidden` while the waveform owns the pill, so spell it out. */
.chat__input-field[hidden] { display: none; }
.chat__input-field::placeholder { color: var(--chat-dim); }
.chat__send {
  appearance: none;
  border: 0;
  margin: 0;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--chat-accent);
  color: var(--chat-on-accent);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
}
.chat__send svg { width: 17px; height: 17px; }
.chat__send[disabled] { opacity: 0.5; cursor: not-allowed; }
/* Nothing to send: DIMMED, never hidden. The pill has to keep its shape under the
   thumb (ChatGPT shows the button too), and a `disabled` here would also disable
   the stop button — this same control cancels a running turn. */
.chat__send--idle { opacity: 0.4; }
/* Stop state of the send control (CHAT-CANCEL-AND-TRANSPARENT-STATUS). */
.chat__send--stop { background: var(--danger, #d9534f); }
.chat__send--stop svg { width: 16px; height: 16px; }

@media (prefers-reduced-motion: reduce) {
  /* The horse stands still in the accent colour — no drift, no breath — and the
     word does not cross-fade. The JS half (typingReducedMotion) also stops
     picking new words, so the line is set once and left alone. */
  .chat__typing-horse { animation: none; transform: none; background-color: var(--chat-accent); }
  .chat__typing-word { transition: none; }
  .chat__typing-status { opacity: 0.9; }
  .chat__mic--holding .chat__mic-icon::before,
  .chat__mic--recording .chat__mic-icon::before { animation: none; opacity: 0; }
  /* 24 bars jumping 60 times a second is exactly the motion this query asks us
     not to make. The bars go away and a single pulsing dot takes over — still a
     living "I am listening", but one shape instead of two dozen. The JS half
     (waveReducedMotion) skips the AnalyserMeter entirely, so nothing is being
     computed for a meter nobody sees. */
  .chat__wave-bars { display: none; }
  .chat__wave-dot {
    display: inline-block;
    width: 10px; height: 10px; flex: 0 0 auto;
    border-radius: 50%;
    background: var(--chat-accent);
    animation: chat-wave-breathe 1.6s var(--ease-standard, ease-in-out) infinite;
  }
  .chat__busy-spinner { animation: none; }
}
@keyframes chat-wave-breathe { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }

/* ── SLEEP-DURATION-TIMEPICKER — two-column wheel picker overlay ─────────── */
.fz-wheel-backdrop {
  position: fixed; inset: 0;
  background: rgba(0, 0, 0, 0.45);
  display: flex; align-items: flex-end; justify-content: center;
  z-index: 1000;
}
.fz-wheel-sheet {
  background: var(--surface);
  color: var(--ink);
  width: 100%; max-width: 480px;
  border-top-left-radius: var(--radius-lg);
  border-top-right-radius: var(--radius-lg);
  padding: 8px 12px 16px;
  box-shadow: var(--elev-up);
}
.fz-wheel-sheet__header {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px;
  padding: 8px 4px 12px;
}
.fz-wheel-sheet__title {
  flex: 1; text-align: center;
  font-weight: 600; font-size: 16px;
}
.fz-wheel-sheet__btn {
  background: transparent;
  border: none;
  font: inherit; font-weight: 600;
  padding: 8px 12px;
  border-radius: var(--radius-md);
  cursor: pointer;
}
.fz-wheel-sheet__btn--ghost { color: var(--muted); }
.fz-wheel-sheet__btn--accent {
  background: var(--accent);
  color: var(--on-accent);
}
.fz-wheel-sheet__body {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  gap: 8px;
  padding: 4px 0 8px;
}
.fz-wheel-sheet__indicator {
  position: absolute; left: 12px; right: 12px;
  top: 50%; height: 40px;
  transform: translateY(-50%);
  border-top: 1px solid var(--surface-2, rgba(127, 127, 127, 0.2));
  border-bottom: 1px solid var(--surface-2, rgba(127, 127, 127, 0.2));
  pointer-events: none;
}
.fz-wheel-col {
  --fz-wheel-item-h: 40px;
  height: 200px;
  width: 80px;
  overflow-y: auto;
  scroll-snap-type: y mandatory;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  text-align: center;
}
.fz-wheel-col::-webkit-scrollbar { display: none; }
.fz-wheel-col__pad { height: 80px; }
.fz-wheel-col__item {
  height: 40px;
  line-height: 40px;
  scroll-snap-align: center;
  font-size: 20px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  transition: color var(--dur-fast) var(--ease-standard), font-weight var(--dur-fast) var(--ease-standard);
}
.fz-wheel-col__item.is-selected {
  color: var(--ink);
  font-weight: 700;
}
.fz-wheel-col__suffix {
  font-size: 18px;
  color: var(--muted);
  font-weight: 600;
  align-self: center;
}

/* ───────── RECIPE-SHARE-VIA-COMPACT-URL-V1 — /r/:code landing ─────────
   Standalone (no tabbar) share view: recipe card + either a sticky Ja/Nee add
   bar (logged-in) or a create-account upsell (logged-out). Tokens only. */
.fz-share__sec {
  /* leave room for the sticky add bar so it never covers the last ingredient */
  padding-bottom: var(--space-7);
}
.fz-share__body {
  width: 100%;
}
.fz-share__from {
  margin: 0 0 var(--space-1);
  color: var(--ink-2);
  font-size: 13px;
  font-weight: 600;
}
.fz-share__title {
  margin: 0 0 var(--space-4);
  color: var(--ink);
  font-size: 24px;
  font-weight: 700;
  line-height: 1.2;
}
.fz-share__items {
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  overflow: hidden;
}
.fz-share__item {
  padding: var(--space-3) var(--space-4);
  color: var(--ink);
  font-size: 15px;
  border-bottom: 1px solid var(--line-2);
}
.fz-share__item:last-child {
  border-bottom: 0;
}
/* Sticky bottom add bar — logged-in receiver. */
.fz-share__bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  padding-bottom: calc(var(--space-4) + var(--safe-bottom));
  background: var(--surface);
  border-top: 1px solid var(--line);
}
.fz-share__barq {
  color: var(--ink);
  font-size: 15px;
  font-weight: 600;
  text-align: center;
}
.fz-share__baracts {
  display: flex;
  gap: var(--space-3);
}
.fz-share__baracts .btn {
  flex: 1;
}
/* Upsell card — logged-out visitor. */
.fz-share__upsell {
  width: 100%;
  margin-top: var(--space-5);
  padding: var(--space-5);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  text-align: center;
}
.fz-share__upsellcopy {
  margin: 0 0 var(--space-4);
  color: var(--ink);
  font-size: 15px;
  line-height: 1.4;
}
.fz-share__cta {
  display: inline-flex;
  width: 100%;
  justify-content: center;
}
/* "Deel recept" button on the recipe builder — icon + label inline. */
.nut-rb__share {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
}

/* ADMIN-3-WEEKLY-RATING-PROMPT — bottom-sheet star rating overlay. */
.rate-overlay {
  position: fixed;
  inset: 0;
  z-index: 9000;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(0, 0, 0, 0.45);
}
.rate-sheet {
  width: 100%;
  max-width: 520px;
  background: var(--surface);
  color: var(--ink);
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  box-shadow: var(--elev-up);
}
.rate-sheet__title {
  margin: 0;
  font-size: 18px;
  line-height: 1.3;
  text-align: center;
}
/* FUNNEL-D-POST-ENTRY-ACTIVATION — supporting copy line in the activation sheet. */
.rate-sheet__sub {
  margin: 0;
  font-size: 14px;
  line-height: 1.45;
  color: var(--muted);
  text-align: center;
}
.rate-sheet__stars {
  display: flex;
  justify-content: center;
  gap: var(--space-2);
}
.rate-star {
  background: transparent;
  border: 0;
  padding: var(--space-2);
  cursor: pointer;
  color: var(--muted);
  line-height: 0;
}
.rate-star svg { fill: currentColor; }
.rate-star.on { color: var(--accent); }
.rate-sheet__comment {
  width: 100%;
  padding: var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  color: var(--ink);
  font: inherit;
  resize: vertical;
}
.rate-sheet__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}
.rate-sheet__submit {
  flex: 1;
  padding: var(--space-3) var(--space-4);
  background: var(--accent);
  color: var(--on-accent);
  border: 0;
  border-radius: var(--radius-sm);
  font-weight: 600;
  cursor: pointer;
}
.rate-sheet__submit:disabled { opacity: 0.5; cursor: not-allowed; }
.rate-sheet__skip {
  background: transparent;
  border: 0;
  color: var(--muted);
  padding: var(--space-3);
  cursor: pointer;
  text-decoration: underline;
}

/* SESSIEFEEDBACK-DE-KLANT-GEEFT-EEN-CIJFER-NA-ZIJN-TRAINING — het blad een uur
   na de sessie. Leent de doos van .rate-sheet en zet er alleen het raster en de
   koptekst bovenop; een eigen blad zou een tweede plek zijn waar de randradius
   en de schaduw uit elkaar kunnen lopen. */
.sfb-sheet { gap: var(--space-3); }
.sfb-sheet__head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}
.sfb-sheet__titles { flex: 1 1 auto; min-width: 0; }
/* De kop staat hier LINKS en niet gecentreerd zoals bij de sterrenvraag: er
   staat een kruisje naast, en een gecentreerde kop naast een knop leest scheef. */
.sfb-sheet__titles .rate-sheet__title { text-align: left; }
.sfb-sheet__sub {
  margin: var(--space-1) 0 0;
  font-size: 13px;
  line-height: 1.4;
  color: var(--muted);
}
.sfb-sheet__x {
  flex: 0 0 auto;
  background: transparent;
  border: 0;
  color: var(--muted);
  padding: var(--space-1);
  line-height: 0;
  cursor: pointer;
}
.sfb-sheet__q {
  margin: 0;
  font-size: 15px;
  line-height: 1.4;
}
/* Elf vakjes op één regel, dus geen vaste breedte maar gelijke fracties: op een
   smalle telefoon krimpen ze mee in plaats van de rij te laten omklappen. */
.sfb-grid {
  display: grid;
  grid-template-columns: repeat(11, 1fr);
  gap: 4px;
}
.sfb-grid__cell {
  aspect-ratio: 1 / 1;
  min-width: 0;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface-card);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.sfb-grid__cell.on {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}
.sfb-grid__ends {
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  color: var(--muted);
}
.sfb-sheet__label {
  margin: var(--space-2) 0 0;
  font-size: 14px;
  line-height: 1.4;
}
/* Overslaan links, Niet meer vragen rechts — en de tweede bewust NIET rood of
   zwaarder: hij is een even geldige keuze als de eerste, geen waarschuwing. */
.sfb-sheet__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}
.sfb-sheet__mute { text-align: right; }
.sfb-sheet__note {
  margin: 0;
  font-size: 12px;
  line-height: 1.4;
  color: var(--muted);
  text-align: center;
}

/* FEEDBACK-SETTINGS-ENTRY-AND-SHEET — settings row lead icon + sheet chips. */
.set-row__lead {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  margin-right: var(--space-3);
  flex: 0 0 auto;
}
.set-row__lead svg { width: 20px; height: 20px; }
.fb-sheet__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.fb-chip {
  flex: 1 1 auto;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
}
.fb-chip.on {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
  font-weight: 600;
}
.fb-sheet__context {
  margin: 0;
  font-size: 12px;
  color: var(--muted);
  text-align: center;
  word-break: break-word;
}

/* PREMIUM-UPGRADE-UI-AND-SELF-SERVE — single reusable subscribe sheet. */
.premium-overlay {
  position: fixed;
  inset: 0;
  z-index: 9200;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(0, 0, 0, 0.5);
}
.premium-sheet {
  width: 100%;
  max-width: 520px;
  background: var(--surface);
  color: var(--ink);
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  box-shadow: var(--elev-up);
}
.premium-sheet__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}
.premium-sheet__header h2 {
  margin: 0;
  font-size: 20px;
  line-height: 1.2;
}
.premium-sheet__close {
  background: transparent;
  border: 0;
  color: var(--muted);
  padding: var(--space-2);
  cursor: pointer;
  line-height: 0;
}
.premium-sheet__close svg { width: 22px; height: 22px; }
.premium-sheet__lede {
  margin: 0;
  color: var(--muted);
}
.premium-sheet__benefits {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.premium-sheet__benefits li {
  position: relative;
  padding-left: var(--space-5);
}
.premium-sheet__benefits li::before {
  content: "";
  position: absolute;
  left: 6px;
  top: 0.55em;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
}
.premium-sheet__skus {
  display: flex;
  gap: var(--space-3);
  /* PLUS-PROEF-ZONDER-BETALING — de proefknop is een DERDE kind in deze rij en
     hoort op een eigen regel (zie .premium-sku--trial). Zonder wrap zou hij de
     twee prijskaarten samendrukken tot ze hun bedrag niet meer kwijt kunnen. */
  flex-wrap: wrap;
}
.premium-sku {
  flex: 1;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: var(--space-4) var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  color: var(--ink);
  cursor: pointer;
}
.premium-sku:disabled { opacity: 0.5; cursor: not-allowed; }
.premium-sku--annual {
  border-color: var(--accent);
}
/* PLUS-PROEF-ZONDER-BETALING — volle breedte onder de twee prijskaarten.
   Bewust GEEN gevulde accentknop: de jaarkaart draagt het accent al, en twee
   even luide knoppen naast elkaar laten de keuze juist trager voelen. Alleen
   een accentrand plus accenttekst, dus er is hier ook geen achtergrond waar
   --on-accent bij hoort. Het woord "gratis" is geen bedrag en krijgt daarom
   niet de 22px van .premium-sku__price. */
.premium-sku--trial {
  flex-basis: 100%;
  border-color: var(--accent);
  color: var(--accent);
}
.premium-sku--trial .premium-sku__price {
  font-size: 16px;
}
/* PAYMENTS-MOLLIE-MANDATE-FIRST-PAYMENT — the coach-subscription sheet reuses
   the premium sheet chrome but has ONE amount and ONE button, so the price is a
   read-only line and the button is a full-width primary rather than a chip in a
   SKU row. */
.pt-sub-sheet__price {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 6px;
  padding: var(--space-3) 0;
}
/* De eenmalige startbijdrage boven de knop: hij verklaart waarom het bedrag op
   die knop hoger is dan de termijnprijs erboven. */
.pt-sub-sheet__fee {
  margin: 0 0 var(--space-3); text-align: center; font-size: 13px; color: var(--muted);
}
.pt-sub-sheet__pay {
  width: 100%;
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
  font-weight: 700;
}
.premium-sku__price {
  font-size: 22px;
  font-weight: 700;
}
.premium-sku__interval {
  font-size: 13px;
  color: var(--muted);
}
.premium-sku__badge {
  position: absolute;
  top: -10px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 11px;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: var(--radius-sm);
}
.premium-sheet__loading {
  margin: 0;
  color: var(--muted);
}
/* DE-PRIJZEN-API-ACHTER-HET-SLOT — één btw-regel onder de bedragen, in
   dezelfde toon als de instellingenkaart die dat al zegt. Geen tweede plek. */
.premium-sheet__vat {
  /* .premium-sheet__skus is een flexrij met wrap; zonder een volle basis wordt
     deze regel een DERDE kolom naast de twee prijskaarten in plaats van een
     regel eronder. */
  flex: 0 0 100%;
  margin: 0;
  color: var(--muted);
  font-size: 12px;
  text-align: center;
}
/* PLUS-DECLAREERBAAR-BIJ-CZ-ALS-ONLINE-GEZONDHEIDSPROGRAMMA — de
   vergoedingsregel onder de btw-regel. Zelfde vorm als die regel (volle basis
   in de flexrij, anders wordt hij een derde kolom naast de prijskaarten), maar
   hij mag over twee regels lopen, dus line-height in plaats van kaal 12px. */
.premium-sheet__insurer {
  flex: 0 0 100%;
  margin: var(--space-2) 0 0;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.45;
  text-align: center;
}
.premium-sheet__insurer-link {
  color: var(--accent);
  text-decoration: underline;
}
/* PROEF-PLAFOND-IS-EEN-MAAND-PLUS — één regel onder de proefknop die zegt wat
   de gratis maand precies is. Zelfde vorm als de btw-regel (volle basis in de
   flexrij, anders wordt hij een derde kolom naast de prijskaarten). */
.premium-sheet__trialnote {
  flex: 0 0 100%;
  margin: var(--space-2) 0 0;
  color: var(--muted);
  font-size: 12px;
  text-align: center;
}
/* DE-KLEINE-LETTERS-KLOPPEN — de afstandsverklaring boven de prijskaarten.
   Een label met het vinkje links en de zin ernaast; de zin mag lang zijn en
   moet leesbaar blijven, dus geen 12px maar de gewone kleine maat. */
.premium-sheet__consent {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  cursor: pointer;
}
.premium-sheet__consent-box {
  flex: 0 0 auto;
  margin: 2px 0 0;
  width: 18px;
  height: 18px;
  accent-color: var(--accent);
  cursor: pointer;
}
.premium-sheet__consent-text {
  font-size: 13px;
  line-height: 1.45;
  color: var(--ink);
}
.premium-sheet__consent-link {
  color: var(--accent);
  text-decoration: underline;
  white-space: nowrap;
}
.premium-sheet__legal {
  color: var(--muted);
  font-size: 12px;
  text-align: center;
}

/* FASE-9-OPRAAK-SCHERM-DAT-BEWAART — the "your month of AI is gone" sheet.
   Reuses the .premium-overlay/.premium-sheet chrome wholesale; only the kept
   thumbnail and the three ways out are new. The thumbnail is the point of the
   screen, so it gets real size — a 40px avatar would not read as "your photo
   is safe". */
.fz-quota-sheet__kept {
  display: flex;
  justify-content: center;
}
.fz-quota-sheet__thumb {
  max-height: 168px;
  max-width: 100%;
  border-radius: var(--radius-sm);
  object-fit: cover;
  box-shadow: var(--elev-1, 0 1px 3px rgba(0, 0, 0, 0.2));
}
.fz-quota-sheet__preview {
  margin: 0;
  padding: var(--space-3);
  background: var(--surface-2, rgba(127, 127, 127, 0.12));
  border-radius: var(--radius-sm);
  font-style: italic;
  color: var(--ink);
}
.fz-quota-sheet__ways {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.fz-quota-way {
  width: 100%;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  border: 0;
  font: inherit;
  font-size: 15px;
  cursor: pointer;
  text-align: center;
}
.fz-quota-way--primary {
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
}
.fz-quota-way--secondary {
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--line, var(--muted));
}
.fz-quota-way--tertiary {
  background: transparent;
  color: var(--muted);
  text-decoration: underline;
  padding: var(--space-2);
}

/* The way-out-2 follow-up: one line, above the tab bar, offering to run what
   was kept. Never a modal — the user already chose to wait once. */
.fz-quota-banner {
  position: fixed;
  left: var(--space-3);
  right: var(--space-3);
  bottom: calc(var(--tabbar-h, 64px) + var(--space-3));
  z-index: 9100;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--elev-up);
  max-width: 520px;
  margin: 0 auto;
}
.fz-quota-banner__text { flex: 1; font-size: 14px; }
.fz-quota-banner__run {
  background: var(--accent);
  color: var(--on-accent);
  border: 0;
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.fz-quota-banner__close {
  background: transparent;
  border: 0;
  color: var(--muted);
  padding: var(--space-2);
  line-height: 0;
  cursor: pointer;
}
.fz-quota-banner__close svg { width: 18px; height: 18px; }

/* PREMIUM-UPSELL-REQUEST-FORM-INTERIM — access-request form body inside the
   reused .premium-sheet chrome. */
.premium-request__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.premium-request__textarea {
  width: 100%;
  padding: var(--space-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-weight: 400;
  min-height: 72px;
  resize: vertical;
}
.premium-request__submit { width: 100%; }
.premium-request__done {
  margin: 0;
  color: var(--muted);
}

/* SASSU-FOR-COACHES-WAITLIST — coming-soon + beta waitlist sheet. Reuses the
   .premium-overlay / .premium-sheet chrome; these rules style the form body. */
.coach-waitlist__hero {
  margin-bottom: var(--space-3);
}
.coach-waitlist__hero .lede {
  margin: 0 0 var(--space-1);
  font-weight: 600;
}
.coach-waitlist__hero .sub {
  margin: 0;
  color: var(--muted);
  font-size: 14px;
}
.coach-waitlist__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.coach-waitlist__form label {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: 14px;
  font-weight: 600;
}
.coach-waitlist__form input,
.coach-waitlist__form select,
.coach-waitlist__form textarea {
  width: 100%;
  padding: var(--space-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-weight: 400;
}
.coach-waitlist__form textarea {
  min-height: 72px;
  resize: vertical;
}
.coach-waitlist__form .honeypot {
  position: absolute;
  left: -9999px;
  opacity: 0;
  height: 0;
  width: 0;
}
.coach-waitlist__submit {
  width: 100%;
  margin-top: var(--space-1);
}
.coach-waitlist__error {
  margin: 0;
  color: var(--danger);
  font-size: 13px;
}
.coach-waitlist__success {
  text-align: center;
  padding: var(--space-4) var(--space-2);
}
.coach-waitlist__success h3 {
  margin: 0 0 var(--space-2);
}
.coach-waitlist__success p {
  margin: 0;
  color: var(--muted);
}

/* TRAINER-MARKETPLACE-BROWSABLE-PROFILES — the marketplace as a real screen
   (/marketplace + /marketplace/:slug), not a drawer. Single-column list: with
   one coach a grid would read as missing tiles, one full-width card reads as a
   complete list. All colour comes from tokens, so Dag and Nacht both hold. */
.mkt-page { padding-bottom: var(--space-6); }

.mkt-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* MARKTPLAATS-ZOEKEN-OP-WAT-IK-ZOEK — search + chips above the list, the same
   pill shape the public /trainers page uses, but drawn from tokens so Dag and
   Nacht both hold. The count line is never hidden: narrowing to zero has to
   read as narrowing, not as an empty marketplace. */
.mkt-filters {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.mkt-search {
  display: flex;
  align-items: center;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  padding: 0 var(--space-3);
}
.mkt-search__input {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-size: 14px;
  border: 0;
  background: transparent;
  color: var(--ink);
  padding: 12px 0;
  outline: none;
}
.mkt-filters__group {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.mkt-filters__label {
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
}
.mkt-filters__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}
.mkt-chipbtn {
  font: inherit;
  font-size: 12px;
  line-height: 1.6;
  padding: 5px 12px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink-2);
  cursor: pointer;
}
.mkt-chipbtn.is-on {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
}
.mkt-countbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}
.mkt-count {
  margin: 0;
  font-size: 12px;
  color: var(--muted);
}
.mkt-clear {
  font: inherit;
  font-size: 12px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  text-decoration: underline;
  cursor: pointer;
}
.mkt-cards {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.mkt-card {
  display: grid;
  grid-template-columns: 64px 1fr 20px;
  align-items: start;
  gap: var(--space-3);
  width: 100%;
  text-align: left;
  padding: var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  transition: border-color 140ms ease, transform 140ms ease;
}
.mkt-card:hover { border-color: var(--accent); }
.mkt-card:active { transform: scale(0.99); }

.mkt-card__avatar {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  object-fit: cover;
  flex: 0 0 auto;
}
.mkt-card__avatar--placeholder,
.mkt-hero__avatar--placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 700;
  font-size: 22px;
}
.mkt-card__body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.mkt-card__name {
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
}
.mkt-card__meta {
  font-size: 12px;
  color: var(--muted);
}
/* MARKTPLAATS-VOL-IS-EEN-EERLIJKE-STAND — a full trainer keeps his card, sits at
   the bottom of the list and carries the word instead of quietly disappearing. */
.mkt-card__full {
  margin-left: var(--space-1);
  padding: 1px 7px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-2, var(--surface));
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
  vertical-align: middle;
}
.mkt-card--full .mkt-card__avatar { opacity: 0.72; }
.mkt-card__pitch {
  margin: var(--space-1) 0 0;
  font-size: 13px;
  line-height: 1.45;
  color: var(--ink-2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.mkt-card__chev {
  display: inline-flex;
  align-self: center;
  color: var(--muted);
}
.mkt-card__chev svg { width: 20px; height: 20px; }

.mkt-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin-top: var(--space-1);
}
.mkt-chip {
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: var(--surface-muted, var(--line));
  border: 1px solid var(--line);
  color: var(--ink-2);
  font-size: 11px;
  line-height: 1.6;
}

.mkt-note {
  margin: var(--space-2) 0 0;
  font-size: 12px;
  color: var(--muted);
  text-align: center;
}

/* Loading skeleton — structure while the fetch runs, never a blank screen. */
.mkt-skel {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.mkt-skel__row {
  height: 96px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--line);
  background: var(--surface);
  opacity: 0.6;
  animation: mkt-skel-pulse 1.4s ease-in-out infinite;
}
@keyframes mkt-skel-pulse {
  0%, 100% { opacity: 0.4; }
  50%      { opacity: 0.75; }
}

/* Empty / error / success — always carries the next action. */
.mkt-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  text-align: center;
  padding: var(--space-6) var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.mkt-state__title {
  margin: 0;
  font-size: 16px;
  font-weight: 700;
  color: var(--ink);
}
.mkt-state__body {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted);
  max-width: 34ch;
}
.mkt-state__action { margin-top: var(--space-2); }

/* Profile page. */
.mkt-profile {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.mkt-hero {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.mkt-hero__avatar {
  width: 88px;
  height: 88px;
  border-radius: 50%;
  object-fit: cover;
  flex: 0 0 auto;
}
.mkt-hero__ident {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.mkt-hero__name {
  margin: 0;
  font-size: 20px;
  font-weight: 700;
  color: var(--ink);
}
.mkt-hero__city {
  font-size: 13px;
  color: var(--muted);
}
/* TRAINERSPROFIEL-LEEFTIJD-EN-JAREN-ACTIEF — rendered only when the trainer
   filled at least one of the two in, so this never reserves empty space. */
.mkt-hero__facts {
  font-size: 13px;
  color: var(--muted);
}

.mkt-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.mkt-section__title {
  margin: 0;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
.mkt-section__body {
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink);
  white-space: pre-line;
}

/* TRAINERSPROFIEL-MIJN-CHALLENGES — title left, own result right. One row per
   challenge, no rank column: there is nothing to compare against on purpose. */
.mkt-ch-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.mkt-ch {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
}
.mkt-ch__title {
  font-size: 14px;
  color: var(--ink);
}
.mkt-ch__result {
  font-size: 14px;
  font-weight: 700;
  color: var(--ink);
  white-space: nowrap;
}

.mkt-lead {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.mkt-lead__label {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: 13px;
  color: var(--muted);
}
.mkt-lead__msg {
  width: 100%;
  min-height: 84px;
  padding: var(--space-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface-bg, var(--surface));
  color: var(--ink);
  font: inherit;
  resize: vertical;
}
.mkt-lead__error {
  margin: 0;
  font-size: 13px;
  color: var(--danger);
}
.mkt-lead__book { width: 100%; }

@media (prefers-reduced-motion: reduce) {
  .mkt-card { transition: none; }
  .mkt-card:active { transform: none; }
  .mkt-skel__row { animation: none; opacity: 0.55; }
}

/* Settings Premium card — status + manage / upgrade. */
.premium-card__upgrade {
  width: 100%;
  margin-top: var(--space-2);
}
.premium-status {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.premium-status__line {
  margin: 0;
  font-weight: 600;
}
/* COACH-WORDEN-IS-EEN-EIGEN-DEUR — de herkomstregel onder het gratis jaar. Een
   toelichting, geen tweede mededeling: lichter en kleiner dan de regel erboven. */
.premium-status__sub {
  margin: calc(var(--space-2) * -1) 0 0;
  font-size: 0.875rem;
  color: var(--muted);
}
.premium-status__cancel {
  align-self: flex-start;
}

/* PLUS-CODE-EEN-CODE-GEEFT-GRATIS-PLUS — "Heb je een code?".
   Bewust bescheiden: één veld, één knop, en pas bij succes één regel in de
   accentkleur. Het grote feestscherm is deel 2; hier hoort een cadeau te
   voelen als een cadeau zonder het scherm over te nemen. */
.plus-code {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3) 0 0;
}
.plus-code__row {
  display: flex;
  gap: var(--space-2);
  /* stretch, niet center: veld en knop komen uit twee verschillende
     stijlfamilies en zijn een paar pixels verschillend hoog. */
  align-items: stretch;
}
.plus-code__input {
  flex: 1 1 auto;
  min-width: 0;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
/* De basisregel .btn draagt width:100% — honderden knoppen leunen daarop, dus
   die blijft. In een flex-rij is die 100% wel de flex-basis van de knop, en dan
   houdt hij de hele rij bezet terwijl het veld tot nul krimpt. Alleen hier
   terug naar auto, zodat de knop zo breed is als zijn eigen tekst. */
.plus-code__btn {
  width: auto;
  flex: 0 0 auto;
  white-space: nowrap;
  padding: 14px 18px;
}
.plus-code__msg {
  margin: 0;
  font-size: 0.875rem;
  color: var(--muted);
}
.plus-code__msg--err {
  color: var(--danger, #c0392b);
}
.plus-code__msg--ok {
  color: var(--accent);
  font-weight: 600;
  font-size: 1rem;
}
/* Een rustige fade, geen confetti. */
.plus-code__msg--in {
  animation: plus-code-in 420ms ease-out both;
}
@keyframes plus-code-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .plus-code__msg--in { animation: none; }
}

/* PLUS-OPZEGGEN-EN-TERUGKOMEN — één regel per gekocht onderdeel. Rustig: dit is
   een opsomming van wat je hebt, geen actielijst, dus alleen het kruisje van
   een add-on is aanraakbaar. */
.premium-status__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border);
}
.premium-status__item-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.premium-status__item-note {
  color: var(--muted);
  font-size: 0.85rem;
}
.premium-status__item-drop {
  flex: none;
  width: 32px;
  height: 32px;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}
.premium-status__item-drop:disabled { opacity: 0.5; cursor: default; }
.premium-status__reactivate {
  align-self: flex-start;
}

/* DE-PROEF-KAN-PLUS-KOPEN — de koopknop onder een proef en onder een
   opzegging. Zelfde maat en uitlijning als de heropen-knop ernaast, want het
   zijn twee wegen naar hetzelfde: Plus houden. */
.premium-status__buy {
  align-self: flex-start;
}

/* BETAALMETHODE-WIJZIGEN-KAN-GEWOON — de knop met zijn uitlegzin als één blok.
   De zin MOET onder de knop blijven staan (align-self: stretch, geen
   flex-start): op 390px breed is "Je betaalt meteen je eerstvolgende termijn"
   twee regels, en die twee regels zijn de hele reden dat deze knop eerlijk is. */
.premium-status__paymethod {
  align-self: stretch;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-top: var(--space-2);
}
.premium-status__paymethod-btn { align-self: flex-start; }
.premium-status__paymethod-sub {
  margin: 0;
  font-size: 13px;
  line-height: 1.45;
  color: var(--muted);
}

/* PLUS-DECLAREERBAAR-BIJ-CZ-ALS-ONLINE-GEZONDHEIDSPROGRAMMA — de
   declaratiefactuur als knop met zijn uitlegzin eronder. Zelfde vorm als het
   betaalmethode-blok hierboven, om dezelfde reden: de zin eronder is op 390px
   twee regels en mag niet naast de knop belanden. Het is een <a>, dus
   text-decoration/line-height moeten expliciet, anders erft hij de link-stijl
   van de pagina en ziet de knop eruit als een zin. */
.premium-status__insurer {
  align-self: stretch;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-top: var(--space-2);
}
.premium-status__insurer-btn {
  align-self: flex-start;
  text-decoration: none;
}
.premium-status__insurer-sub {
  margin: 0;
  font-size: 13px;
  line-height: 1.45;
  color: var(--muted);
}

/* Return-from-Mollie verifying overlay. */
.premium-verify {
  position: fixed;
  inset: 0;
  z-index: 9300;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  background: rgba(0, 0, 0, 0.55);
}
.premium-verify__card {
  background: var(--surface);
  color: var(--ink);
  border-radius: var(--radius-sm);
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  text-align: center;
}
.premium-verify__spinner {
  width: 36px;
  height: 36px;
  border: 3px solid var(--line);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: premium-spin var(--dur-loop) linear infinite;
}
.premium-verify__msg { margin: 0; }
@keyframes premium-spin { to { transform: rotate(360deg); } }

/* BETALING-KOMT-THUIS — de terugkeer uit Mollie is een PAGINA, geen overlay
   meer: hij heeft een adres (/premium/confirm) en een eigen uitgang. De kaart
   blijft dezelfde kaart, alleen gecentreerd in de pagina in plaats van boven
   een scherm waar de gebruiker toch niets mee kan tot hij weet of het gelukt is. */
.premium-confirm {
  min-height: 60vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
}
.premium-confirm .premium-verify__card { width: 100%; max-width: 26rem; }
.premium-confirm__title { font-size: 20px; line-height: 1.25; margin: 0; color: var(--ink); }
.premium-confirm__cta {
  display: block; width: 100%; padding: 14px var(--space-4);
  border: 0; border-radius: var(--radius);
  background: var(--accent); color: var(--on-accent);
  font: inherit; font-weight: 700; cursor: pointer;
}

/* ADMIN-4-MANUAL-PROMPTS-PUSH — centered modal overlay for admin questions. */
.admin-prompt-overlay {
  position: fixed;
  inset: 0;
  z-index: 9100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  background: rgba(0, 0, 0, 0.55);
}
.admin-prompt-sheet {
  width: 100%;
  max-width: 520px;
  background: var(--surface-card);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  box-shadow: var(--elev-3);
}
.admin-prompt-sheet__question {
  margin: 0;
  font-size: 20px;
  line-height: 1.35;
  font-weight: 600;
}
.admin-prompt-sheet__answer {
  width: 100%;
  padding: var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  resize: vertical;
}
.admin-prompt-sheet__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}
.admin-prompt-sheet__submit {
  flex: 1;
  padding: var(--space-3) var(--space-4);
  background: var(--accent);
  color: var(--on-accent);
  border: 0;
  border-radius: var(--radius-sm);
  font-weight: 600;
  cursor: pointer;
}
.admin-prompt-sheet__submit:disabled { opacity: 0.5; cursor: not-allowed; }
/* ADMIN-EENMALIGE-MEDEDELING-AAN-ALLE-GEBRUIKERS — mededeling heeft geen
   tekstvak en geen tweede knop, dus de Oké-knop mag de hele rij vullen. */
.admin-prompt-sheet--announcement .admin-prompt-sheet__row { justify-content: stretch; }
.admin-prompt-sheet--announcement .admin-prompt-sheet__submit { width: 100%; }
.admin-prompt-sheet__later {
  background: transparent;
  border: 0;
  color: var(--muted);
  padding: var(--space-3);
  cursor: pointer;
  text-decoration: underline;
}

/* ADMIN-8-PAYMENT-FLOW-POLISH-AND-FAQ — /beta/pay + /beta/confirm polish. */
.beta-pay { padding: var(--space-4); max-width: 480px; margin: 0 auto; }
.beta-pay-logo { display:block; width:96px; height:96px; margin:0 auto var(--space-3); }
.beta-pay-intro { margin: 0 0 var(--space-3); }
.beta-pay-wave { margin: 0 0 var(--space-2); }
.beta-pay-perks { padding-left: var(--space-4); margin: 0 0 var(--space-3); color: var(--ink); }
.beta-pay-perks li { margin-bottom: var(--space-1); }
.beta-pay-price { font-weight:600; margin: 0 0 var(--space-3); }
.beta-pay-testmode { background: var(--warn-bg, #fff8c4); color: var(--ink); border: 1px solid var(--warn-line, #e6c200); padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); margin-bottom: var(--space-3); }
.beta-pay-status { margin: var(--space-2) 0 0; }
.beta-pay-mailto { display:inline-block; margin-top: var(--space-2); color: var(--accent); }
.beta-pay-celebrate { text-align:center; padding-top: 25vh; }
.beta-pay-celebrate h1 { color: var(--accent); }

/* DONATE-FLOW — voluntary donation surface (/donate + /donate/confirm). */
.donate { padding: var(--space-4); max-width: 520px; margin: 0 auto; }
.donate-back-btn { background: transparent; color: var(--ink); padding: var(--space-1) var(--space-2); margin-bottom: var(--space-2); }
.donate-hero-img { width: 100%; max-height: 260px; object-fit: cover; object-position: center 30%; border-radius: var(--radius-lg); margin-bottom: 1rem; }
.donate-settings-cta { display: flex; align-items: center; gap: 0.875rem; width: 100%; padding: 1rem 1.25rem; background: var(--accent); color: var(--on-accent); border: none; border-radius: var(--radius-lg); cursor: pointer; text-align: left; transition: opacity var(--dur-fast) var(--ease-standard); }
.donate-settings-cta:active { opacity: 0.82; }
.donate-settings-cta svg { flex-shrink: 0; width: 1.75rem; height: 1.75rem; }
.donate-settings-cta__text { display: flex; flex-direction: column; gap: 0.2rem; flex: 1; }
.donate-settings-cta__label { font-weight: 700; font-size: 1rem; line-height: 1.2; }
.donate-settings-cta__sub { font-size: 0.8rem; opacity: 0.8; }
.donate-title { margin: 0 0 var(--space-2); color: var(--ink); }
.donate-subtitle { margin: 0 0 var(--space-3); }
.donate-feedback { display: flex; flex-direction: column; gap: var(--space-2); margin: var(--space-3) 0 var(--space-4); }
.donate-feedback__intro { color: var(--ink); font-size: 0.95em; line-height: 1.5; max-width: 32rem; margin: 0 0 var(--space-2); }
.donate-feedback__label { font-size: 0.85rem; color: var(--muted); font-weight: 500; }
.donate-feedback__input { width: 100%; padding: var(--space-2); border-radius: var(--radius-sm); border: 1px solid var(--line); background: var(--surface); color: var(--ink); font-size: 1rem; line-height: 1.4; resize: vertical; font-family: inherit; }
.donate-feedback__input:focus { outline: none; border-color: var(--accent); }
.donate-feedback__submit { align-self: flex-start; }
.donate-feedback__status { font-size: 0.8rem; color: var(--muted); font-style: italic; min-height: 1.2em; margin: 0; }
.donate__hero-stats { font-size: 0.875rem; color: var(--muted); margin-top: 0.75rem; line-height: 1.5; }
.donate-testmode { background: var(--warn-bg, var(--surface-card)); color: var(--ink); border: 1px solid var(--warn-line, var(--line)); padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); margin-bottom: var(--space-3); }
.donate-tier { padding: 1rem; margin-bottom: 0.75rem; display: flex; flex-direction: column; gap: 0.5rem; border-radius: var(--radius-lg); }
.donate-tier__product-img { display: block; width: 96px; height: 96px; object-fit: contain; margin: 0 auto 0.25rem; }
.donate-tier__head { display: flex; align-items: center; gap: var(--space-2); }
.donate-tier__label { font-weight: 600; color: var(--ink); }
.donate-tier__desc { font-size: 0.85rem; color: var(--muted); margin: 0; }
.donate-amount-input { width: 100%; padding: var(--space-2); border-radius: var(--radius-sm); border: 1px solid var(--line); background: var(--surface); color: var(--ink); font-size: 1rem; }
.donate-tier__cta { background: var(--accent); color: var(--on-accent); border: 0; padding: 0.6rem 1rem; border-radius: var(--radius-sm); font-weight: 600; cursor: pointer; }
.donate-tier__cta[disabled] { opacity: 0.6; }
.donate-confirm { text-align: center; padding: 1.5rem; }
.donate-confirm__img { width: 100%; border-radius: var(--radius-lg); margin-bottom: 1.5rem; }
.donate-confirm__headline { font-size: 1.4rem; font-weight: 700; margin-bottom: 0.75rem; color: var(--ink); }
.donate-confirm__body { color: var(--muted); margin-bottom: 1.5rem; }
.donate-confirm__back { background: var(--accent); color: var(--on-accent); border: 0; padding: 0.7rem 1.2rem; border-radius: var(--radius-sm); font-weight: 600; cursor: pointer; }

/* ADMIN-8 — dashboard summary cards row. */
.admin-summary-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 16px; }
.admin-summary-card { background: var(--surface-card, #fff); border: 1px solid var(--line, #e5e7eb); border-radius: var(--radius-md); padding: 14px; text-decoration: none; color: inherit; display: block; }
.admin-summary-card:hover { border-color: var(--accent, #c3e600); }
.admin-summary-card .label { font-size: 12px; color: var(--muted, #777); text-transform: uppercase; letter-spacing: 0.04em; }
.admin-summary-card .value { font-size: 28px; font-weight: 700; margin-top: 4px; }
.admin-summary-card .sub { font-size: 11px; color: var(--muted, #777); margin-top: 4px; }

/* USER-SKILLS-SYSTEM-PHASE-1 — My Skills settings list, editor modal, and the
   chat slash-trigger autocomplete menu. Tokens only (no hex literals). */
.settings-skills__list { list-style: none; margin: 0; padding: 0; }
.settings-skills__item {
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
}
.settings-skills__item-main { flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.settings-skills__item-name { font-size: 15px; font-weight: 600; color: var(--ink); }
.settings-skills__item-trigger { font-size: 13px; color: var(--accent); font-family: ui-monospace, monospace; }
.settings-skills__item-desc { font-size: 13px; color: var(--muted); }
.settings-skills__item-actions { flex: 0 0 auto; display: flex; gap: var(--space-2); }
.settings-skills__edit,
.settings-skills__delete {
  -webkit-appearance: none; appearance: none; background: transparent;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 6px 12px; font: inherit; font-size: 13px; font-weight: 600;
  color: var(--ink); cursor: pointer;
}
.settings-skills__delete { color: var(--danger); border-color: var(--danger); }
.settings-skills__add {
  -webkit-appearance: none; appearance: none; width: 100%;
  background: transparent; border: 0; padding: var(--space-3) var(--space-4);
  font: inherit; font-size: 15px; font-weight: 600; color: var(--accent);
  text-align: left; cursor: pointer;
}

.skills-editor__overlay {
  position: fixed; inset: 0; z-index: 1000;
  background: rgba(0, 0, 0, 0.5);
  display: flex; align-items: flex-end; justify-content: center;
}
.skills-editor {
  width: 100%; max-width: 560px; max-height: 90vh; overflow-y: auto;
  background: var(--surface-card); border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  padding: var(--space-5) var(--space-4)
    calc(var(--space-5) + var(--safe-bottom));
  display: flex; flex-direction: column; gap: var(--space-3);
}
@media (min-width: 600px) {
  .skills-editor__overlay { align-items: center; }
  .skills-editor { border-radius: var(--radius-lg); }
}
.skills-editor__title { margin: 0; font-size: 18px; font-weight: 700; color: var(--ink); }
.skills-editor__examples { display: flex; flex-direction: column; gap: var(--space-2); }
.skills-editor__examples-label { margin: 0; font-size: 12px; color: var(--section-label); text-transform: uppercase; letter-spacing: 0.05em; }
.skills-editor__examples-row { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.skills-editor__example {
  -webkit-appearance: none; appearance: none;
  background: transparent; border: 1px solid var(--line); border-radius: var(--radius-pill);
  padding: 6px 14px; font: inherit; font-size: 13px; color: var(--ink); cursor: pointer;
}
.skills-editor__field { display: flex; flex-direction: column; gap: 4px; }
.skills-editor__field-label { font-size: 13px; font-weight: 600; color: var(--ink); }
.skills-editor__field-input {
  width: 100%; box-sizing: border-box;
  background: var(--surface-app, transparent);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 10px 12px; font: inherit; font-size: 15px; color: var(--ink);
}
textarea.skills-editor__field-input { resize: vertical; min-height: 120px; }
.skills-editor__error { margin: 0; font-size: 13px; color: var(--danger); }
.skills-editor__preview {
  margin: 0; white-space: pre-wrap; word-break: break-word;
  background: var(--surface-app, transparent); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 12px; font-size: 13px; color: var(--muted);
  max-height: 240px; overflow-y: auto;
}
.skills-editor__actions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2); }
.skills-editor__preview-btn,
.skills-editor__cancel {
  -webkit-appearance: none; appearance: none; background: transparent;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 10px 18px; font: inherit; font-weight: 600; color: var(--ink); cursor: pointer;
}
.skills-editor__save {
  -webkit-appearance: none; appearance: none; margin-left: auto;
  background: var(--accent); color: var(--on-accent); border: 0;
  border-radius: var(--radius-sm); padding: 10px 18px; font: inherit; font-weight: 600; cursor: pointer;
}
.skills-editor__save:disabled { opacity: 0.6; cursor: default; }

/* Chat slash-trigger autocomplete — floats above the input row. */
.chat__skills-menu {
  position: absolute; left: var(--space-3); right: var(--space-3); bottom: 100%;
  margin-bottom: var(--space-2); z-index: 60;
  background: var(--surface-card); border: 1px solid var(--line);
  border-radius: var(--radius-lg); overflow: hidden;
  box-shadow: var(--elev-2);
}
.chat__skills-menu[hidden] { display: none; }
.chat__skill-item {
  display: flex; flex-direction: column; gap: 2px;
  padding: 10px var(--space-3); cursor: pointer;
}
.chat__skill-item + .chat__skill-item { border-top: 1px solid var(--line); }
.chat__skill-item.is-active { background: var(--accent); }
.chat__skill-item.is-active .chat__skill-name,
.chat__skill-item.is-active .chat__skill-trigger,
.chat__skill-item.is-active .chat__skill-desc { color: var(--on-accent); }
.chat__skill-name { font-size: 14px; font-weight: 600; color: var(--ink); }
.chat__skill-trigger { font-size: 12px; color: var(--accent); font-family: ui-monospace, monospace; }
.chat__skill-desc { font-size: 12px; color: var(--muted); }

/* STRENGTH-REST-TIMER-WORKOUT-DONE-BUTTON-USE-TARGET — primary action under the
   TARGET pill in the fullscreen rest-timer overlay; commits target as rest. */
.rt-full__done-btn {
  margin-top: var(--space-3, 12px);
  padding: var(--space-3, 12px) var(--space-4, 16px);
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: var(--radius-lg);
  font-family: inherit;
  font-size: 1em;
  font-weight: 600;
  min-height: 48px;
  cursor: pointer;
}
.rt-full__done-btn:active { opacity: 0.85; }

/* ─────────────────────────────────────────────────────────────────────────
 * DIARY-MOOD-CHAT-PREMIUM-BEST-SELF-JOURNAL — premium AI mood check-in.
 * Button on the Check-in mood section + the guided-conversation sheet.
 * Tokens only, no hardcoded hex (css-hex-lint rule).
 * ───────────────────────────────────────────────────────────────────────── */
.mood-ai-chat-btn {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  margin: 10px 0 8px;
  padding: 12px 14px;
  background: var(--accent-subtle);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-size: 15px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.mood-ai-chat-btn:active { opacity: 0.85; }
.mood-ai-chat-btn__icon { display: inline-flex; flex: 0 0 auto; }
.mood-ai-chat-btn__icon svg { width: 20px; height: 20px; color: var(--accent); }
.mood-ai-chat-btn__label { flex: 1 1 auto; }
.mood-ai-chat-btn__hint { color: var(--muted); font-weight: 500; font-size: 12px; }

/* Conversation sheet */
.mood-chat-overlay {
  position: fixed; inset: 0; z-index: 1200;
  display: flex; align-items: flex-end; justify-content: center;
  background: rgba(0, 0, 0, 0.55);
}
.mood-chat-sheet {
  width: 100%; max-width: 560px;
  max-height: 88vh;
  display: flex; flex-direction: column;
  background: var(--surface);
  border-top-left-radius: var(--radius-lg); border-top-right-radius: var(--radius-lg);
  border: 1px solid var(--line);
  overflow: hidden;
}
.mood-chat-sheet__head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 16px;
  border-bottom: 1px solid var(--line);
}
.mood-chat-sheet__head h2 { margin: 0; font-size: 16px; color: var(--ink); }
.mood-chat-sheet__close {
  display: inline-flex; padding: 6px; background: transparent; border: none;
  color: var(--muted); cursor: pointer;
}
.mood-chat-sheet__close svg { width: 20px; height: 20px; }
.mood-chat-sheet__loading { padding: 32px 20px; text-align: center; color: var(--ink-2); font-size: 14px; }
.mood-chat-sheet__progress { padding: 10px 16px 0; color: var(--muted); font-size: 12px; font-weight: 600; }
.mood-chat-sheet__messages {
  flex: 1 1 auto; overflow-y: auto;
  padding: 12px 16px;
  display: flex; flex-direction: column; gap: 8px;
}
.mood-chat-msg {
  max-width: 82%;
  padding: 10px 12px;
  border-radius: var(--radius-lg);
  font-size: 14px; line-height: 1.45;
  white-space: pre-wrap;
}
.mood-chat-msg--ai { align-self: flex-start; background: var(--surface-card); color: var(--ink); }
.mood-chat-msg--user { align-self: flex-end; background: var(--accent); color: var(--on-accent); }
.mood-chat-sheet__emotions {
  padding: 8px 16px;
  border-top: 1px solid var(--line);
  font-size: 13px; color: var(--ink-2);
}
.mood-chat-sheet__emotions-lbl { font-weight: 600; color: var(--ink); }
.mood-chat-sheet__emotions small { display: block; margin-top: 4px; color: var(--muted); font-size: 11px; }
/* Two axes, two rows: what the user FELT vs what the answer was ABOUT. The row
   separator is what tells the user the second set came from their own words. */
.mood-chat-sheet__tagrow { margin-bottom: 4px; }
.mood-chat-sheet__tagrow + .mood-chat-sheet__tagrow {
  padding-top: 4px; border-top: 1px dashed var(--line);
}
.mood-chat-chip {
  display: inline-block;
  padding: 2px 8px; margin: 2px 2px 0 0;
  background: var(--accent-subtle); color: var(--accent);
  border-radius: var(--radius-sm); font-size: 12px; font-weight: 600;
}
.mood-chat-chip--topic {
  background: transparent; color: var(--ink-2);
  border: 1px solid var(--line);
}
.mood-chat-sheet__input {
  display: flex; gap: 8px; align-items: flex-end;
  padding: 12px 16px;
  border-top: 1px solid var(--line);
}
.mood-chat-sheet__input textarea {
  flex: 1 1 auto;
  resize: none;
  padding: 10px 12px;
  background: var(--surface-card);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font: inherit; font-size: 14px;
}
.mood-chat-sheet__input button {
  flex: 0 0 auto;
  padding: 10px 16px;
  background: var(--accent); color: var(--on-accent);
  border: none; border-radius: var(--radius-sm);
  font-weight: 600; font-size: 14px; cursor: pointer;
}
.mood-chat-sheet__input button:disabled { opacity: 0.6; cursor: default; }
/* The dictation mic is a round icon control, not an accent pill — it has to beat
   the generic `.mood-chat-sheet__input button` rule above on specificity. */
.mood-chat-sheet__input .mood-chat-sheet__mic {
  width: 40px; height: 40px; padding: 0; border-radius: 50%;
  background: var(--accent-subtle); color: var(--ink);
  border: 1px solid var(--line);
  display: inline-flex; align-items: center; justify-content: center;
  -webkit-tap-highlight-color: transparent;
}
.mood-chat-sheet__input .mood-chat-sheet__mic svg { width: 20px; height: 20px; display: block; }
.mood-chat-sheet__input .mood-chat-sheet__mic.fz-dict-mic--recording {
  background: var(--danger, #e5484d); color: var(--on-accent); border-color: transparent;
}
.mood-chat-sheet__input .mood-chat-sheet__mic.fz-dict-mic--busy {
  background: var(--accent); color: var(--on-accent); border-color: transparent;
}
/* NUTRITION-ADHERENCE-EVENING-QUESTION-AND-DAYSTAMP — de ja/nee-tikrij boven de invoer.
   Tekst-only: geen emoji, geen icoon (harde projectregel, net als de maat-chips). */
.mood-chat-sheet__qr { display: flex; gap: var(--space-2); margin: 0; padding: 0 16px 12px; }
.mood-chat-qr {
  flex: 1 1 0; padding: var(--space-2) var(--space-3);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface-2, var(--surface)); color: var(--ink); font: inherit; cursor: pointer;
}
.mood-chat-qr:hover:not(:disabled) { border-color: var(--accent); }
.mood-chat-qr:disabled { opacity: .5; cursor: default; }

.mood-chat-sheet__choice { padding: 20px 16px; }
.mood-chat-sheet__choice p { margin: 0 0 14px; color: var(--ink-2); font-size: 14px; }
.mood-chat-sheet__choice-btns { display: flex; gap: 10px; }
.mood-chat-sheet__choice-btns button {
  flex: 1 1 0; padding: 12px; cursor: pointer;
  background: var(--accent-subtle); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  font-weight: 600; font-size: 14px;
}
.mood-chat-sheet__done { padding: 14px 16px; border-top: 1px solid var(--line); }
.mood-chat-sheet__mood { display: block; margin-bottom: 14px; }
.mood-chat-sheet__mood-lbl {
  display: block; margin-bottom: 8px;
  color: var(--ink); font-size: 14px; font-weight: 600;
}
.mood-chat-sheet__mood input[type="range"] {
  width: calc(100% - 40px); vertical-align: middle; accent-color: var(--accent);
}
.mood-chat-sheet__mood-val {
  display: inline-block; width: 32px; margin-left: 8px;
  text-align: right; color: var(--ink); font-size: 15px; font-weight: 700;
}
.mood-chat-sheet__err {
  margin: 0 0 10px; color: var(--danger, #e5484d); font-size: 13px;
}
.mood-chat-sheet__done-btn {
  width: 100%; padding: 12px;
  background: var(--accent); color: var(--on-accent);
  border: none; border-radius: var(--radius-sm);
  font-weight: 600; font-size: 15px; cursor: pointer;
}
.mood-chat-sheet__done-btn:disabled { opacity: 0.6; cursor: default; }

/* COACH-CLIENT-SIGNUP-LINK-GENERATE-AND-SHARE (v0.9.869) — coach dialog + public landing.
   Accent CTAs use var(--on-accent) for contrast (lint rule g). No hex literals. */
.invite-gen { margin: var(--space-4) 0; }
.invite-gen__btn {
  width: 100%; padding: var(--space-3) var(--space-4);
  background: transparent; color: var(--accent);
  border: 1px solid var(--accent); border-radius: var(--radius-sm);
  font-weight: 600; font-size: 15px; cursor: pointer;
}
.invite-gen__btn:hover { background: var(--surface); }

.btn-accent {
  width: 100%; padding: var(--space-3) var(--space-4);
  background: var(--accent); color: var(--on-accent);
  border: 1px solid var(--accent); border-radius: var(--radius-sm);
  font-weight: 700; font-size: 15px; cursor: pointer;
}
.btn-accent:hover { opacity: 0.9; }
.btn-accent:disabled { opacity: 0.4; cursor: not-allowed; }

.invite-dialog { max-width: 360px; display: flex; flex-direction: column; gap: var(--space-3); }
.invite-dialog__h { margin: 0; font-size: 18px; color: var(--ink); }
.invite-dialog__hint { margin: 0; color: var(--muted); font-size: 14px; }
.invite-dialog__note { color: var(--muted); font-size: 12px; }
.invite-dialog__in {
  width: 100%; padding: var(--space-3); box-sizing: border-box;
  background: var(--bg); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm); font-size: 15px;
}
.invite-dialog__opt {
  display: flex; align-items: center; gap: var(--space-2);
  color: var(--ink); font-size: 15px; cursor: pointer;
}
.invite-dialog__opt input { accent-color: var(--accent); width: 18px; height: 18px; }
.invite-link-box {
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: var(--space-3); overflow-wrap: anywhere;
}
.invite-link-box code { color: var(--accent); font-size: 13px; }

/* Public sign-up landing (/invite/<token>) — standalone page, loads app.css. */
.invite-landing-body {
  margin: 0; min-height: 100vh; background: var(--bg); color: var(--ink);
  min-height: 100dvh; /* dvh wins on modern engines, 100vh above stays fallback */
  display: flex; align-items: center; justify-content: center; padding: var(--space-5);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.invite-landing, .invite-landing__msg {
  width: 100%; max-width: 380px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: var(--space-5);
}
.invite-landing__head { text-align: center; margin-bottom: var(--space-4); }
.invite-landing__emblem { width: 64px; height: 64px; object-fit: contain; }
.invite-landing__head h1 { font-size: 20px; margin: var(--space-3) 0 var(--space-2); }
.invite-landing__head p { color: var(--muted); font-size: 14px; margin: 0; }
.invite-signup-form { display: flex; flex-direction: column; gap: var(--space-3); }
.invite-signup-form label { display: flex; flex-direction: column; gap: 6px; font-size: 13px; color: var(--muted); }
.invite-signup-form input {
  padding: var(--space-3); box-sizing: border-box;
  background: var(--bg); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm); font-size: 16px;
}
.invite-landing__err { color: var(--danger); font-size: 14px; margin: 0; }
.invite-landing footer { margin-top: var(--space-4); text-align: center; }
.invite-landing footer small, .invite-landing__msg p { color: var(--muted); font-size: 12px; }
/* DE-VOORWAARDEN-OP-DE-UITNODIGINGSPAGINA-ZIJN-EEN-LINK — the terms link sits in
   12px --muted fine print, which is neither readable enough nor big enough to hit
   with a thumb. It gets --accent (defined by every preset, so it holds in nacht and
   dag), a real underline, and padding that lifts the tap target out of the caption. */
.invite-landing__terms {
  color: var(--accent);
  font-size: 14px;
  text-decoration: underline;
  text-underline-offset: 2px;
  display: inline-block;
  padding: 10px 0;
}
.invite-landing__msg { text-align: center; }
.invite-landing__msg a { color: var(--accent); }
.invite-landing__alt { margin: var(--space-2) 0 0; text-align: center; font-size: 13px; }
.invite-landing__alt a { color: var(--accent); }

/* PLUS-CODE-DE-LINK-IS-EEN-CADEAU — de cadeaulanding (/plus/<code>) en het
   feestscherm (/plus/<code>/klaar). Beide hangen aan .invite-landing-body
   hierboven, dus de plaat, de kaart en de knop zijn dezelfde als op de
   uitnodigingspagina; hieronder staat alleen wat er ECHT bij komt. Alles uit
   tokens (--accent / --on-accent / --ink / --muted), dus dag én nacht. */
.plusgift__list {
  list-style: none; margin: var(--space-4) 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--space-2);
}
.plusgift__list li {
  position: relative; padding-left: var(--space-4);
  color: var(--ink); font-size: 14px; line-height: 1.4;
}
.plusgift__list li::before {
  content: ''; position: absolute; left: 0; top: 0.5em;
  width: 6px; height: 6px; border-radius: 50%; background: var(--accent);
}
.plusgift .invite-landing__alt { margin-top: var(--space-3); color: var(--muted); }

.plusparty { text-align: center; }
/* [hidden] is maar een UA-regel van display:none en verliest van elke eigen
   display eronder — dezelfde val als .pkco__form. De knoppenrij hieronder
   KRIJGT een display:flex, dus deze regel moet erbij staan, anders staat het
   hele feest meteen op het scherm en meet elke assertie nog steeds groen. */
.plusparty [hidden] { display: none !important; }
.plusparty__stage {
  position: relative; display: grid; place-items: center;
  margin: var(--space-3) 0 var(--space-4);
}
/* Het paard-embleem komt met de BESTAANDE boot-animatie in beeld: .boot__ring
   en .boot__horse staan al in dit bestand en worden hier hergebruikt, niet
   nagebouwd. */
.plusparty__num {
  margin: 0; font-size: 64px; line-height: 1; font-weight: 800;
  color: var(--accent); font-variant-numeric: tabular-nums;
}
.plusparty__unit { margin: var(--space-2) 0 0; color: var(--muted); font-size: 14px; }
.plusparty__line { margin: var(--space-4) 0 0; color: var(--ink); font-size: 16px; }
.plusparty__actions {
  display: flex; flex-direction: column; align-items: center; gap: var(--space-3);
  margin-top: var(--space-5);
}
.plusparty__actions .btn-accent { display: block; box-sizing: border-box; text-align: center; text-decoration: none; }
.plusparty__later {
  color: var(--muted); font-size: 14px; text-decoration: underline;
  text-underline-offset: 2px; padding: 8px 0;
}
.plusparty__fade { opacity: 0; transition: opacity 260ms var(--ease-standard); }
.plusparty__fade.is-in { opacity: 1; }

/* De sparkle: 14 kleine ronde deeltjes in de accentkleur die vanaf het embleem
   uitwaaieren en verdwijnen. Eigen CSS, geen bibliotheek, en met opzet GEEN
   confetti over het hele scherm — het moet één keer verrassen en daarna weg
   zijn. De richting komt per deeltje uit --tx/--ty. */
.plusparty__spark {
  position: absolute; left: 50%; top: 50%;
  width: 8px; height: 8px; margin: -4px 0 0 -4px;
  border-radius: 50%; background: var(--accent);
  pointer-events: none; opacity: 0;
  animation: plus-spark 800ms var(--ease-standard) forwards;
}
@keyframes plus-spark {
  0%   { opacity: 1; transform: translate(0, 0) scale(0.4); }
  100% { opacity: 0; transform: translate(var(--tx, 0), var(--ty, 0)) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  /* Dan alleen de fade. De deeltjes worden door het script al niet aangemaakt;
     deze regel is de tweede grendel voor het geval iemand ze toch neerzet. */
  .plusparty__spark { display: none; }
  .plusparty__fade { transition: opacity 160ms linear; }
}

/* DE-PAKKETLINK-EEN-KLANT-MELDT-ZICH-AAN-VULT-ZIJN-GEGEVENS-IN-EN-BETAALT
   (2026-09-05) — de checkout op dezelfde /invite/<token>-route wanneer de link
   een pakket draagt. Eén scherm: links de gegevens, rechts het pakket en de
   betaling. Breder dan de gewone landing, want dertien velden naast een
   bestelsamenvatting passen niet in 380px. */
/* align-self + margin:auto in plaats van het align-items:center van de body:
   een formulier dat HOGER is dan het venster wordt door centreren aan BEIDE
   kanten afgekapt, en dan is de eerste vraag onbereikbaar. Zo staat hij
   gecentreerd zolang hij past en tegen de bovenkant zodra hij dat niet doet. */
/* align-self + een VERTICALE auto-marge in plaats van het align-items:center van
   de body: een formulier dat HOGER is dan het venster wordt door centreren aan
   BEIDE kanten afgekapt, en dan is de eerste vraag onbereikbaar. Alleen
   margin-block — een horizontale auto-marge laat de flex-item krimpen tot zijn
   inhoud en maakt van 880px een kolom van 400. */
.invite-landing--checkout { max-width: 880px; align-self: flex-start; margin-block: auto; }
/* AFSPRAAK-VERZETTEN-MET-EEN-LINK-ZONDER-INLOGGEN — dezelfde kaart, maar breed
   genoeg voor het weekraster (.cwp-dlg staat op min(680px, 96vw), dus dezelfde
   maat hier houdt de kolommen op dezelfde breedte als in de app). Verticale
   auto-marge om dezelfde reden als bij --checkout hierboven: het raster is
   hoger dan een telefoonvenster, en centreren kapt dan de bovenste uren af. */
.invite-landing--reschedule { max-width: 680px; align-self: flex-start; margin-block: auto; }
.pkco { display: flex; flex-direction: column; gap: var(--space-4); }
/* [hidden] is maar een UA-regel van display:none, en elke eigen display eronder
   wint ervan. Zonder deze regel staat het inlogformulier NAAST het
   aanmeldformulier op het scherm. */
.pkco [hidden], .pkco__form[hidden], .pkco__grid[hidden] { display: none !important; }

/* De bestelsamenvatting staat OPEN in stap 3 — geen <details> meer: daar zat de
   knop in die op een telefoon nooit tevoorschijn kwam. */
.pkco__sum {
  background: var(--bg); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: var(--space-3);
}
.pkco__sumbody { padding-top: var(--space-3); }
.pkco__pkg { font-size: 17px; margin: 0 0 var(--space-3); color: var(--ink); }
.pkco__lines { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.pkco__lines li { display: flex; justify-content: space-between; gap: var(--space-3); font-size: 13px; color: var(--muted); }
.pkco__lines li span:last-child { color: var(--ink); text-align: right; }
.pkco__total {
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-3);
  margin-top: var(--space-3); padding-top: var(--space-3);
  border-top: 1px solid var(--line); font-size: 14px; color: var(--ink);
}
.pkco__total strong { font-size: 20px; color: var(--accent); }
/* De zin onder het totaal bij een startbijdrage: "Daarna € 450,00 per maand".
   Rustig en klein — het totaal mag de blik houden, maar wie schrikt van een
   groter bedrag moet in dezelfde oogopslag zien dat het eenmalig is. */
.pkco__then { margin: var(--space-2) 0 0; font-size: 12px; line-height: 1.5; color: var(--muted); }
/* Geen max-height en geen scrollvenster: een akkoordtekst die je moet scrollen
   om te lezen is slechter akkoord, en op de bewijsfoto sneed hij een zin
   halverwege af zodat het scherm er kapot uitzag in plaats van scrollbaar. */
.pkco__consent {
  margin: var(--space-3) 0 0; font-size: 12px; line-height: 1.5; color: var(--muted);
}
.pkco__agree {
  display: flex; gap: var(--space-2); align-items: flex-start;
  margin-top: var(--space-3); font-size: 13px; color: var(--ink); cursor: pointer;
}
.pkco__agree input { margin-top: 2px; flex: 0 0 auto; }
.pkco__agree.pkco__f--bad span { color: var(--danger); }
.pkco__agree a { color: var(--accent); }
/* Drie losse akkoorden moeten als drie losse besluiten lezen, niet als één lap
   tekst met drie vakjes ervoor. Een lijntje ertussen doet dat zonder dat er een
   woord bij hoeft; de rode kleur van een ontbrekend vinkje blijft zo ook bij
   het juiste blok horen in plaats van over te lopen naar de buurzin. */
.pkco__agrees { display: flex; flex-direction: column; }
.pkco__agrees .pkco__agree + .pkco__agree {
  border-top: 1px solid var(--line); padding-top: var(--space-3);
}
.pkco__pay { width: 100%; margin-top: var(--space-3); }
.pkco__fine { margin: var(--space-2) 0 0; font-size: 12px; color: var(--muted); }

.pkco__tabs { display: flex; gap: var(--space-2); border-bottom: 1px solid var(--line); }
.pkco__tab {
  flex: 1; padding: var(--space-3) var(--space-2); background: none; border: 0;
  border-bottom: 2px solid transparent; color: var(--muted);
  font: inherit; font-size: 14px; cursor: pointer;
}
.pkco__tab.is-on { color: var(--ink); border-bottom-color: var(--accent); }
.pkco__form { display: flex; flex-direction: column; gap: var(--space-3); }
.pkco__grid { display: grid; grid-template-columns: 1fr; gap: var(--space-3); }
.pkco__f { display: flex; flex-direction: column; gap: 6px; font-size: 13px; color: var(--muted); }
.pkco__f--req > span::after { content: " *"; color: var(--accent); }
.pkco__f input, .pkco__f select {
  padding: var(--space-3); box-sizing: border-box; width: 100%;
  background: var(--bg); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm); font-size: 16px;
}
/* Een ontbrekend veld wordt AANGEWEZEN, niet samengevat: één rode regel boven
   een formulier van dertien vakjes vertelt niet welk vakje. */
.pkco__f--bad input, .pkco__f--bad select { border-color: var(--danger); }
.pkco__toggle { display: flex; gap: var(--space-2); align-items: center; font-size: 13px; color: var(--ink); cursor: pointer; }

/* DE-PAKKETLINK-LOOPT-IN-DRIE-STAPPEN-TOT-EN-MET-DE-APP (2026-09-05) — Jayy op
   zijn telefoon: inloggen, en daarna niets om op te drukken. De enige knop zat
   in een <details> die alleen boven 860px openklapt. Vandaar deze twee regels:
   drie stappen met elk hun EIGEN knop, en die knop staat STICKY onderin — nooit
   meer in iets dat dicht kan. */
.pkco__steps {
  list-style: none; display: flex; gap: var(--space-2);
  margin: 0 0 var(--space-4); padding: 0;
}
.pkco__stepdot {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px;
  font-size: 11px; color: var(--muted); text-align: center;
  border-top: 2px solid var(--line); padding-top: var(--space-2);
}
.pkco__stepdot b {
  display: flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--bg); border: 1px solid var(--line); font-size: 12px; font-weight: 600;
}
.pkco__stepdot.is-on { color: var(--ink); border-top-color: var(--accent); }
.pkco__stepdot.is-on b { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.pkco__stepdot.is-done { color: var(--ink); border-top-color: var(--accent); }
.pkco__pane { display: flex; flex-direction: column; gap: var(--space-3); }
.pkco__pane[hidden] { display: none !important; }
/* De actiebalk hangt aan de ONDERKANT van het scherm zolang de stap langer is
   dan het venster, en staat gewoon onder de inhoud zodra hij past. In beide
   gevallen: zichtbaar zonder zoeken. */
.pkco__actions {
  position: sticky; bottom: 0; z-index: 2;
  margin: var(--space-3) calc(var(--space-5) * -1) calc(var(--space-5) * -1);
  padding: var(--space-3) var(--space-5) calc(var(--space-3) + env(safe-area-inset-bottom, 0px));
  background: var(--surface); border-top: 1px solid var(--line);
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  display: flex; flex-direction: column; gap: var(--space-2);
}
.pkco__go { width: 100%; }
.pkdone__state { margin: 0 0 var(--space-4); font-size: 14px; color: var(--muted); }
.pkdone__go { display: block; text-align: center; text-decoration: none; }
/* Staat er een Opnieuw betalen-knop boven, dan is Naar de app de RUSTIGE van de
   twee: twee even luide accentknoppen naast elkaar vragen allebei de klik. */
.pkdone__go--quiet { background: transparent; color: var(--accent); border: 1px solid var(--line); }
/* PAKKETLINK-NA-BETALEN-TERUG-NAAR-DE-BEDANKTPAGINA-VAN-DE-COACH — de regel die
   aankondigt dat dit scherm zo doorschuift naar de site van de coach. Gedempt en
   klein: het is een mededeling, geen keuze. De link ernaast is wél een keuze en
   draagt daarom de accentkleur — hij is de uitweg voor wie het niet vanzelf ziet
   gebeuren. Geen display:none op [hidden] nodig: die staat al globaal. */
.pkdone__back { margin: var(--space-3) 0 0; font-size: 13px; color: var(--muted); text-align: center; }
.pkdone__back a { color: var(--accent); }

@media (min-width: 640px) {
  .pkco__grid { grid-template-columns: 1fr 1fr; }
}
@media (min-width: 860px) {
  /* Op een breed scherm mag het formulier in twee kolommen, en de
     bestelsamenvatting krijgt lucht. De knop blijft waar hij is: onderaan de
     stap waar hij bij hoort. */
  .pkco__sum { padding: var(--space-4); }
}

/* INTAKE-TAAL-KIEZEN-EN-VASTHOUDEN — the language gate, first screen of the
   invite. Thumb-sized targets; flags are inline SVG, never emoji. */
.invite-lang h1 { font-size: 18px; margin: var(--space-3) 0 var(--space-4); }
.invite-lang__opts { display: flex; flex-direction: column; gap: var(--space-3); }
.invite-lang__btn {
  display: flex; align-items: center; gap: var(--space-3);
  width: 100%; min-height: 56px; padding: var(--space-3); box-sizing: border-box;
  background: var(--bg); color: var(--ink); font: inherit; font-size: 16px; text-align: left;
  border: 1px solid var(--line); border-radius: var(--radius-sm); cursor: pointer;
}
.invite-lang__btn:hover { border-color: var(--accent); }
.invite-lang__flag {
  width: 36px; height: 24px; flex: 0 0 auto;
  border-radius: 2px; border: 1px solid var(--line);
}

/* COACH-SELF-SERVICE-INTAKE-LINK — one-question-per-step intake wizard shown
   before the account screen when the invite requires an intake. */
.invite-wizard { display: flex; flex-direction: column; gap: var(--space-3); }
.invite-wizard__progress { font-size: 12px; color: var(--muted); text-align: center; }
.invite-wizard__bar { height: 4px; border-radius: var(--radius-pill); background: var(--line); overflow: hidden; }
.invite-wizard__bar > span { display: block; height: 100%; background: var(--accent); transition: width var(--dur-base) var(--ease-standard); }
.invite-wizard__q { font-size: 16px; color: var(--ink); font-weight: 600; }
.invite-wizard__opts { display: flex; flex-direction: column; gap: var(--space-2); }
.wiz-opt {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3); box-sizing: border-box;
  background: var(--bg); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  font-size: 15px; cursor: pointer;
}
.wiz-opt input { accent-color: var(--accent); width: 18px; height: 18px; }
.wiz-opt:has(input:checked) { border-color: var(--accent); }
.invite-wizard input[type="text"], .invite-wizard input[type="number"],
.invite-wizard input[type="date"], .invite-wizard textarea {
  padding: var(--space-3); box-sizing: border-box; width: 100%;
  background: var(--bg); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm); font-size: 16px;
}
.invite-wizard textarea { min-height: 96px; resize: vertical; font-family: inherit; }
.invite-wizard__other { margin-top: var(--space-2); }
.invite-wizard__nav { display: flex; gap: var(--space-3); margin-top: var(--space-2); }
.invite-wizard__nav .btn-accent { flex: 1; }
.invite-wizard .btn-ghost {
  padding: var(--space-3) var(--space-4); box-sizing: border-box;
  background: transparent; color: var(--muted);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  font-size: 15px; cursor: pointer;
}

/* NUTRITION-PHOTO-ESTIMATE-CHOOSER-SHEET-CAMERA-OR-GALLERY (2026-06-24) */
.photo-estimate-chooser__hint {
  text-align: center;
  color: var(--muted);
  margin-bottom: var(--space-3);
}
.photo-estimate-chooser__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-2);
  min-height: 48px;
}
/* VOEDING-PLAK-EEN-SCHERMAFBEELDING-BIJ-JE-MAALTIJD (2026-09-14) — the fallback
   paste pad, shown only when navigator.clipboard.read() is missing or throws.
   A dashed edge so it reads as a drop target rather than a filled input, and
   user-select stays on so a long-press can reach the OS paste command. */
.photo-estimate-chooser__pastepad {
  min-height: 96px;
  padding: var(--space-4);
  border: 1px dashed var(--line);
  border-radius: var(--radius-lg);
  color: var(--muted);
  font-size: 14px;
  line-height: 1.45;
  text-align: center;
  -webkit-user-select: text;
  user-select: text;
}

/* DESKTOP-VIEW-FOUNDATION-RESPONSIVE-SHELL-AND-SIDEBAR — additive desktop-only layer; mobile cascade untouched. */
@media (min-width: 1024px) and (pointer: fine) and (hover: hover) {
  /* NUTD-V2-F1 — desktop app-shell: a content column (slim topbar strip + main)
     that WRAPS above the right nav rail (80px compact icon rail since F7).
     flex-wrap keeps the flat DOM (topbar, banners, main, tabbar) intact: every
     child except .app-main + .tabbar is a full-width row, so the topbar renders
     as a top strip — never a broken side column (the pre-fix bug) — and
     .app-main + .tabbar share the next line. */
  html:not([data-touch-tablet]) .app-shell {
    flex-direction: row;
    flex-wrap: wrap;
    align-content: flex-start;
  }
  html:not([data-touch-tablet]) .app-shell > * { flex-basis: 100%; }
  html:not([data-touch-tablet]) .app-shell > .app-main {
    /* basis 0 (not auto/max-content) so app-main never pushes the 80px rail
       onto its own wrapped line — it just fills the space left of the rail. */
    flex: 1 1 0;
    min-width: 0;
  }
  html:not([data-touch-tablet]) .app-shell > .tabbar { flex: 0 0 80px; }

  /* Slim full-width top strip: title left, any topbar actions right (base
     .topbar already flex/space-between/border-bottom). Drop the mobile
     safe-area top padding so it's a true ~56px strip, not a tall header. */
  html:not([data-touch-tablet]) .topbar {
    height: 56px;
    min-height: 56px;
    padding: 0 var(--space-6);
  }
  html:not([data-touch-tablet]) .topbar__title { letter-spacing: 0.08em; }

  /* NUTD-V2-F7-COMPACT-ICON-RAIL — M3-style compact icon rail: an 80px column of
     icon-only cells (was a 240px icon+label rail). Thinner rail = more content
     width + calmer surface ('rust'). Labels stay in the DOM for a11y and reappear
     as a hover/focus tooltip; the active tab keeps a tonal M3 pill. Desktop MQ
     only — the mobile + tablet bottom-bar cascade is pixel-identical. */
  html:not([data-touch-tablet]) .tabbar {
    position: sticky;
    top: 0;
    left: auto;
    right: auto;
    bottom: auto;
    height: 100dvh;
    width: 80px;
    flex: 0 0 80px;
    flex-direction: column;
    justify-content: flex-start;
    align-items: stretch;
    gap: var(--space-3);
    padding: var(--space-5) var(--space-2);
    border-top: 0;
    border-left: 1px solid var(--line);
    z-index: 50;
    align-self: flex-start;
  }

  html:not([data-touch-tablet]) .tabbar__btn {
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 0;
    flex: 0 0 auto;
    padding: var(--space-2) 0;
    border-radius: var(--radius-lg);
    text-transform: none;
    letter-spacing: 0.01em;
    /* comfortable centered hit area, well above the 48px M3 floor */
    min-height: 56px;
    /* let the hover tooltip escape the cell — the base rule clips overflow */
    overflow: visible;
  }

  /* Label kept in the DOM for screen-readers (NOT display:none, so aria stays
     intact) but visually pulled out of the icon cell to opacity:0; revealed as a
     floating tooltip pill to the LEFT of the rail on hover/focus-visible. Hover
     can't fire on touch, and this whole MQ already excludes touch (pointer:fine +
     hover:hover), so the tooltip never appears on a tablet. */
  html:not([data-touch-tablet]) .tabbar__btn span {
    position: absolute;
    right: calc(100% + var(--space-2));
    top: 50%;
    transform: translateY(-50%);
    max-width: none;
    width: max-content;
    padding: 4px var(--space-2);
    border-radius: var(--radius-sm);
    background: var(--ink);
    color: var(--surface);
    font-size: 12px;
    line-height: 1.2;
    letter-spacing: 0.01em;
    text-transform: none;
    white-space: nowrap;
    box-shadow: var(--elev-1);
    opacity: 0;
    pointer-events: none;
    z-index: 60;
    transition: opacity var(--dur-fast) var(--ease-standard);
  }
  html:not([data-touch-tablet]) .tabbar__btn:hover span,
  html:not([data-touch-tablet]) .tabbar__btn:focus-visible span { opacity: 1; }

  html:not([data-touch-tablet]) .tabbar__btn:hover { background: var(--surface-muted); }

  /* M3 active indicator — tonal pill behind the centered icon. */
  html:not([data-touch-tablet]) .tabbar__btn[aria-current="page"] { background: var(--accent-subtle); }

  html:not([data-touch-tablet]) .tabbar__btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }

  /* Re-anchor the count / unseen-dot badge onto the centered icon of the 80px
     cell (the mobile clamp math assumed a flex:1 bottom-bar button). */
  html:not([data-touch-tablet]) .tabbar__badge {
    top: 6px;
    right: 16px;
  }

  html:not([data-touch-tablet]) .app-main {
    max-width: 1680px;
    width: 100%;
    margin: 0 auto;
    padding-left: var(--space-6);
    padding-right: var(--space-6);
    padding-bottom: var(--space-6);
    min-width: 0;
    /* Fill the viewport below the 56px topbar strip so there's no dead
       letterbox at the bottom of short pages. */
    min-height: calc(100dvh - 56px);
  }

  html:not([data-touch-tablet]) .strength-iframe-wrap { height: 100dvh; }
  html:not([data-touch-tablet]) body.tab-strength .app-main { padding: 0; max-width: none; }
  html:not([data-touch-tablet]) .strength-iframe-area { overflow: hidden; }

  html:not([data-touch-tablet]) .nut-fab {
    right: var(--space-6);
    bottom: var(--space-6);
    z-index: 45;
  }

  /* NUTRITION-DESKTOP — standalone master/detail page (Log tab). */
  html:not([data-touch-tablet]) .nutd { width: 100%; min-width: 0; }
  /* NUTD-V2-F1 — keep hydration quick-add labels ('+250 ml') on one line in the
     narrower desktop summary pane; the flex:1 buttons wrapped otherwise. */
  html:not([data-touch-tablet]) .hydration-btn { white-space: nowrap; }
  html:not([data-touch-tablet]) .nutd-grid {
    display: grid;
    grid-template-columns: 220px minmax(0, 1fr);
    gap: var(--space-5);
    align-items: start;
  }
  html:not([data-touch-tablet]) .nutd-rail {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    border-right: 1px solid var(--line);
    padding-right: var(--space-4);
  }
  html:not([data-touch-tablet]) .nutd-rail__today {
    text-align: left;
    background: transparent;
    border: 0;
    border-radius: var(--radius-sm);
    padding: var(--space-2) var(--space-3);
    margin-bottom: var(--space-2);
    color: var(--accent);
    cursor: pointer;
    font: inherit;
    font-weight: 600;
  }
  html:not([data-touch-tablet]) .nutd-rail__today:hover { background: var(--surface-muted); }
  html:not([data-touch-tablet]) .nutd-rail__day {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-2);
    text-align: left;
    background: transparent;
    border: 0;
    border-left: 2px solid transparent;
    border-radius: var(--radius-sm);
    padding: var(--space-2) var(--space-3);
    color: var(--ink-2);
    cursor: pointer;
    font: inherit;
  }
  html:not([data-touch-tablet]) .nutd-rail__day-wd { font-weight: 600; text-transform: capitalize; }
  html:not([data-touch-tablet]) .nutd-rail__day-dt { color: var(--muted); font-size: 12px; }
  html:not([data-touch-tablet]) .nutd-rail__day.is-active .nutd-rail__day-dt { color: var(--ink-2); }
  html:not([data-touch-tablet]) .nutd-rail__day:hover { background: var(--surface-muted); color: var(--ink); }
  html:not([data-touch-tablet]) .nutd-rail__day.is-active {
    background: var(--surface-muted);
    color: var(--ink);
    border-left: 2px solid var(--accent);
  }
  html:not([data-touch-tablet]) .nutd-detail { min-width: 0; }
  html:not([data-touch-tablet]) .nutd-panes {
    display: grid;
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
    gap: var(--space-5);
    align-items: start;
  }
  html:not([data-touch-tablet]) .nutd-pane { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
  html:not([data-touch-tablet]) .nutd-pane--summary { grid-column: 2; grid-row: 1; }
  html:not([data-touch-tablet]) .nutd-pane--meals { grid-column: 1; grid-row: 1 / span 2; }

  /* NUTD-V2-F3 — meals become a comfortable, scannable timeline on the laptop.
     More breathing room between cards; entry rows read as aligned table-like
     rows with kcal locked into its own right-aligned column; the touch swipe
     tray is retired in favour of hover/keyboard-revealed compact action buttons.
     Desktop-only branch (inside the min-width/hover MQ) — mobile keeps the touch
     swipe tray pixel-identical. Tokens only. */
  html:not([data-touch-tablet]) .nutd-pane--meals .nut-meals { gap: var(--space-4); }
  html:not([data-touch-tablet]) .nutd-pane--meals .nut-meal__head { padding: var(--space-4); }
  html:not([data-touch-tablet]) .nutd-pane--meals .nut-entry { padding: var(--space-3) var(--space-4); }
  /* kcal locked to a right-aligned column so numbers line up down the card. */
  html:not([data-touch-tablet]) .nutd-pane--meals .nut-entry__kcal { min-width: 3.5em; text-align: right; }
  /* Subtle M3 state-layer over the whole row while pointer or keyboard is on it. */
  html:not([data-touch-tablet]) .nutd-pane--meals .nut-entry-wrap:hover .nut-entry,
  html:not([data-touch-tablet]) .nutd-pane--meals .nut-entry-wrap:focus-within .nut-entry {
    background: var(--surface-muted);
  }
  /* Retire the full-height touch tray: compact icon buttons pinned right,
     opacity 0 by default (kept in the tab order — NOT display:none / visibility:
     hidden), revealed on row :hover and :focus-within for keyboard a11y parity.
     pointer-events gate so the invisible cluster never blocks the kcal beneath. */
  html:not([data-touch-tablet]) .nutd-pane--meals .nut-entry-actions {
    inset: 0 var(--space-2) 0 auto;
    align-items: center;
    gap: var(--space-1);
    padding-left: var(--space-5);
    background: linear-gradient(to right, transparent, var(--surface-muted) var(--space-5));
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--dur-fast) var(--ease-standard);
  }
  html:not([data-touch-tablet]) .nutd-pane--meals .nut-entry-wrap:hover .nut-entry-actions,
  html:not([data-touch-tablet]) .nutd-pane--meals .nut-entry-wrap:focus-within .nut-entry-actions {
    opacity: 1;
    pointer-events: auto;
  }
  html:not([data-touch-tablet]) .nutd-pane--meals .nut-entry-act {
    width: auto; min-height: 32px; padding: 0 var(--space-2);
    background: none; color: var(--muted); border-radius: var(--radius-md);
  }
  html:not([data-touch-tablet]) .nutd-pane--meals .nut-entry-act:hover { background: var(--surface); color: var(--ink); }
  html:not([data-touch-tablet]) .nutd-pane--meals .nut-entry-act--del:hover { color: var(--danger); }
  html:not([data-touch-tablet]) .nutd-pane--meals .nut-entry-act__lbl { display: none; }
  /* NUTD-V2-F2 — full-width hero dashboard band: elevated tonal card with three
     zones (calorie ring · macro bars · hydration). Desktop-only; tokens only. */
  html:not([data-touch-tablet]) .nutd-hero {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) minmax(14rem, 20rem);
    gap: var(--space-5);
    align-items: center;
    padding: var(--space-5);
    margin-bottom: var(--space-5);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--elev-1);
  }
  html:not([data-touch-tablet]) .nutd-hero__ring {
    position: relative;
    width: 180px;
    height: 180px;
    justify-self: center;
  }
  html:not([data-touch-tablet]) .nutd-ring { display: block; transform: rotate(-90deg); }
  html:not([data-touch-tablet]) .nutd-ring__track { fill: none; stroke: var(--line); stroke-width: 14; }
  html:not([data-touch-tablet]) .nutd-ring__prog {
    fill: none;
    stroke: var(--accent);
    stroke-width: 14;
    stroke-linecap: round;
  }
  html:not([data-touch-tablet]) .nutd-ring.is-over .nutd-ring__prog { stroke: var(--warning); }
  html:not([data-touch-tablet]) .nutd-ring__center {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-1);
    pointer-events: none;
  }
  html:not([data-touch-tablet]) .nutd-ring__num {
    font-size: var(--text-headline);
    line-height: 1;
    font-weight: 700;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
  }
  html:not([data-touch-tablet]) .nutd-ring__lbl {
    font-size: var(--text-label);
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
  }
  html:not([data-touch-tablet]) .nutd-hero__macros {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    min-width: 0;
  }
  html:not([data-touch-tablet]) .nutd-macro { display: flex; flex-direction: column; gap: var(--space-1); }
  html:not([data-touch-tablet]) .nutd-macro__top {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-2);
  }
  html:not([data-touch-tablet]) .nutd-macro__lbl { font-size: var(--text-body); font-weight: 600; color: var(--ink); }
  html:not([data-touch-tablet]) .nutd-macro__val { font-size: var(--text-body-sm); color: var(--ink-2); font-variant-numeric: tabular-nums; }
  html:not([data-touch-tablet]) .nutd-macro__rem { font-size: var(--text-label); color: var(--muted); }
  html:not([data-touch-tablet]) .nutd-hero__hydration { min-width: 0; }

  /* NUTD-V2-F4 — week-insights card (7-day kcal bar chart + stats row) and the
     day-rail logged/achieved dots. Desktop-only; tokens only. */
  html:not([data-touch-tablet]) .nutd-insights {
    display: flex; flex-direction: column; gap: var(--space-3);
    background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--radius-lg); padding: var(--space-4) var(--space-5);
  }
  html:not([data-touch-tablet]) .nutd-insights__title {
    font-size: var(--text-label); font-weight: 600; color: var(--muted);
    text-transform: uppercase; letter-spacing: 0.04em;
  }
  html:not([data-touch-tablet]) .nutd-insights__empty {
    font-size: var(--text-body-sm); color: var(--muted);
    padding: var(--space-4) 0; text-align: center;
  }
  /* NUTD-V2-F8 — week-glance is now the full-width top band: title + macro
     segmented switcher on one header row, a taller detailed chart, and a stats
     row. Reuses the .set-cardio-seg tile idiom with tokens; desktop-only. */
  html:not([data-touch-tablet]) .nutd-week__head {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-4); flex-wrap: wrap;
  }
  html:not([data-touch-tablet]) .nutd-week__body {
    display: flex; flex-direction: column; gap: var(--space-3);
  }
  html:not([data-touch-tablet]) .nutd-week-seg {
    display: flex; gap: 4px; flex-wrap: wrap;
    background: var(--surface-muted); border: 1px solid var(--line);
    border-radius: var(--radius-pill); padding: 3px;
  }
  html:not([data-touch-tablet]) .nutd-week-seg__tile {
    padding: 6px 14px; min-width: 0; border: 0; background: transparent;
    color: var(--ink-2); font: inherit; font-size: var(--text-body-sm); font-weight: 600;
    border-radius: var(--radius-pill); cursor: pointer;
    transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
  }
  html:not([data-touch-tablet]) .nutd-week-seg__tile.is-on {
    background: var(--accent); color: var(--on-accent);
  }
  html:not([data-touch-tablet]) .nutd-week-seg__tile:focus-visible {
    outline: 2px solid var(--accent); outline-offset: 2px;
  }
  html:not([data-touch-tablet]) .nutd-ichart { position: relative; height: 120px; }
  /* Promoted week band gets a ~2x taller chart for the detailed labeled bars. */
  html:not([data-touch-tablet]) .nutd-week .nutd-ichart {
    height: 240px; margin-top: var(--space-4);
  }
  html:not([data-touch-tablet]) .nutd-ichart__goal {
    position: absolute; left: 0; right: 0; height: 0;
    border-top: 1px dashed var(--warning); z-index: 1; pointer-events: none;
  }
  /* Goal value labeled at the right end of the dashed line. */
  html:not([data-touch-tablet]) .nutd-ichart__goalval {
    position: absolute; right: 0; bottom: 1px;
    font-size: var(--text-label); font-weight: 600; color: var(--warning);
    background: var(--surface); padding: 0 4px; font-variant-numeric: tabular-nums;
  }
  html:not([data-touch-tablet]) .nutd-ibars {
    position: absolute; inset: 0; display: flex; align-items: flex-end; gap: var(--space-2);
  }
  html:not([data-touch-tablet]) .nutd-ibar-slot {
    flex: 1 1 0; min-width: 0; height: 100%;
    display: flex; flex-direction: column; align-items: center; gap: var(--space-1);
  }
  html:not([data-touch-tablet]) .nutd-ibar-track {
    flex: 1 1 auto; width: 60%; max-width: 26px; min-height: 0;
    display: flex; align-items: flex-end;
  }
  html:not([data-touch-tablet]) .nutd-ibar {
    position: relative; width: 100%; min-height: 2px;
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    background: var(--accent);
  }
  html:not([data-touch-tablet]) .nutd-ibar.is-over { background: var(--warning); }
  /* Exact value labeled above each bar — muted, small, anchored to the bar top. */
  html:not([data-touch-tablet]) .nutd-ibar__val {
    position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%);
    margin-bottom: 3px; font-size: var(--text-label); color: var(--muted);
    font-variant-numeric: tabular-nums; white-space: nowrap; pointer-events: none;
  }
  html:not([data-touch-tablet]) .nutd-ibar__wd {
    flex: 0 0 auto; font-size: var(--text-label); color: var(--muted); text-transform: uppercase;
  }
  html:not([data-touch-tablet]) .nutd-ibar__wd.is-current { color: var(--ink); font-weight: 700; }
  html:not([data-touch-tablet]) .nutd-ibar__dom {
    flex: 0 0 auto; font-size: var(--text-label); color: var(--muted);
    font-variant-numeric: tabular-nums; line-height: 1;
  }
  html:not([data-touch-tablet]) .nutd-ibar__dom.is-current { color: var(--ink); font-weight: 700; }
  html:not([data-touch-tablet]) .nutd-istats {
    display: flex; gap: var(--space-5);
    border-top: 1px solid var(--line); padding-top: var(--space-3);
  }
  html:not([data-touch-tablet]) .nutd-istat { display: flex; flex-direction: column; gap: 2px; }
  html:not([data-touch-tablet]) .nutd-istat__val {
    font-size: var(--text-body); font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums;
  }
  html:not([data-touch-tablet]) .nutd-istat__lbl {
    font-size: var(--text-label); color: var(--muted); text-transform: uppercase; letter-spacing: 0.03em;
  }
  /* Day-rail dots: accent outline = logged, filled accent = achieved (strict
     engine semantics, same tokens as .nut-consist__dot). Absolute so the rail
     row layout is unchanged for un-logged days. */
  html:not([data-touch-tablet]) .nutd-rail__day { position: relative; }
  html:not([data-touch-tablet]) .nutd-rail__dot {
    position: absolute; left: 4px; top: 50%; transform: translateY(-50%);
    width: 6px; height: 6px; border-radius: 50%; box-sizing: border-box;
    background: transparent; border: 1.5px solid var(--accent);
  }
  html:not([data-touch-tablet]) .nutd-rail__dot.is-achieved { background: var(--accent); }
}

/* NUTD-V2-F6 — 13" laptop (1024–1280px) intermediate tweak. Below ~1280px the
   detail column is narrow enough that the hero's three-column grid (ring · macros
   · 14rem hydration) crushes the macro zone — "124 / 150 g" wrapped letter-by-
   letter and the hydration card collided with the bars. Drop to two columns
   (ring · macros) and float hydration onto a full-width second row so every zone
   keeps a readable measure. Same MQ guards as the wave; mobile untouched. */
@media (min-width: 1024px) and (max-width: 1279.98px) and (pointer: fine) and (hover: hover) {
  html:not([data-touch-tablet]) .nutd-hero {
    grid-template-columns: auto minmax(0, 1fr);
  }
  html:not([data-touch-tablet]) .nutd-hero__hydration {
    grid-column: 1 / -1;
  }
}

/* NUTD-V2-F4 — the bar-grow reveal is gated behind prefers-reduced-motion so no
   bars animate under reduce. Top-level (not nested) but the selector still only
   matches the desktop insights chart. */
@media (prefers-reduced-motion: no-preference) {
  html:not([data-touch-tablet]) .nutd-ibar {
    transform-origin: bottom;
    animation: nutd-ibar-grow var(--dur-slow) var(--ease-standard) both;
  }
}
@keyframes nutd-ibar-grow { from { transform: scaleY(0); } to { transform: scaleY(1); } }

/* NUTD-V2-F5-LIBRARY-GRID — desktop nutrition wave 5/6: the library sub-tabs
   (My Recipes / Groceries / My Meals / My Foods) get real laptop layouts.
   Flat item lists become a responsive card grid; grouped grocery lists get a
   centered readable column instead of a stretched mobile strip. Cards reuse the
   F3 --surface-muted state-layer on hover/focus. Guarded by the same desktop MQ
   + html:not([data-touch-tablet]); mobile stays pixel-identical. Tokens only. */
@media (min-width: 1024px) and (pointer: fine) and (hover: hover) {
  /* Header row: search grows, create-actions right-aligned, one line. The
     shared-community panel (recipes/foods) stays a full-width band above. */
  html:not([data-touch-tablet]) .nutd .nut-lib {
    display: grid; grid-template-columns: 1fr auto;
    column-gap: var(--space-4); row-gap: var(--space-4);
    align-items: center; max-width: 1200px;
  }
  html:not([data-touch-tablet]) .nutd .nut-lib > * { grid-column: 1 / -1; }
  html:not([data-touch-tablet]) .nutd .nut-lib > .nut-lib__search {
    grid-column: 1; margin-bottom: 0;
  }
  html:not([data-touch-tablet]) .nutd .nut-lib > .nut-lib__actions {
    grid-column: 2; justify-self: end; margin-bottom: 0;
  }
  html:not([data-touch-tablet]) .nutd .nut-libaction { flex: 0 0 auto; }
  html:not([data-touch-tablet]) .nutd .nut-lib__list { margin-top: 0; }

  /* Meals / Foods: the flat item list becomes a card grid. Recipes keep their
     category buckets stacked (:has) and grid the rows INSIDE each bucket. */
  html:not([data-touch-tablet]) .nutd .nut-lib__list {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: var(--space-4); align-items: start;
  }
  html:not([data-touch-tablet]) .nutd .nut-lib__list:has(.recipe-category) {
    display: block;
  }
  html:not([data-touch-tablet]) .nutd .recipe-category__recipes {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: var(--space-4); align-items: start;
  }

  /* Card shell on the grid cell: .nut-libitem for meals/foods, .recipe-row for
     recipes (it wraps the drag grip + .nut-libitem). Full border replaces the
     mobile top-divider list style. */
  html:not([data-touch-tablet]) .nutd .nut-lib__list > .nut-libitem,
  html:not([data-touch-tablet]) .nutd .recipe-category__recipes > .recipe-row {
    border: 1px solid var(--line); border-radius: var(--radius-lg);
    background: var(--surface); padding-inline: var(--space-3);
    transition: background var(--dur-fast) var(--ease-standard);
    /* The recipe card carries its own stacking margin for the mobile list; in
       this grid the `gap` above is the only spacer, or the rows drift apart. */
    margin-bottom: 0;
  }
  /* Reuse the F3 state-layer; neutralize the mobile per-button accent tint so
     the whole card lights up as one surface. focus-within keeps keyboard focus
     visible without touching focus-visible outlines. */
  html:not([data-touch-tablet]) .nutd .nut-lib__list > .nut-libitem:hover,
  html:not([data-touch-tablet]) .nutd .nut-lib__list > .nut-libitem:focus-within,
  html:not([data-touch-tablet]) .nutd .recipe-category__recipes > .recipe-row:hover,
  html:not([data-touch-tablet]) .nutd .recipe-category__recipes > .recipe-row:focus-within {
    background: var(--surface-muted);
  }
  html:not([data-touch-tablet]) .nutd .nut-libitem__main:hover { background: transparent; }

  /* Grouped grocery list resists gridding — give it a centered readable column
     instead of a stretched full-width strip. */
  html:not([data-touch-tablet]) .nutd .nut-groc {
    max-width: 760px; margin-inline: auto;
  }

  /* Tab strip stays; active state already token-driven (accent underline).
     Add a desktop hover cue. */
  html:not([data-touch-tablet]) .nutd .nut-tab:hover { color: var(--ink); }
}

/* SASSU-CHALLENGE-MEMBER-TAB-LAUNCH — member Challenges tab.
   CHALLENGES-MEMBER-UX-OVERHAUL (iter 1): wallet demoted to a header chip +
   toggle panel; classes first; leaderboard = podium centerpiece with motion.
   All colour via theme tokens so it renders in every preset. */
.challenges .ch-section { margin-bottom: var(--space-4); }
.challenges .set-card.ch-section { padding: var(--space-4); }
/* set-card auto-draws divider lines between children; ch-* sections manage
   their own hierarchy, so suppress them here. */
.challenges .set-card.ch-section > * + *::before { display: none; }
.challenges .ch-section__title { margin: 0 0 var(--space-3); font-size: 1.05rem; color: var(--ink); }
.challenges .ch-muted { color: var(--muted); }
.challenges .ch-error { color: var(--danger); }
.challenges .ch-confirm { color: var(--ok); }
.challenges .ch-subhead { color: var(--ink); margin: var(--space-3) 0 var(--space-2); font-size: 0.95rem; }

/* ── Header + wallet chip ─────────────────────────────────────────── */
.ch-header { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
.ch-header__title { margin: 0; font-size: 1.5rem; color: var(--ink); }
.ch-wallet-chip {
  position: relative;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px;
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  background: var(--surface-card); color: var(--ink);
  font-size: 0.9rem; font-weight: 600;
  cursor: pointer;
}
.ch-wallet-chip svg { color: var(--accent); }
.ch-wallet-chip[aria-expanded="true"] { border-color: var(--accent); }
.ch-wallet-chip__dot {
  position: absolute; top: -2px; right: -2px;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--bg);
}
.ch-wallet-panel .ch-wallet__balance { margin: 0 0 var(--space-2); color: var(--ink); font-weight: 600; font-size: 1.05rem; }
.ch-wallet-panel .ch-wallet__pending { margin: 0 0 var(--space-2); color: var(--accent); font-size: 0.85rem; }
.ch-wallet__earned {
  display: flex; align-items: center; gap: var(--space-2);
  margin: 0 0 var(--space-3); padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm); background: var(--accent); color: var(--on-accent);
}
.ch-wallet__earned svg { color: var(--on-accent); flex-shrink: 0; }
.ch-wallet__earned-head { font-weight: 600; font-size: 0.9rem; }
.ch-wallet__earned-sub { font-size: 0.78rem; opacity: 0.85; margin-top: 2px; }
.ch-wallet__explainer { margin: var(--space-3) 0 0; color: var(--muted); font-size: 0.8rem; line-height: 1.45; }

/* Mollie checkout return banner */
.ch-purchase-banner {
  display: flex; align-items: center; gap: var(--space-2);
  margin: 0 0 var(--space-3); padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm); font-size: 0.9rem; font-weight: 600;
}
.ch-purchase-banner svg { flex-shrink: 0; }
.ch-purchase-banner--ok { background: var(--accent); color: var(--on-accent); }
.ch-purchase-banner--ok svg { color: var(--on-accent); }
.ch-purchase-banner--wait { background: var(--line); color: var(--ink); }

/* ── Shared bits ──────────────────────────────────────────────────── */
.ch-badge { display: inline-block; padding: 2px 8px; border-radius: var(--radius-pill); background: var(--line); color: var(--ink); font-size: 0.75rem; white-space: nowrap; }
.ch-badge--ok { background: var(--ok); color: var(--bg); }
.ch-badge--accent { background: var(--accent); color: var(--on-accent); }
/* EEN-CHALLENGE-KAN-CARDIO-ZIJN — pillar label: icon + word on one line.
   inline-flex overrides the inline-block above, so the svg centres against the
   word instead of sitting on the text baseline and stretching the pill. */
.ch-badge--pillar { display: inline-flex; align-items: center; gap: 4px; }
.ch-need { color: var(--danger); font-size: 0.85rem; }
.ch-ok-text { color: var(--ok); font-size: 0.85rem; }
.ch-bar { height: 4px; border-radius: var(--radius-pill); background: var(--line); overflow: hidden; }
.ch-bar__fill { height: 100%; border-radius: var(--radius-pill); background: color-mix(in srgb, var(--accent) 55%, var(--line)); }
/* COLOR-3 — the sRGB midpoint of the cyan accent and the near-neutral track
   line lands noticeably greyer than half-strength accent looks like it should;
   in oklch the mix keeps its chroma. Declared as an override so the sRGB line
   above stays the fallback. */
@supports (color: color-mix(in oklch, red, blue)) {
  .ch-bar__fill { background: color-mix(in oklch, var(--accent) 55%, var(--line)); }
}
.ch-bar__fill--accent { background: var(--accent); }

/* ── Wallet bundles ───────────────────────────────────────────────── */
.ch-bundles { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.ch-bundle { position: relative; flex: 1 1 140px; padding: var(--space-3); border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); }
.ch-bundle__credits { font-weight: 600; color: var(--ink); padding-right: 64px; }
.ch-bundle__price { margin: var(--space-1) 0 var(--space-2); }
.ch-bundle__was { text-decoration: line-through; color: var(--muted); margin-right: 6px; }
.ch-bundle__now { color: var(--ink); font-weight: 600; }
.ch-bundle .ch-badge { position: absolute; top: 6px; right: 6px; }
.ch-bundle__btn { width: 100%; padding: 10px 12px; font-size: 12px; }
.ch-bundle--single { border: 1px solid var(--accent); background: var(--accent-subtle); }
.ch-bundle__sub { margin: 2px 0 0; color: var(--muted); font-size: 0.78rem; }

/* ── Classes ──────────────────────────────────────────────────────── */
.ch-class { padding: var(--space-3); border: 1px solid var(--line); border-radius: var(--radius-sm); margin-bottom: var(--space-3); background: var(--surface); }
.ch-class:last-child { margin-bottom: 0; }
.ch-class__head { display: flex; justify-content: space-between; align-items: center; gap: var(--space-2); }
.ch-class__title { font-weight: 600; color: var(--ink); }
.ch-class__meta { display: flex; flex-wrap: wrap; gap: var(--space-2); color: var(--muted); font-size: 0.85rem; margin-top: var(--space-1); }
.ch-class__cost { color: var(--ink); font-weight: 600; }
.ch-class__capacity { margin-top: var(--space-2); }
.ch-class__capacity-label { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--space-2); color: var(--muted); font-size: 0.8rem; margin-bottom: var(--space-1); }
.ch-class__cutoff { display: inline-flex; align-items: center; gap: 4px; color: var(--muted); font-size: 0.8rem; margin-top: var(--space-2); }
.ch-class__cutoff--urgent { color: var(--danger); }
.ch-class__actions { margin-top: var(--space-3); }
.ch-class__actions .btn { padding: 12px 16px; font-size: 13px; }

/* ── Leaderboard hero ─────────────────────────────────────────────── */
.ch-hero {
  padding: var(--space-4);
  border-radius: var(--radius-sm);
  background: linear-gradient(
    135deg,
    color-mix(in srgb, var(--accent) 16%, var(--surface)),
    var(--surface) 70%
  );
  border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--line));
}
.ch-hero--banner { overflow: hidden; }
.ch-hero__banner {
  position: relative;
  margin: calc(-1 * var(--space-4)) calc(-1 * var(--space-4)) var(--space-3);
  height: 150px;
}
.ch-hero__banner-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.ch-hero__banner::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to bottom,
    transparent 40%,
    color-mix(in srgb, var(--surface) 78%, transparent)
  );
  pointer-events: none;
}
.ch-hero__top { display: flex; align-items: center; gap: var(--space-2); }
.ch-hero__top svg { color: var(--accent); flex-shrink: 0; }
.ch-hero__title { flex: 1; font-size: 1.15rem; font-weight: 700; color: var(--ink); }
.ch-hero__author { margin: var(--space-1) 0 0; color: var(--muted); font-size: 0.8rem; font-weight: 600; }
.ch-hero__desc { margin: var(--space-2) 0 0; color: var(--muted); font-size: 0.85rem; line-height: 1.45; }
/* CHALLENGE-PRIJS-DIE-DE-COACH-ZELF-KIEST */
.ch-hero__prize { display: flex; align-items: center; gap: var(--space-1); margin: var(--space-2) 0 0; color: var(--text); font-size: 0.85rem; font-weight: 600; line-height: 1.35; }
.ch-hero__prize svg { flex: 0 0 auto; color: var(--accent); }
.ch-hero__discount {
  display: inline-block; margin: var(--space-2) 0 0;
  padding: 2px 8px; border-radius: var(--radius-pill);
  background: var(--accent-subtle); color: var(--accent);
  font-size: 0.72rem; font-weight: 600; white-space: nowrap;
}
/* DE-CHALLENGE-BEHEER-JE-VANAF-DE-CHALLENGE — the author's door, one calm row
   under the hero. Bordered, not accent-filled: it sits above the leaderboard the
   participant came to read, so it must be findable without shouting. The waiting
   counter IS the accent — it is the only thing on this card that asks for an
   action today, and it carries --on-accent for its own text. */
.ch-hero__manage {
  display: flex; align-items: center; gap: var(--space-2);
  width: 100%; margin: var(--space-3) 0 0; padding: 10px var(--space-3);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  background: transparent; color: var(--text);
  font: inherit; font-size: 0.85rem; font-weight: 600; text-align: left;
  cursor: pointer;
}
.ch-hero__manage svg { flex: 0 0 auto; color: var(--muted); }
.ch-hero__manage-lbl { flex: 1 1 auto; min-width: 0; }
.ch-hero__manage-count {
  flex: 0 0 auto;
  padding: 2px 8px; border-radius: var(--radius-pill);
  background: var(--accent); color: var(--on-accent);
  font-size: 0.72rem; font-weight: 700; white-space: nowrap;
}
.ch-hero__stats { display: flex; gap: var(--space-4); margin-top: var(--space-3); flex-wrap: wrap; }
.ch-hero__stat-label { color: var(--muted); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em; }
.ch-hero__stat-value { color: var(--ink); font-size: 0.9rem; margin-top: 2px; }
.ch-hero__mybest-num { font-size: 1.7rem; font-weight: 700; color: var(--ink); line-height: 1.1; }
.ch-hero__mybest-unit { color: var(--muted); font-size: 0.9rem; }
/* CRESCO90-HET-SPEL-LEGT-ZICHZELF-UIT — het plafond reist met het cijfer mee,
   ook op de tegel. Een kale "2" onder "Cresco-score" is geen informatie; dit is
   dezelfde maat als bovenaan de wereld, in dezelfde verhouding: de stand groot,
   het plafond kleiner en gedempt. */
.ch-hero__mybest-max { color: var(--muted); font-size: 0.9rem; font-weight: 700; }
.ch-hero__pending { display: inline-flex; align-items: center; gap: 4px; margin-top: 4px; color: var(--accent); font-size: 0.75rem; }
.ch-hero__stats--tappable { cursor: pointer; align-items: center; }
.ch-hero__stats--tappable:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--radius-md); }
.ch-hero__stats-chev { margin-left: auto; color: var(--muted); flex: 0 0 auto; transform: rotate(-90deg); }
.ch-challenge__standings--flash { animation: chStandingsFlash var(--dur-slow) var(--ease-standard); border-radius: var(--radius-lg); }
@keyframes chStandingsFlash {
  0% { box-shadow: 0 0 0 0 var(--accent-subtle); }
  30% { box-shadow: 0 0 0 4px var(--accent-subtle); }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* ── Podium ───────────────────────────────────────────────────────── */
.ch-podium {
  display: grid; grid-template-columns: 1fr 1fr 1fr;
  gap: var(--space-2); align-items: end;
  margin: var(--space-4) 0 var(--space-2);
}
.ch-podium__spot { display: flex; flex-direction: column; align-items: center; gap: 4px; min-width: 0; }
.ch-podium__medal {
  display: flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: 50%;
  border: 2px solid var(--accent); color: var(--accent);
  background: var(--surface);
  font-weight: 700;
}
.ch-podium__spot--r1 .ch-podium__medal { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.ch-podium__rank-num { font-size: 0.9rem; }
.ch-podium__bar {
  width: 100%; border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  background: linear-gradient(
    to top,
    color-mix(in srgb, var(--accent) 12%, var(--surface)),
    color-mix(in srgb, var(--accent) 45%, var(--surface))
  );
  border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--line));
  border-bottom-width: 2px;
  transform-origin: bottom;
}
.ch-podium__spot--r1 .ch-podium__bar { height: 72px; }
.ch-podium__spot--r2 .ch-podium__bar { height: 52px; }
.ch-podium__spot--r3 .ch-podium__bar { height: 38px; }
/* CHALLENGE-PODIUM-BAR-PHOTO-FILL — best-entry evidence photo fills the column
   (inline background-image set in JS). Same shape/height/animation; a neutral
   base shows if the photo 403/404s, and a bottom gradient keeps the medal/name/
   value legible over any photo in both Dag and Nacht. */
.ch-podium__bar--photo {
  background-color: color-mix(in srgb, var(--accent) 18%, var(--surface));
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  position: relative;
  overflow: hidden;
}
.ch-podium__bar--photo::after {
  content: '';
  position: absolute; inset: 0;
  border-radius: inherit;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0) 55%);
  pointer-events: none;
}
/* display:block is load-bearing: the name cell is a <span> (or an <a> when the
   participant is a trainer with a published marketplace profile), not the <div>
   it used to be — see chNameNode in challenges/index.js. */
.ch-podium__name { display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.85rem; font-weight: 600; color: var(--ink); }
.ch-podium__val { font-size: 0.75rem; color: var(--muted); }
.ch-podium__spot.is-mine .ch-podium__name { color: var(--accent); }
/* CHALLENGE-KLASSEMENT-KLAAR-VOOR-DE-ZAAL — a name that leads to a trainer's
   marketplace profile. Only opted-in trainers get this; everyone else is plain
   text, so the underline is the ONLY thing that says "this one is tappable". */
.ch-podium__name--link,
.ch-lb__name--link { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; cursor: pointer; }

/* ── Leaderboard rows 4+ ──────────────────────────────────────────── */
.ch-lb { list-style: none; margin: var(--space-2) 0 0; padding: 0; }
.ch-lb__row { padding: var(--space-2) var(--space-2); border-radius: var(--radius-sm); }
.ch-lb__row + .ch-lb__row { margin-top: 2px; }
.ch-lb__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-2); margin-bottom: 5px; }
.ch-lb__left { display: flex; align-items: baseline; gap: var(--space-2); min-width: 0; }
.ch-lb__rank { color: var(--muted); font-size: 0.8rem; min-width: 2em; }
.ch-lb__name { color: var(--ink); font-size: 0.9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ch-lb__val { color: var(--ink); font-weight: 600; font-size: 0.9rem; white-space: nowrap; }
.ch-lb__you { padding: 1px 7px; border-radius: var(--radius-pill); background: var(--accent); color: var(--on-accent); font-size: 0.7rem; font-weight: 700; }
.ch-lb__row--mine {
  background: color-mix(in srgb, var(--accent) 10%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line));
}
.ch-lb__row--mine .ch-lb__name { font-weight: 700; }
/* Unofficial (not-yet-checked) pending row: dimmed, icon-only badge + label. */
.ch-lb__row--pending { opacity: 0.6; }
.ch-lb__unchecked {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 2em; color: var(--muted);
}
.ch-lb__unchecked svg { flex-shrink: 0; }
.ch-lb__unchecked-note {
  color: var(--muted); font-size: 0.72rem; margin: 0 0 5px;
}
.ch-bar__fill--pending {
  background: repeating-linear-gradient(
    45deg,
    color-mix(in srgb, var(--muted) 40%, var(--line)) 0 6px,
    color-mix(in srgb, var(--muted) 20%, var(--line)) 6px 12px
  );
}
.ch-goal {
  display: flex; align-items: center; gap: 6px;
  margin-top: var(--space-2); padding: var(--space-2) var(--space-2);
  color: var(--accent); font-size: 0.85rem; font-weight: 600;
}
.ch-goal svg { flex-shrink: 0; }

/* ── The trainer's own line, above a ranking he is not in ──────────────
   CHALLENGE-TRAINERS-STAAN-NIET-OP-HET-LEDENBORD. Deliberately NOT styled
   like .ch-lb__row--mine: an accent-filled row reads as "a place", and this
   line is exactly the absence of one. Muted surface, no rank column, no bar. */
.ch-outside {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  margin: 0 0 var(--space-2); padding: var(--space-2) var(--space-3);
  background: var(--surface-card); border: 1px dashed var(--line);
  border-radius: var(--radius-sm);
}
.ch-outside svg { flex-shrink: 0; color: var(--muted); }
.ch-outside__label { color: var(--muted); font-size: 0.8rem; }
.ch-outside__val { margin-left: auto; color: var(--ink); font-weight: 600; font-size: 0.9rem; white-space: nowrap; }
.ch-outside__pending {
  flex-basis: 100%; color: var(--muted); font-size: 0.72rem;
}

/* ── Entry to the full per-category leaderboard (under the top-3 podium) ── */
.ch-lb-more {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
  width: 100%; margin: var(--space-2) 0 0; padding: var(--space-2) var(--space-3);
  background: var(--surface-card); border: 1px solid var(--line);
  border-radius: var(--radius-sm); color: var(--ink);
  font-size: 0.85rem; font-weight: 600; cursor: pointer; text-align: left;
}
.ch-lb-more:hover { border-color: var(--accent); }
.ch-lb-more__label { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.ch-lb-more__label svg { flex-shrink: 0; color: var(--accent); }
.ch-lb-more > svg { flex-shrink: 0; color: var(--muted); }

/* ── Category leaderboard overlay (horizontal scroll-snap cards) ───── */
.ch-cat-overlay {
  position: fixed; inset: 0; z-index: 9400;
  display: flex; align-items: flex-end; justify-content: center;
  background: rgba(0, 0, 0, 0.55);
}
.ch-cat-sheet {
  display: flex; flex-direction: column;
  width: 100%; max-width: 640px; height: 88vh; max-height: 88vh;
  background: var(--bg);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: var(--elev-up);
  overflow: hidden;
}
.ch-cat-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}
.ch-cat-header__title { font-size: 1rem; font-weight: 700; color: var(--ink); }
.ch-cat-header__close {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; margin: -8px -8px -8px 0;
  background: none; border: none; color: var(--muted); cursor: pointer;
}
.ch-cat-header__close:hover { color: var(--ink); }
.ch-cat-track {
  display: flex; gap: var(--space-3); flex: 1 1 auto; min-height: 0;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
  padding: var(--space-3) var(--space-4);
  scrollbar-width: none;
}
.ch-cat-track::-webkit-scrollbar { display: none; }
.ch-cat-card {
  flex: 0 0 86%; scroll-snap-align: center;
  display: flex; flex-direction: column; min-height: 0;
  background: var(--surface-card); border: 1px solid var(--line);
  border-radius: var(--radius-lg); overflow: hidden;
}
.ch-cat-card__title {
  margin: 0; padding: var(--space-3) var(--space-3) var(--space-2);
  font-size: 0.95rem; font-weight: 700; color: var(--ink);
  border-bottom: 1px solid var(--line); flex-shrink: 0;
}
.ch-cat-card__body {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding: var(--space-2) var(--space-3) var(--space-3);
  -webkit-overflow-scrolling: touch;
}
.ch-cat-card__empty { padding: var(--space-4) var(--space-2); text-align: center; }
.ch-cat-dots {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: var(--space-2) 0 max(var(--space-3), env(safe-area-inset-bottom));
  flex-shrink: 0;
}
.ch-cat-dot {
  width: 8px; height: 8px; padding: 0; border: none; border-radius: var(--radius-pill);
  background: var(--line); cursor: pointer; transition: background var(--dur-base) var(--ease-standard), width var(--dur-base) var(--ease-standard);
}
.ch-cat-dot.is-active { width: 22px; background: var(--accent); }

/* ── DE-CRESCO90-WERELD-ACHTER-DE-TEGEL — the world behind the tile ──
   Rides on .ch-cat-overlay / .ch-cat-sheet (same scrim, same sheet, same
   close affordance); everything below only fills the body. Nothing in here
   is interactive except the close button — the screen reports, it never asks
   the member to tick anything off. */
.cr9-sheet { height: 92vh; max-height: 92vh; }
.cr9-header { padding: var(--space-3) var(--space-4); }
.cr9-brand { display: flex; align-items: center; min-width: 0; }
/* The wordmark is a WHITE transparent PNG, so on a light background it would
   be invisible; inverting it is what the original app did too. */
.cr9-brand__mark { display: block; height: 22px; width: auto; }
:root[data-theme-mode="light"] .cr9-brand__mark { filter: invert(1); }
.cr9-body {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: var(--space-4);
  display: flex; flex-direction: column; gap: var(--space-4);
}
.cr9-week__title,
.cr9-board__title { margin: 0 0 var(--space-2); font-size: 1rem; font-weight: 700; color: var(--ink); }

/* ── CRESCO90-MAANDAG-ZEGT-WAT-JE-WEEK-WAS ────────────────────────────────────
   Eén rustige regel bovenaan de wereld, met het cijfer van de week die 's nachts
   sloot. Bewust GEEN kleur die iets vindt: de tekst staat op --ink op een
   nauwelijks zichtbaar vlak met een randje. Rood is hier onmogelijk, en dat is
   het punt — dezelfde regel draagt zowel "Je week: 68. 1 bolletje." als
   "Je week: 45.", en die twee horen er identiek uit te zien.

   De houder staat op display:contents, dus zolang er niets in zit bestaat er
   geen vak. .cr9-body is een flexkolom MET gap, en een lege div zou daar de
   volle var(--space-4) aan lucht boven het cijfer hebben opgeleverd. */
.cr9-weeknote-slot { display: contents; }
.cr9-weeknote {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  font-size: 0.9rem;
  line-height: 1.4;
  color: var(--ink);
  text-align: center;
}

/* ── CRESCO90-HET-SPEL-LEGT-ZICHZELF-UIT ──────────────────────────────────────
   HET CIJFER MET ZIJN PLAFOND. Het stond in de kop van het weekblok, kaal, en
   las daar als het cijfer van díé week; negentig kwam op het hele blad niet
   voor. Nu staat het bovenaan, groot, met zijn plafond eraan vast — één keer,
   zodat nergens meer uitgelegd hoeft te worden wat het maximum is.
   De stand is groot en op de accentkleur, het plafond kleiner en gedempt: het
   oog leest eerst waar je staat en daarna pas waartegen. */
.cr9-score { text-align: center; }
.cr9-score__row {
  margin: 0; display: flex; align-items: baseline; justify-content: center; gap: 6px;
}
.cr9-score__now { font-size: 3rem; font-weight: 800; line-height: 1; color: var(--accent); }
.cr9-score__max { font-size: 1.25rem; font-weight: 700; line-height: 1; color: var(--muted); }
.cr9-score__label { display: block; margin-top: 4px; font-size: 12px; color: var(--muted); }

/* ÉÉN RIJ VAN TWEE BOLLETJES. Er stonden er twee rijen op één blad — twee bij de
   poort, vier op de kaart — met hetzelfde label eronder. Dit is er één, en sinds
   CRESCO90-DE-LADDER-HEEFT-DRIE-STREPEN telt hij naar de VOLGENDE streep: twee
   goede weken, altijd twee. Het poortvinkje halverwege is daarmee vervallen — er
   valt niets te markeren als de rij zelf de drempel is.
   De bolletjes zijn 18px: groot genoeg om van een meter afstand te tellen,
   want dit is het ene ding dat een deelnemer dagelijks checkt. */
.cr9-dots {
  display: flex; align-items: center; justify-content: center; gap: 10px;
}
.cr9-dots .cr9-dot { width: 18px; height: 18px; border-width: 2px; }

/* DE DRIE REGELS. Wat er van 101 woorden uitleg over is: drie zinnen van
   hoogstens twaalf woorden, ONDER het model dat ze laat zien. Gedempt en nooit
   rood — het is een bijschrift bij iets dat je net hebt zien gebeuren, geen
   waarschuwing. */
.cr9-rules { margin-top: var(--space-3); }
.cr9-rules__line {
  margin: 0 0 4px; font-size: 13px; line-height: 1.5; color: var(--muted); text-align: center;
}
.cr9-rules__line:last-child { margin-bottom: 0; }

/* HET TIKBARE MODEL. Dezelfde ringen en dezelfde bolletjesrij als de echte,
   alleen met een knop eronder. De schijf is in deze stand een <button>, dus de
   knopstijl van de browser moet eraf: de ring zelf IS de affordance. */
.cr9-demo { text-align: center; }
.cr9-demo__title { margin: 0 0 var(--space-3); font-size: 1rem; font-weight: 700; color: var(--ink); }
.cr9-demo__dotwrap { margin-top: var(--space-3); }
.cr9-ring__tap {
  appearance: none; -webkit-appearance: none;
  padding: 0; border: 0; background: none; font: inherit; color: inherit;
  cursor: pointer;
}
.cr9-ring__tap:active { transform: scale(0.96); }
/* Het kaartsilhouet: de eerste echte kaart uit de ladder, gedempt tot de week
   vol is. Geen rand en geen vlak — het is een silhouet dat oplicht, geen tweede
   kaartblok. De vorm wordt HIER teruggedraaid met een extra klasse erbij, omdat
   .cr9-card zelf verderop in dit bestand staat en anders wint. */
.cr9-demo__card { margin-top: var(--space-3); display: flex; justify-content: center; }
.cr9-demo__card .cr9-card--demo {
  margin: 0; padding: 0; border: 0; background: none;
  /* De kaart is sinds CRESCO90-JE-KAART-DRAAGT-JE-STREPEN-EN-JE-KROON een PLAAT met
     een bodemhoogte. Hier hangt alleen het schild in, dus die hoogte moet eruit —
     anders staat er een leeg vlak van 168px onder de uitleg. */
  min-height: 0;
  opacity: 0.28; filter: grayscale(1);
  transition: opacity 220ms ease, filter 220ms ease;
}
.cr9-demo__card .cr9-card--demo.cr9-card--lit { opacity: 1; filter: none; }
@media (prefers-reduced-motion: reduce) {
  .cr9-demo__card .cr9-card--demo { transition: none; }
  .cr9-ring__tap:active { transform: none; }
}
/* CRESCO90-DE-VOORDEUR-SPREEKT-DE-TAAL-VAN-DE-LADDER — `cr9-demo--still` wordt in
   JAVASCRIPT gezet (cr9Motion) en niet door een mediaregel. Het blok hierboven
   dekt de voorkeur die AL aan stond toen het blad werd getekend; deze klasse dekt
   dezelfde voorkeur voor de stand die daarna omslaat, en samen zeggen ze hetzelfde:
   het model WISSELT direct, het beweegt nergens naartoe. */
.cr9-demo--still .cr9-card--demo,
.cr9-demo--still .cr9-ring__fill,
.cr9-demo--still .cr9-ring__art,
.cr9-demo--still .cr9-dot { transition: none; animation: none; }
.cr9-demo--still .cr9-ring__tap:active { transform: none; }

/* De knop om het model opnieuw te spelen. Stil en klein: hij staat er alleen als
   je de twee weken hebt uitgespeeld, en dan is het aanbod van het blad de knop
   ERONDER ("Begin op Makkelijk") en niet deze. Vandaar geen accentvlak. */
.cr9-demo__replay {
  appearance: none; -webkit-appearance: none;
  margin-top: var(--space-3);
  padding: 6px 14px;
  border: 1px solid var(--line); border-radius: 999px;
  background: none; color: var(--muted);
  font: inherit; font-size: 13px; cursor: pointer;
}
.cr9-demo__replay:hover { color: var(--ink); }
.cr9-demo__stripwrap { margin-top: var(--space-3); }

/* HET LADDERRIJTJE OP DE VOORDEUR. Vier namen op een rij, de trede waar je staat
   in het accent (de limoen), de andere drie gedempt met een slot erachter. Eigen
   klassen naast .cr9-tiers__* omdat drie zustertoetsen het aantal rijen en het
   ene `--here` van DAT rijtje pinnen; zie ladderStrip() in cresco90.js. */
.cr9-lstrip {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 6px;
  text-align: left;
}
.cr9-lstrip__row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
  padding: 8px 12px;
  border: 1px solid var(--line); border-radius: var(--radius-md, 10px);
}
.cr9-lstrip__name { font-size: 13px; font-weight: 600; color: var(--ink); }
/* De trede waar je staat. Accentrand en accentkleur — nooit een vlak, want dan
   leest hij als een knop en er valt hier niets te tikken. */
.cr9-lstrip__row--here { border-color: var(--accent); border-width: 2px; padding: 7px 11px; }
.cr9-lstrip__row--here .cr9-lstrip__name { color: var(--accent); }
/* Op slot is geen fout: gedempt, met het slot in de stille kleur. Nooit rood. */
.cr9-lstrip__row--locked { opacity: 0.45; }
.cr9-lstrip__lock { display: inline-flex; color: var(--muted); }
.cr9-lstrip__row--done .cr9-lstrip__name { color: var(--muted); }

/* DE EERSTE DAG. Eén kaartje onder de ringen, met de ene knop die je schema opent.
   Het bestaat één dag en verdwijnt zodra je iets logt, dus het mag opvallen. */
.cr9-firstday {
  margin-top: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--accent); border-radius: var(--radius-lg);
  text-align: center;
}
.cr9-firstday__line {
  margin: 0 0 var(--space-3); font-size: 14px; font-weight: 600; color: var(--ink);
}
.cr9-firstday__btn { width: 100%; }
/* DE KRACHT-RING PULSEERT. De klasse op het blad wordt alleen GEZET als er bewogen
   mag worden (cr9Motion), dus deze regel is nooit aan voor wie beweging uitzette;
   het blok eronder vangt de voorkeur die ná het openen omslaat. Niet de demoringen:
   die horen bij de voordeur en daar is geen eerste dag. */
.cr9-world--firstday .cr9-ring-row:not(.cr9-demo__rings) .cr9-ring__art[data-pillar="strength"] {
  animation: cr9FirstDayPulse 2s ease-in-out infinite;
}
@keyframes cr9FirstDayPulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.06); }
}
@media (prefers-reduced-motion: reduce) {
  .cr9-world--firstday .cr9-ring-row:not(.cr9-demo__rings) .cr9-ring__art[data-pillar="strength"] {
    animation: none;
  }
}

.cr9-week {
  padding: var(--space-3); border: 1px solid var(--line);
  border-radius: var(--radius-lg); background: var(--surface, var(--bg));
}
.cr9-week__head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-3); }
/* CRESCO90-JE-WEEK-IS-VIER-RINGEN-EN-JE-SWIPET-TERUG — de ene regel onder de
   strook. Hij legt uit wat je net gelezen hebt: een telling, geen cijfer. De
   kop die hier ooit boven stond ("Vorige week") is verdwenen met het blok dat
   hij benoemde; de strook zegt nu zelf welke week je bekijkt. */
.cr9-weekcap__note { margin: 2px 0 0; font-size: 11px; line-height: 1.5; color: var(--muted); }
.cr9-weekcap__note--after { margin-top: var(--space-2); }
/* ── CRESCO90-JE-WEEK-IS-VIER-RINGEN-EN-JE-SWIPET-TERUG (Jayy, 2026-09-15) ──
   Eén strook, één week. De twee blokken onder elkaar ("Vorige week" boven
   "Deze week tot nu toe", elk een lijst van vier rijen met een balkje) zijn hier
   verdwenen als vorm; wat ervoor in de plaats komt is een swipebare rij pagina's
   met vier segmentringen per pagina.

   DE MECHANIEK IS DIE VAN DE GYM-WEEKSTRIP: scroll + scroll-snap en verder
   niets. De waarden hieronder zijn met opzet dezelfde als .fz-wkstrip__pages in
   public/strength.html — dat is wat maakt dat de veeg hier hetzelfde aanvoelt
   als daar. De gym leeft in het krachtframe en deelt geen stylesheet met dit
   scherm, dus de twee blokken worden met de hand gelijk gehouden. */
.cr9-wkstrip { margin: var(--space-3) 0 0; }
.cr9-wkstrip__title {
  font-size: 13px; font-weight: 700; color: var(--ink);
  margin: 0 0 var(--space-2);
}
.cr9-wkstrip__pages {
  display: flex; overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x mandatory; scroll-behavior: auto;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}
.cr9-wkstrip__pages::-webkit-scrollbar { display: none; }
.cr9-wkstrip__page { flex: 0 0 100%; scroll-snap-align: start; }
@media (prefers-reduced-motion: reduce) {
  .cr9-wkstrip__pages { scroll-behavior: auto; scroll-snap-type: x mandatory; }
}
/* CRESCO90-ELKE-KNOP-DOET-WAT-HIJ-ZEGT — één bolletje per pagina, het huidige
   gevuld. ZE ZIJN GEEN KNOPPEN: ze staan er om te zeggen dat er meer naast
   staat, niet om aangetikt te worden, dus geen hit-gebied, geen hover en geen
   cursor. Twee tinten uit de tokens en verder niets — een accentkleur zou een
   stand als een prestatie laten lezen. */
.cr9-wkstrip__dots {
  display: flex; justify-content: center; gap: 6px;
  margin-top: var(--space-2);
}
.cr9-wkstrip__dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--line);
}
.cr9-wkstrip__dot--on { background: var(--muted); }
/* ── CRESCO90-ELKE-RING-ZEGT-WAT-HIJ-IS-EN-WAT-JE-MOET-DOEN (Jayy, 2026-09-18) ──
   TWEE RIJEN VAN TWEE, geen vier naast elkaar meer. De ring zelf zou op één rij
   nog passen — op 390px houdt een kolom van vier ~83px over en daar kan een
   ring van 76px in — maar het KAARTJE eronder past daar niet: "Train je schema
   Push Day, 3 keer deze week" valt in zo'n kolom uiteen in acht regels van één
   woord. De fasespec geeft die afweging expliciet aan leesbaarheid, dus het
   worden twee kolommen van ~167px: de zin staat in drie regels en de ring gaat
   van 56 naar 76px.

   DE TWEE KOLOMMEN REKKEN MEE (`stretch`, de standaard, dus niet `start`): een
   zin van twee regels naast een van drie gaf twee kaartjes die onderaan niet
   uitkwamen, en twee omrande vlakken met een verschillende onderrand naast
   elkaar lezen als een fout. Het kaartje groeit daarom mee met de hoogste van
   het paar en centreert zijn tekst zelf. */
.cr9-ring-row {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(2, 1fr);
  gap: var(--space-3);
}
.cr9-ring {
  min-width: 0;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
}
/* De pijlernaam boven de ring. Eén woord, dus nowrap met een ellips als een
   vertaling toch langer uitvalt — een naam die afbreekt duwt de ring omlaag en
   dan staan de vier schijven niet meer op één lijn. */
.cr9-ring__name {
  font-size: 12px; font-weight: 700; color: var(--ink);
  max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cr9-ring__disc {
  position: relative; display: block;
  width: 76px; height: 76px; max-width: 100%;
}
/* EXPLICIETE pixels op de svg, geen 100% (GROCERIES-SORT-BTN-ICON-SIZE-FIX):
   een svg op procenten in een geneste span erft een onbegrensde intrinsieke maat
   zodra een voorouder hem niet afknijpt. De maat staat dus twee keer — hier en
   in de smalle-schermregel onderaan — en dat is met opzet. */
.cr9-ring__svg { display: block; width: 76px; height: 76px; }
/* De onvervulde stukken. Nooit rood en nooit weg: ook een week waarin niets
   gebeurde moet een LEESBARE ring zijn, anders lijkt de pijler te ontbreken in
   plaats van leeg te staan. */
.cr9-ring__track { stroke: var(--line); }
/* CRESCO90-ELKE-PIJLERDAG-IS-EEN-MUNT-IN-ZIJN-KLEUR — de gevulde stukken zijn de
   MUNTEN, en een munt draagt de kleur van zijn pijler. De variabele wordt op de
   <li> gezet (zie het blok onder .cr9-ring__art) en niet hier per pijler
   uitgeschreven: dan staat de kleurkeuze op ÉÉN plek en erft ook het icoon in het
   midden hem, zonder een tweede lijst van vier selectors.
   De terugval op --accent is met opzet en niet vrijblijvend: de proefstand en elke
   toekomstige ring zonder data-pijler blijven zo tekenen in plaats van zwart
   (een niet-bestaande custom property maakt `stroke` ongeldig → zwart). */
.cr9-ring__fill { stroke: var(--cr9-coin, var(--accent)); }
/* Tabulaire cijfers zodat "1 / 6" en "10 / 6" even breed staan, en nowrap zodat
   de telling nooit over twee regels valt op een smal scherm. */
.cr9-ring__count {
  font-size: 12px; font-weight: 700; color: var(--muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis;
}
.cr9-ring__count--hit { color: var(--accent); }
/* HET DOELKAARTJE — wat je moet doen om deze ring vol te krijgen, en één tik
   naar dat werk. Een RUSTIGE instructie: gedempte tekst op een vlak dat maar
   net van de achtergrond verschilt, nooit een accentkleur en nooit rood. Dit
   kaartje moedigt niet aan, het vertelt.

   `font: inherit` vóór de font-size, want een <button> erft van zichzelf niet
   de letter van de app maar die van het besturingssysteem; de volgorde is dus
   niet vrij — de shorthand zou een losse font-size erboven weer overschrijven. */
.cr9-ring__goal {
  flex: 1 1 auto; width: 100%; margin: 2px 0 0; padding: 8px;
  display: flex; align-items: center; justify-content: center;
  font: inherit; font-size: 11px; line-height: 1.45; text-align: center;
  color: var(--muted); background: var(--surface-muted);
  border: 1px solid var(--line); border-radius: 10px;
  -webkit-appearance: none; appearance: none; cursor: pointer;
}
.cr9-ring__goal:hover { color: var(--ink); border-color: var(--accent); }
.cr9-ring__goal:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Op de smalste telefoons (360px) krimpt de ring mee; de telling en het kaartje
   worden niet kleiner, want dát is wat gelezen moet worden. */
@media (max-width: 380px) {
  .cr9-ring__disc { width: 64px; height: 64px; }
  .cr9-ring__svg { width: 64px; height: 64px; }
  .cr9-ring-row { gap: var(--space-2); }
}
/* CRESCO90-KRIJGT-ZIJN-EIGEN-GEZICHT — the pillar drawings.
   The source art is WHITE line work on transparency, drawn for the old app's
   dark screens; painted as an <img> it disappears on the day theme. So the
   file is used as a MASK and the paint is --accent, which is legible on both
   presets and needs no second theme mechanic and no per-theme duplicate. */
.cr9-pillar__art {
  flex: 0 0 auto; width: 34px; height: 34px;
  background-color: var(--accent);
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: contain; mask-size: contain;
}
.cr9-pillar__art[data-pillar="strength"] {
  -webkit-mask-image: url('/assets/img/cresco90/pillar-strength.webp');
  mask-image: url('/assets/img/cresco90/pillar-strength.webp');
}
.cr9-pillar__art[data-pillar="cardio"] {
  -webkit-mask-image: url('/assets/img/cresco90/pillar-cardio.webp');
  mask-image: url('/assets/img/cresco90/pillar-cardio.webp');
}
.cr9-pillar__art[data-pillar="nutrition"] {
  -webkit-mask-image: url('/assets/img/cresco90/pillar-nutrition.webp');
  mask-image: url('/assets/img/cresco90/pillar-nutrition.webp');
}
.cr9-pillar__art[data-pillar="mobility"] {
  -webkit-mask-image: url('/assets/img/cresco90/pillar-mobility.webp');
  mask-image: url('/assets/img/cresco90/pillar-mobility.webp');
}
/* CRESCO90-JE-WEEK-IS-VIER-RINGEN-EN-JE-SWIPET-TERUG — dezelfde tekening, nu in
   het MIDDEN van een ring in plaats van links van een rij. Alleen de plaatsing
   en de maat veranderen; het masker en zijn kleur blijven precies wat de regels
   hierboven ervan maken, want anders staat er een tweede thema-mechaniek.

   DEZE REGEL MOET HIERONDER STAAN, niet bij de rest van de ring-opmaak. Hij
   heeft exact dezelfde specificiteit als `.cr9-pillar__art` (één klasse), dus de
   LATERE regel wint. Dat de twee maten nu allebei 34px zijn maakt die volgorde
   niet vrijblijvend: de plaatsing (absoluut, gecentreerd) komt van hier.

   CRESCO90-ELKE-RING-ZEGT-WAT-HIJ-IS-EN-WAT-JE-MOET-DOEN: 24px was op een ring
   van 56px niet te herkennen ("zo klein dat je niet kan zien wat het is"). De
   ring is nu 76px en de lijn schaalt mee met de viewBox (5/56 → ~6,8px), dus de
   binnenruimte is ruwweg 62px — een tekening van 34px vult daar ongeveer de
   helft van, en dat is precies de maat die de pijlerrij op het keuzeblad al
   gebruikt. */
.cr9-ring__art {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 34px; height: 34px;
  /* CRESCO90-ELKE-PIJLERDAG-IS-EEN-MUNT-IN-ZIJN-KLEUR — het icoon in het midden
     draagt de kleur van ZIJN pijler, niet het limoen van de app. Dat mag hier
     omdat deze regel al ná .cr9-pillar__art staat en dus met gelijke
     specificiteit wint; de pijlerRIJ op het keuzeblad blijft limoen, want die
     zet --cr9-coin niet en valt op de terugval terug. Een rij is geen munt. */
  background-color: var(--cr9-coin, var(--accent));
}
/* ── CRESCO90-ELKE-PIJLERDAG-IS-EEN-MUNT-IN-ZIJN-KLEUR ──
   ÉÉN variabele per ring, en alles binnen die ring erft hem: de munten
   (.cr9-ring__fill), het icoon in het midden (.cr9-ring__art) en de munt die
   naar zijn plek vliegt (.cr9-coin). Het alternatief — vier selectors maal drie
   onderdelen — laat een vijfde onderdeel morgen stil in limoen staan.

   De pijler staat als DATA-ATTRIBUUT op de <li> en niet als klasse, precies
   zoals .cr9-pillar__art het al deed: dezelfde sleutel die de server stuurt
   ('strength'|'cardio'|'nutrition'|'mobility'), geen tweede naamgeving ernaast.

   Waarom deze acht hexes en geen andere: zie het tokenblok bovenaan dit bestand
   (gemeten contrast + kleurenblindheidssimulatie). KLEUR IS NOOIT HET ENIGE
   SIGNAAL — elke munt zit in een ring met de pijlerNAAM erboven, de telling
   eronder in --ink en het pijlerICOON in het midden. Wie geen kleur ziet leest
   dezelfde week. */
.cr9-ring[data-pillar="strength"]  { --cr9-coin: var(--cr9-strength); }
.cr9-ring[data-pillar="cardio"]    { --cr9-coin: var(--cr9-cardio); }
.cr9-ring[data-pillar="nutrition"] { --cr9-coin: var(--cr9-nutrition); }
.cr9-ring[data-pillar="mobility"]  { --cr9-coin: var(--cr9-mobility); }
/* EEN VOLLE RING KRIJGT EEN LIMOENEN RAND. Limoen is van geen pijler: het is de
   kleur van vol, gehaald en jij — dus de munten blijven hun eigen kleur en het
   FEIT "deze ring is vol" spreekt in de kleur van de app. Een ::after en geen
   border op de schijf zelf: een rand op .cr9-ring__disc zou de svg 2px naar
   binnen duwen en alle vier de ringen een andere maat geven zodra er één vol is.
   inset -3px legt hem buiten de lijn van 5px, zodat hij de munten niet overlapt. */
.cr9-ring--full .cr9-ring__disc::after {
  content: ''; position: absolute; inset: -3px;
  border: 1px solid var(--accent); border-radius: 50%;
  box-shadow: 0 0 10px 1px var(--accent-subtle);
  pointer-events: none;
}
/* "12 van 15 munten" — ÉÉN regel onder de vier ringen, en alleen bij een week
   die gemeten is: onder een lege voorbeeldrij zou "0 van 15" een uitslag
   beweren over een week die nog niet begonnen is. Gedempt en klein, want het is
   de samenvatting van wat er boven al staat, geen tweede mededeling. */
.cr9-coins {
  margin: var(--space-2) 0 0; text-align: center;
  font-size: 12px; font-weight: 700; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* DE MUNT DIE JE NET VERDIENDE. Hij hangt IN de schijf (die staat op
   position:relative) en wordt vanaf het midden naar zijn plek geschoven, met
   --cr9-coin-x/y die lib-cresco-ring.js erop zet. Dat de baan in de CSS staat en
   de plek in de JS is geen willekeur: de PLEK volgt uit de ringmeetkunde (één
   bron), de BAAN is beeld en hoort bij het beeld.

   Het icoon erin staat in --surface en niet in --ink: de munt is een gevuld vlak
   in de pijlerkleur, en die vier kleuren halen allemaal 3:1 op hun ondergrond —
   maar een donkere lijn op #2ED3A0 en dezelfde lijn op #1F6FD1 zijn twee heel
   verschillende dingen. Het kaartvlak is de ene kleur die op alle acht werkt. */
.cr9-coin {
  position: absolute; top: 50%; left: 50%; z-index: 2;
  width: 26px; height: 26px; margin: -13px 0 0 -13px;
  border-radius: 50%; background: var(--cr9-coin, var(--accent));
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;
}
.cr9-coin__art { width: 15px; height: 15px; background-color: var(--surface); }
/* GEEN VLUCHT ZONDER DE KLASSE, en de klasse wordt niet gezet als de telefoon om
   rust vraagt (coinFlyClass). Deze @media is de TWEEDE tand op dezelfde afspraak:
   zou de klasse ooit toch landen, dan staat de munt hier meteen op zijn plek in
   plaats van 620 ms te bewegen. Zonder klasse is de munt er helemaal niet — het
   scherm wisselt de ring dan direct om. */
@media (prefers-reduced-motion: no-preference) {
  .cr9-coin--fly { animation: cr9CoinFly .62s cubic-bezier(.22, .61, .36, 1) both; }
}
/* De BOOG. Niet een rechte lijn van A naar B: een munt die in een boog aankomt
   leest als iets dat GEGOOID is en ergens landt, en dat is het gevoel dat deze
   fase moet geven. Het 60%-beeld staat 14px HOGER dan de rechte lijn — dat is de
   hele boog, en meer zou hem uit de kaart laten lopen. */
@keyframes cr9CoinFly {
  0%   { opacity: 0; transform: translate(0, 44px) scale(.5); }
  22%  { opacity: 1; transform: translate(calc(var(--cr9-coin-x, 0px) * .25), 16px) scale(1.06); }
  60%  { opacity: 1; transform: translate(calc(var(--cr9-coin-x, 0px) * .7), calc(var(--cr9-coin-y, 0px) * .7 - 14px)) scale(1); }
  100% { opacity: 1; transform: translate(var(--cr9-coin-x, 0px), var(--cr9-coin-y, 0px)) scale(.64); }
}
/* HET MUNTMOMENT ALS KAART (coinCard in lib-cresco-ring.js) — ÉÉN ring in plaats
   van vier, voor de twee plekken waar er nog géén ring stond toen de munt viel:
   onderaan de Voeding-tab (hier) en het cardio-blad (public/strength.html, waar
   dit blok een tweelingbroer heeft — dezelfde reden dat het tokenblok en de munt
   daar ook twee keer staan: het iframe deelt geen enkel symbool met de SPA).

   De MAAT staat hier niet. coinCard zet die inline, want de Voeding-tab heeft de
   ruimte voor 76px (net als de wereld) en het cardio-blad niet. Dat is ook de
   reden dat de svg-maat hieronder op 100% moet: .cr9-ring__svg pint 76px voor de
   wereld, en zonder deze regel zou de ring in het blad uit zijn schijf lopen. */
.cr9-mint { text-align: center; }
.cr9-mint[data-pillar="strength"]  { --cr9-coin: var(--cr9-strength); }
.cr9-mint[data-pillar="cardio"]    { --cr9-coin: var(--cr9-cardio); }
.cr9-mint[data-pillar="nutrition"] { --cr9-coin: var(--cr9-nutrition); }
.cr9-mint[data-pillar="mobility"]  { --cr9-coin: var(--cr9-mobility); }
.cr9-mint__disc { position: relative; margin: 0 auto var(--space-2); }
.cr9-mint__disc .cr9-ring__svg { width: 100%; height: 100%; }
/* Dezelfde limoenen rand als bij een volle ring in de wereld, met hetzelfde
   ::after en om dezelfde reden. Limoen is van geen pijler. */
.cr9-mint--full .cr9-mint__disc::after {
  content: ''; position: absolute; inset: -3px;
  border: 1px solid var(--accent); border-radius: 50%;
  box-shadow: 0 0 10px 1px var(--accent-subtle);
  pointer-events: none;
}
.cr9-mint__name {
  font-size: 11px; font-weight: 800; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
}
.cr9-mint__count {
  font-size: 22px; font-weight: 800; color: var(--ink);
  font-variant-numeric: tabular-nums; margin-top: 2px;
}
/* Beide regels staan er VANAF HET BEGIN en worden alleen doorzichtig gehouden: een
   regel die pas op de beat wordt INGEVOEGD duwt de kaart omhoog terwijl je ernaar
   kijkt. De vertraging op de hint is de tik tussen "de teller schuift om" en "de
   regel komt" — daarom heeft coinCard() één timer en niet drie. */
.cr9-mint__hint {
  font-size: 14px; font-weight: 700; color: var(--ink);
  margin: var(--space-2) 0 0; min-height: 18px;
  opacity: 0; transition: opacity .3s ease .18s;
}
.cr9-mint__coins {
  font-size: 12px; font-weight: 700; color: var(--muted);
  font-variant-numeric: tabular-nums; margin: 4px 0 0;
  opacity: 0; transition: opacity .3s ease .3s;
}
.cr9-mint__hint.is-in, .cr9-mint__coins.is-in { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .cr9-mint__hint, .cr9-mint__coins { transition: none; }
}
/* Het vak waarin de munt ONDERAAN de Voeding-tab landt. Geen blad over het
   scherm: je bent aan het loggen, en iets dat je moet wegtikken maakt van een
   cadeau een handeling. Hij ruimt zichzelf op na een paar seconden. */
.cr9-mintbox {
  margin: var(--space-4) 0 var(--space-2);
  padding: var(--space-4) var(--space-3);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}

/* ── CRESCO90-JE-WEEK-DRAAGT-HET-MERK-IN-DAGBOEK-EN-VOEDING ──
   ÉÉN regel onder het weekoverzicht van het Dagboek en dat van Voeding, en
   alleen bij een deelnemer: de server stuurt `cresco90: null` voor iedereen die
   niet meedoet (Cresco90Score::badge) en dan wordt er geen enkel element
   gebouwd. Een merkteken bij een niet-deelnemer is reclame.

   ÉÉN teken per WEEK, nooit per dag. Een teken op elke dagcel is binnen een week
   onzichtbaar geworden — precies waarom GYM-DE-SCHEMAKAART-WORDT-RUSTIG eerder
   een herhaald label van de kaart haalde.

   Het woordmerk komt hier niet als <img> binnen maar als MASKER, dezelfde
   mechaniek als .cr9-pillar__art hierboven: cresco90-wordmark.png is vlak wit op
   transparant, dus als plaatje verdwijnt hij op het dagthema. Als masker draagt
   hij de inkt van het thema en heeft hij geen tweede thema-truc nodig. De oudere
   filter:invert(1) van .cr9-brand__mark kan hier NIET: die staat in de
   Cresco-wereld op een bekende donkere ondergrond, en dit staat midden in het
   Dagboek. Geen nieuw beeldbestand — dit is het enige Cresco-beeld dat al in de
   voorlaadlijst van de service worker staat.

   De KLEUR mag niet mee naar buiten, op één plek na: sluit de week op 90, dan
   staat het woordmerk in var(--accent) — één regel, één keer per week. */
.cr9line {
  display: flex; align-items: center; gap: 8px;
  width: 100%; box-sizing: border-box;
  margin: 0; padding: var(--space-2) 0 0;
  border: 0; border-top: 1px solid var(--line);
  background: none; font: inherit; text-align: left;
  color: var(--ink-2); cursor: pointer;
}
.cr9line__mark {
  flex: 0 0 auto; width: 48px; height: 14px;
  background-color: var(--ink);
  -webkit-mask-image: url('/assets/img/brand/cresco90-wordmark.png');
  mask-image: url('/assets/img/brand/cresco90-wordmark.png');
  -webkit-mask-position: left center; mask-position: left center;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: contain; mask-size: contain;
}
.cr9line--full .cr9line__mark { background-color: var(--accent); }
.cr9line__txt { flex: 1 1 auto; min-width: 0; font-size: var(--text-body-sm); line-height: 1.3; }
.cr9line__txt b { color: var(--ink); font-weight: 700; font-variant-numeric: tabular-nums; }
.cr9line__go { flex: 0 0 auto; color: var(--muted); }

/* ── DAGBOEK-JE-CRESCO90-WEEK-STAAT-IN-JE-DAGBOEK — de Cresco90-kaart ──
   De rij hierboven hing ONDER de weekgrafiek en moest het hele verhaal in één
   regel kwijt. Dat lukte niet: de score van vorige week, de munten van deze week
   en de vier pijlers zijn vier verschillende dingen. Dus wordt het een KAART, en
   de rij verdwijnt uit het Dagboek (de Voeding-tab gebruikt .cr9line nog wel).

   HUISSTIJL IS DAGBOEK, NIET CRESCO. Dezelfde maten als .fz-weekchart —
   hetzelfde vlak, dezelfde rand, dezelfde radius, dus de kaart leest als het
   volgende blok van hetzelfde Dagboek en niet als een reclameblok ertussen.
   Cresco mag op precies drie plekken spreken: het WOORDMERK bovenaan, de
   RINGKLEUREN (--cr9-strength/cardio/nutrition/mobility, via data-pillar op de
   <li>) en de MUNT. Verder geen Cresco-vlak, geen Cresco-rand, geen tweede font.

   De hele kaart is tikbaar naar het blad; de ringen en de kop zijn eigen
   knoppen die hun eigen tik afhandelen (de JS laat de kaart-tik los zodra het
   doelwit in een <button> zit). Vandaar cursor:pointer op het geheel.

   HOOGTEBUDGET: ≤ 200px bij 390px breed, en dat is krap met opzet — hieronder de
   grafiek hoort dit een SAMENVATTING te zijn die je in één blik leest, geen
   tweede scherm dat je moet scrollen. 22 (padding) + 28 (kop) + 50 (cijfers) +
   89 (ringen) ≈ 189. Elke maat die hier groeit, moet een andere laten krimpen. */
.cr9card {
  margin: var(--space-2) 0 0; padding: 12px 12px 10px;
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  background: var(--surface); cursor: pointer;
}
/* De kop: woordmerk links, pijl rechts. Het woordmerk is HETZELFDE masker als
   .cr9line__mark (één beeldbestand, al in de voorlaadlijst van de service
   worker), alleen groter — de kaart heeft de ruimte die de regel niet had. */
.cr9card__head {
  appearance: none; -webkit-appearance: none;
  display: flex; align-items: center; gap: 8px;
  width: 100%; margin: 0 0 8px; padding: 0;
  border: 0; background: none; font: inherit; color: inherit;
  text-align: left; cursor: pointer;
}
.cr9card__head .cr9line__mark { width: 72px; height: 16px; }
.cr9card__head:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cr9card__go { margin-left: auto; color: var(--muted); font-size: 18px; line-height: 1; }
/* De twee cijferkolommen. Links de uitslag die VAST staat (vorige week), rechts
   de week die nog loopt. Die scheiding is de hele reden dat er twee kolommen
   zijn: één getal onder het andere zou lezen als één verloop. */
.cr9card__nums { display: flex; align-items: flex-start; gap: var(--space-3); margin: 0 0 8px; }
.cr9card__col--right { margin-left: auto; text-align: right; }
.cr9card__lbl { display: block; font-size: 12px; line-height: 1.25; color: var(--muted); }
/* "23 / 90" — het WOORD "Score" staat in het label hierboven, want in de test
   lazen twee van de drie mensen dit getal zonder dat woord als "dag 23 van 90".
   De 90 blijft gedempt en klein: wat verandert is de 23. */
.cr9card__score {
  display: block; font-size: 15px; font-weight: 700; color: var(--muted);
  line-height: 1.05; white-space: nowrap; font-variant-numeric: tabular-nums;
}
.cr9card__score b { font-size: 24px; font-weight: 800; color: var(--ink); }
/* De week die nog niet gesloten is heeft geen uitslag. Dan staat er een ZIN en
   geen "0 / 90": nul beweert een slechte week over een week die niet bestaat. */
.cr9card__score--wait { font-size: 12px; font-weight: 400; line-height: 1.35; white-space: normal; }
.cr9card__coins {
  display: flex; align-items: center; justify-content: flex-end; gap: 6px;
  font-size: 15px; font-weight: 800; color: var(--ink);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* VIER op een rij, niet twee zoals in het blad: .cr9-ring-row blijft dus van het
   blad en de kaart heeft zijn eigen raster. Dan kan het blad nooit meebewegen
   met een maat die hier goed uitkomt. */
.cr9card__rings {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-2);
}
/* DE COMPACTE RING. Dezelfde pillarRing() uit lib-cresco-ring.js als het blad,
   één vlag anders: schijf van 52 in plaats van 76, de naam ONDER de telling, en
   geen doelkaartje eronder (dat hoort in het blad, waar je de week inricht).
   De telling staat hier in --ink en niet in --muted, want in de kaart is de
   telling het cijfer dat je komt lezen; --hit wordt daarna opnieuw gezet zodat
   een volle pijler ook hier oplicht. */
.cr9-ring--compact { gap: 3px; }
.cr9-ring--compact .cr9-ring__disc { width: 52px; height: 52px; }
.cr9-ring--compact .cr9-ring__svg { width: 52px; height: 52px; }
.cr9-ring--compact .cr9-ring__art { width: 22px; height: 22px; }
.cr9-ring--compact .cr9-ring__count { font-size: 13px; font-weight: 800; color: var(--ink); }
.cr9-ring--compact .cr9-ring__count--hit { color: var(--accent); }
.cr9-ring--compact .cr9-ring__name { font-size: 11px; font-weight: 600; color: var(--muted); }
@media (max-width: 380px) {
  .cr9-ring--compact .cr9-ring__disc { width: 46px; height: 46px; }
  .cr9-ring--compact .cr9-ring__svg { width: 46px; height: 46px; }
}
/* OP DE GLASPLAAT VAN HET DAGBOEK ZIJN --muted EN --ink NIET DE LEESBARE PAREN.
   Dezelfde vaststelling en dezelfde tokens als .fz-weekavg__lbl en
   .fz-weekchart__lbl een eind hierboven: deze tab is een FOTO met panelen erop,
   en gedempte inkt op een geblurde foto haalt 4,5:1 bij geen enkele tint.

   Gescoped op .cr9card en NIET op de ring-klassen zelf, want het Cresco-blad kan
   boven deze tab open staan — en dat blad staat op zijn eigen dekkende vlak,
   waar --muted en --ink juist de goede paren zijn. Eén klasse erbij in de
   selector is de prijs van een blad dat niet verandert zodra je het vanuit het
   Dagboek opent. */
body.tab-calendar .cr9card__lbl,
body.tab-calendar .cr9card__score,
body.tab-calendar .cr9card__go,
body.tab-calendar .cr9card .cr9-ring__name { color: var(--glass-ink-2); }
body.tab-calendar .cr9card__score b,
body.tab-calendar .cr9card__coins,
body.tab-calendar .cr9card .cr9-ring__count { color: var(--glass-ink); }
/* EN DE ACCENTKLEUR BLIJFT. De regel hierboven is specifieker dan
   `.cr9-ring--compact .cr9-ring__count--hit` en zou "vol" dus stil terugzetten
   naar gewone inkt — het enige verschil tussen 2/2 en 1/3 zou dan het cijfer
   zelf zijn. Gelijke specificiteit, latere regel: deze wint. */
body.tab-calendar .cr9card .cr9-ring__count--hit { color: var(--accent); }
/* Het woordmerk is een MASKER en neemt dus background-color, geen color; de lege
   ringstukken zijn een svg-streek en nemen stroke. Allebei dezelfde beweging als
   .fz-weekchart__axis hierboven: een haarlijn uit --line valt op glas weg. */
body.tab-calendar .cr9card .cr9line__mark { background-color: var(--glass-ink); }
body.tab-calendar .cr9card .cr9-ring__track { stroke: var(--glass-hair); }

/* DAGBOEK-EEN-CRESCO-RING-LEGT-ZIJN-DOEL-UIT — het blad onder een ring.
   Het is een gewoon .mo-sheet en draagt dus het DEKKENDE vlak (--bg) van elk
   ander blad in de app. Precies daarom staan de glasregels hierboven op
   `.cr9card`: dit blad hangt boven de foto maar niet ÓP de foto, en --muted en
   --ink zijn hier weer de leesbare paren. Eén klasse scheelt een hele reeks
   terugzettingen. */
.cr9sheet__head { gap: var(--space-3); align-items: flex-start; }
/* De ring is dezelfde compacte ring als in de kaart; hij staat hier in een <ul>
   omdat pillarRing een <li> teruggeeft — één tekenaar, dus de lijstschil is de
   prijs. Geen naam en geen telling eronder: die staan al in de bovenregel en in
   de standregel, en drie keer hetzelfde is geen kop meer. */
.cr9sheet__ring { list-style: none; margin: 0; padding: 0; flex: 0 0 auto; }
.cr9sheet__ring .cr9-ring__name,
.cr9sheet__ring .cr9-ring__count { display: none; }
.cr9sheet__titles { flex: 1 1 auto; min-width: 0; padding-top: 2px; }
.cr9sheet__eyebrow {
  font-size: 12px; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--muted); line-height: 1.3;
}
/* De KOP is de opdracht in vier woorden. Hij overschrijft .mo-title met opzet:
   een bladtitel is daar rustig en gespatieerd, hier is hij het antwoord op de
   vraag waarmee je dit blad opende. */
.cr9sheet__title {
  font-size: 20px; font-weight: 800; letter-spacing: 0; line-height: 1.25;
  margin: 2px 0 0; color: var(--ink);
}
.cr9sheet__body { padding: var(--space-4) var(--space-5) var(--space-5); }
.cr9sheet__rule { margin: 0; font-size: 15px; line-height: 1.45; color: var(--ink); }
/* HET GETAL dat je zelf hebt ingesteld, op zijn eigen regel en zwaarder dan de
   regel erboven: dat is wat je komt opzoeken. */
.cr9sheet__nums {
  margin: var(--space-2) 0 0; font-size: 15px; font-weight: 700; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.cr9sheet__note { margin: var(--space-2) 0 0; font-size: 13px; line-height: 1.4; color: var(--muted); }
/* WAAR JE NU STAAT, als eigen vlakje — niet als vierde tekstregel. Drie zinnen
   onder elkaar lezen als één alinea en dan verdwijnt juist de stand. */
.cr9sheet__stand {
  margin: var(--space-4) 0 0; padding: 12px 14px;
  background: var(--surface-2); border-radius: var(--radius-md);
  font-size: 14px; font-weight: 700; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.cr9sheet__do { margin: var(--space-4) 0 0; }
/* "Niveau aanpassen" is de STILLE deur. Een tweede volle knop zou de eerste
   evenveel gewicht geven, en dan is dit blad een keuzemenu in plaats van een
   uitleg met één voor de hand liggende volgende stap. */
.cr9sheet__change {
  appearance: none; -webkit-appearance: none;
  display: block; width: 100%; margin: var(--space-3) 0 0; padding: 10px 0;
  border: 0; background: none; font: inherit; cursor: pointer;
  font-size: 13px; color: var(--muted); text-decoration: underline;
}
.cr9sheet__change:hover { color: var(--ink); }
.cr9sheet__change:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* DE MUNT ALS KENTEKEN. Hetzelfde ronde vlak als de munt die in een ring vliegt,
   maar hier in de tekstregel: dus geen absolute plek en geen vluchtbaan. Goud en
   niet een pijlerkleur — deze munt is van GEEN pijler, hij telt ze alle vier. */
.cr9-coin--mark {
  position: static; top: auto; left: auto; margin: 0;
  width: 16px; height: 16px; flex: 0 0 auto;
  --cr9-coin: var(--cr9-gold);
  /* De vliegende munt staat op pointer-events:none omdat hij tijdens zijn 620 ms
     over een schijf heen scheert en daar geen tik mag opvangen. Dit kenteken
     beweegt niet en ligt in een tekstregel, dus die uitzondering hoort hier niet
     te gelden. Het verschil is NIET te zien en NIET te voelen — een tik komt zo
     of zo bij de kaart uit — maar wel te meten: een vlakje dat voor
     elementFromPoint onzichtbaar is, is voor de contrastpoort onvindbaar, en een
     merkteken dat geen poort kan graden is een merkteken dat in het donker stil
     op het kaartvlak kan vallen. */
  pointer-events: auto;
}

/* ── JOUW-KEUZES-VOOR-CRESCO90 — the choice blade ──
   A SECOND blade inside the SAME overlay, not a second overlay: the world and
   the choices are one screen you page between, so closing is one gesture and
   there is never a stack of scrims. Everything below only styles that blade;
   the reporting blade above is untouched. */
.cr9-door__note { margin: var(--space-2) 0 0; font-size: 0.8rem; color: var(--muted); text-align: center; }
/* CRESCO90-EEN-TIK-OP-DE-RIJ-OPENT-ZIJN-WEEK — DE TOESTEMMING BOVEN DE KNOP.
   Direct boven Meedoen en niet eronder: eronder is een voetnoot bij een besluit
   dat je al nam. Gedempt maar niet kleiner dan de regel onder de knop — dit is
   geen kleine letter, en hem kleiner zetten dan de rest zou hem er precies toe
   maken. GEEN var(--danger) en geen kader: er staat niets fout te gebeuren. */
.cr9-door__consent {
  margin: 0 0 var(--space-3); font-size: 0.8rem; line-height: 1.55;
  color: var(--muted); text-align: center;
}
.cr9-nav {
  display: inline-flex; align-items: center; gap: 6px;
  background: none; border: none; padding: 6px 8px 6px 0;
  color: var(--muted); font-size: 13px; font-weight: 600; cursor: pointer;
}
.cr9-nav svg { transform: rotate(180deg); }
.cr9-choices { display: flex; flex-direction: column; gap: var(--space-4); }
.cr9-choices__title { margin: 0; font-size: 1.15rem; font-weight: 800; color: var(--ink); }
.cr9-choices__lead { margin: var(--space-2) 0 0; font-size: 13px; line-height: 1.55; color: var(--muted); }
.cr9-pick { display: flex; flex-direction: column; gap: var(--space-2); }
.cr9-pick__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
.cr9-pick__title { margin: 0; font-size: 0.95rem; font-weight: 700; color: var(--ink); }
.cr9-pick__bar {
  flex-shrink: 0; font-size: 11px; font-weight: 700; color: var(--accent);
  padding: 2px 8px; border-radius: var(--radius-pill);
  background: color-mix(in oklab, var(--accent) 12%, transparent);
}
.cr9-pick__hint { margin: 0; font-size: 12px; line-height: 1.5; color: var(--muted); }
/* CRESCO90-ELKE-KNOP-DOET-WAT-HIJ-ZEGT — de regel die zegt dat je zojuist het
   ene mobiliteitsspoor door het andere hebt vervangen. Hij staat TUSSEN de twee
   bedieningen in, dus hij heeft lucht boven en onder nodig om niet als het
   bijschrift van de bovenste te lezen. Verder erft hij alles van .cr9-pick__hint:
   grijs, klein, rustig. Geen rood en geen kader — er ging niets mis. */
.cr9-mobswap { margin: var(--space-2) 0; }
/* CRESCO90-MEEDOEN-ZET-JE-IN-DE-CRESCO-STIJL — de ene regel die vertelt dat de
   app zojuist omkleurde. Hij opent het blad, dus hij heeft lucht onder zich
   nodig om niet als bijschrift van het cijfer eronder te lezen. Verder erft hij
   alles van .cr9-pick__hint: grijs, klein, rustig. Geen kader en geen kleurvlak
   — het bewijs staat al om hem heen, dat is de hele mededeling. */
.cr9-theme-note { margin: 0 0 var(--space-3); }
.cr9-pick__sub {
  margin: var(--space-2) 0 0; font-size: 11px; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase; color: var(--muted);
}
.cr9-opts { display: flex; flex-direction: column; gap: 6px; }
/* CRESCO90-JE-KIEST-JE-LIJN-EN-JE-WEET-WAAROM — de bibliotheek staat bovenaan op
   het wisselblad, je eigen schema's zitten achter deze secundaire knop. Zelfde
   rustige stijl als .cr9-sum__other: het is een tweede weg, geen tweede actie.
   Volle breedte, want een klein tekstlinkje op 390px is geen doel om te raken. */
.cr9-more {
  display: block; width: 100%; margin: var(--space-3) 0 0;
  padding: 8px var(--space-3); text-align: left;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--muted);
  font-size: 12px; font-weight: 700; cursor: pointer;
}
.cr9-own__panel[hidden] { display: none; }

/* CRESCO90-JE-KIEST-JE-GEWRICHTEN-OP-HET-SKELET — the wrapper around the SHARED
   figure. The figure itself styles through joint-figure.css (.bp-stage and
   friends); only the room it stands in belongs to this screen, exactly as
   .bp-stagewrap does on the coach surface. The side marks ("left" / "right")
   sit OUTSIDE the stage, so the horizontal padding is what keeps them on
   screen at 390px. */
.cr9-pick__q { margin: var(--space-2) 0 0; font-size: 0.95rem; font-weight: 700; color: var(--ink); }
.cr9-figure {
  display: flex; justify-content: center;
  padding: var(--space-2) 44px; margin-top: var(--space-2);
}
/* De opsomming is een echte tekstregel, geen kleur op de tekening: dit is wat
   een schermlezer voorleest en wat leesbaar blijft zonder kleurwaarneming. */
.cr9-picked {
  margin: 0; font-size: 13px; font-weight: 600; line-height: 1.5;
  color: var(--ink); text-align: center;
}
.cr9-picked.is-empty { font-weight: 400; color: var(--muted); }
.cr9-opt {
  display: flex; align-items: center; gap: var(--space-3); width: 100%; text-align: left;
  padding: 10px var(--space-3); border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink); cursor: pointer;
}
.cr9-opt.is-on { border-color: var(--accent); background: color-mix(in oklab, var(--accent) 8%, var(--surface)); }
.cr9-opt__main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.cr9-opt__name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cr9-opt__sub { font-size: 12px; color: var(--muted); line-height: 1.4; }
.cr9-opt__mark {
  flex-shrink: 0; width: 22px; height: 22px; border-radius: 50%;
  border: 1.5px solid var(--line); display: inline-flex; align-items: center; justify-content: center;
  color: transparent;
}
.cr9-opt.is-on .cr9-opt__mark { border-color: var(--accent); background: var(--accent); color: var(--on-accent); }
/* EEN-SCHEMA-LAAT-ZIJN-OEFENINGEN-ZIEN — pick target and open target side by side. */
.cr9-optwrap { display: flex; flex-direction: column; }
.cr9-optrow { display: flex; align-items: stretch; gap: 6px; }
.cr9-optrow .cr9-opt { flex: 1; min-width: 0; }
/* The opener costs the row 46px, and a one-line schema name would lose the end
   of itself to an ellipsis on exactly the library rows whose name says what
   they are about. Wrapping instead of cutting. */
.cr9-optrow .cr9-opt__name { white-space: normal; overflow: visible; }
.cr9-optmore {
  flex-shrink: 0; width: 40px; display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--muted); cursor: pointer;
}
.cr9-optmore svg { transition: transform .15s ease; }
.cr9-optwrap.is-open .cr9-optmore { color: var(--ink); }
.cr9-optwrap.is-open .cr9-optmore svg { transform: rotate(180deg); }
.cr9-optex { padding: 8px var(--space-3) 2px; }
.cr9-optex__count { margin: 0 0 4px; font-size: 12px; font-weight: 600; color: var(--muted); }
.cr9-optex__list { margin: 0; padding-left: 18px; }
.cr9-optex__item { font-size: 13px; line-height: 1.6; color: var(--ink); }
/* JE-KIEST-HOEVEEL-VAN-JE-SCHEMA-JE-DOET — the difficulty pill that used to sit
   beside a library schema (.cr9-lvl) is gone. It said easy/medium/hard about
   how demanding the SCHEMA is, while the same three words on the same screen now
   say how much of it you do; two meanings of one word is what made this page
   unreadable. programs.library_difficulty still orders the library server-side.

   The level buttons carry a whole sentence ("Easy — 1 of 7 exercises"), so they
   stack one per row instead of sharing a row like the four short nutrition
   labels do. */
.cr9-level { margin-top: var(--space-3); }
.cr9-level__q {
  margin: var(--space-2) 0 0; font-size: 11px; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase; color: var(--muted);
}
.cr9-seg--stack .cr9-seg__btn { flex: 1 1 100%; text-align: left; padding-left: var(--space-3); }
/* Wraps rather than squeezes: since CRESCO90-VOEDING-BEGINT-BIJ-WATER there is
   a fourth, longer label ("Waterdoel"), and four equal columns on a 390px phone
   would clip it. The basis lets them sit on one row where there is room and
   fall to two rows of two where there is not. */
.cr9-seg { display: flex; flex-wrap: wrap; gap: 6px; }
.cr9-seg__btn {
  flex: 1 1 84px; min-width: 0;
  padding: 10px 6px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink); font-size: 13px; font-weight: 600; cursor: pointer;
}
.cr9-seg__btn.is-on { border-color: var(--accent); background: var(--accent); color: var(--on-accent); }
/* CRESCO90-JE-KIEST-JE-LIJN-EN-JE-WEET-WAAROM — een lijnknop draagt de naam plus
   één zin die zegt wat die vorm is. De zin heeft GEEN eigen kleurwaarde: hij
   erft de kleur van de knop en zakt met opacity terug, want een vast grijs zou
   op de aan-stand (accent-vlak) onleesbaar worden. */
.cr9-seg__btn--two { padding: 10px var(--space-3); text-align: left; line-height: 1.35; }
.cr9-seg__name { display: block; font-size: 13px; font-weight: 700; }
.cr9-seg__sub  { display: block; margin-top: 2px; font-size: 12px; font-weight: 400; opacity: .72; }
/* De regel onder een uitgezette Meedoen-knop. Zelfde grijze hint-stijl als elke
   andere hint op dit blad: nog niet gekozen hebben is geen fout. */
.cr9-save__need { margin: var(--space-2) 0 0; text-align: center; }
.cr9-save__btn[disabled] { opacity: .5; cursor: default; }
/* Cardio is the one pillar with no block. One muted line so nobody wonders
   where it went — an explanation is not a choice. */
.cr9-cardio {
  display: flex; gap: var(--space-3); align-items: flex-start;
  padding: var(--space-3); border: 1px dashed var(--line); border-radius: var(--radius-sm);
}
.cr9-cardio__icon { flex-shrink: 0; color: var(--muted); display: inline-flex; }
.cr9-cardio__txt { margin: 0; font-size: 12px; line-height: 1.5; color: var(--muted); }
/* `bottom: 0` pins this strip to the SCROLL CONTAINER'S PADDING BOX, not to the
   glass. .cr9-body carries var(--space-4) of bottom padding, so the button was
   floating 16px above the visible floor and the next line of copy kept scrolling
   through the transparent band underneath it — the "text under the button" on
   the 390px phone. A NEGATIVE sticky offset drops the opaque strip onto that
   band; extra padding alone cannot, it would only lift the button. The two
   var(--space-4)s must stay equal: the offset drops the strip onto the floor,
   the padding puts the space back INSIDE the opaque strip. */
.cr9-save {
  position: sticky; bottom: calc(-1 * var(--space-4)); background: var(--bg);
  padding-top: var(--space-3);
  padding-bottom: calc(var(--space-4) + env(safe-area-inset-bottom, 0px));
}
.cr9-save__btn { width: 100%; padding: 13px 16px; font-size: 14px; }
.cr9-save__err { margin: var(--space-2) 0 0; font-size: 12px; color: var(--danger); text-align: center; }

/* ── CRESCO90-EEN-CRESCO-DAG-IS-ALLE-OEFENINGEN-VAN-JE-NIVEAU ──
   Drie kleine dingen, alle drie in de bestaande hint-stijl van dit blad.

   GEEN --danger EN GEEN RODE RAND. Lichter gaan spelen is een geldige zet: je
   strepen blijven, je trede blijft, je kunt morgen terug. Het kost je reeks, en
   dat staat er met zoveel woorden. Rood zou er een fout van maken, en dat doet
   dit blad nergens. */
.cr9-optex__pick { margin-top: 6px; }
/* De ene regel voor wie een coach heeft, onder de oefeningen. Lucht erboven,
   anders leest hij als het laatste item van de lijst erboven. */
.cr9-sum__coach { margin-top: var(--space-2); }
/* Het waarschuwingsblok komt IN DE PLAATS van de opslaanknop te staan, in
   hetzelfde sticky vak — dus geen eigen achtergrond: die staat er al. */
.cr9-lighter { display: flex; flex-direction: column; gap: var(--space-2); }
/* Het UITGEREKENDE niveau in vrij spel. Hetzelfde pilvormpje als .cr9-pick__bar
   (accent op 12% van zichzelf), en de KAPITALEN komen van CSS: het woord in de
   JavaScript is het gewone 'Makkelijk'/'Medium'/'Zwaar' uit het woordenboek, en
   een tweede sleutel voor MAKKELIJK zou datzelfde woord een tweede keer laten
   vertalen. */
.cr9-lighter__pill {
  align-self: flex-start;
  font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--accent); padding: 2px 8px; border-radius: var(--radius-pill);
  background: color-mix(in oklab, var(--accent) 12%, transparent);
}
.cr9-lighter__say { margin: 0; font-size: 13px; line-height: 1.5; color: var(--ink); }
/* Twee knoppen naast elkaar, even breed: geen van de twee is de gevaarlijke.
   Onder 300px staan ze onder elkaar, want twee vakken van 130px met een woord
   van elf letters breken anders middenin dat woord. */
.cr9-lighter__btns { display: flex; gap: var(--space-2); }
.cr9-lighter__btns .btn { flex: 1 1 0; min-width: 0; padding: 13px 12px; font-size: 14px; }
@media (max-width: 300px) { .cr9-lighter__btns { flex-direction: column; } }

/* ── CRESCO90-MEEDOEN-BEGINT-MET-EEN-KEUZE-UIT-DRIE — de drie kaarten ──
   Het keuzeblok hierboven is 1819 pixels lang op een telefoon van 390 breed. Dit
   blad is twee tikken: een kaart, en dan je week. Alles hieronder gebruikt de
   bestaande tokens en de bestaande knopvorm — een aangetikte kaart kleurt met
   --accent en var(--on-accent), precies zoals .cr9-seg__btn.is-on. NOOIT rood:
   --danger blijft waar het staat, in de foutregel bij het opslaan. */
.cr9-lvls { display: flex; flex-direction: column; gap: var(--space-3); margin-top: var(--space-3); }
/* DE EERLIJKE WAARSCHUWING — één regel onder de drie pakketten, op de plek waar
   je de lat kiest. Ze stond als alinea op de voordeur, waar niemand hem kon
   wegen; hier staat ze naast de keuze waar ze over gaat. Gedempt en NOOIT rood:
   het is informatie, geen afwijzing. */
.cr9-lvls__frank {
  margin: var(--space-3) 0 0; font-size: 12px; line-height: 1.5; color: var(--muted);
}
.cr9-lvl {
  display: flex; flex-direction: column; gap: 6px; width: 100%; text-align: left;
  padding: var(--space-3); border: 1px solid var(--line); border-radius: var(--radius-lg);
  background: var(--surface); color: var(--ink); cursor: pointer;
}
.cr9-lvl.is-on { border-color: var(--accent); background: var(--accent); color: var(--on-accent); }
.cr9-lvl__name { font-size: 1.1rem; font-weight: 800; }
.cr9-lvl__lead { font-size: 13px; line-height: 1.5; color: var(--muted); }
.cr9-lvl.is-on .cr9-lvl__lead,
.cr9-lvl.is-on .cr9-lvl__bar { color: var(--on-accent); opacity: .85; }
.cr9-lvl__lines { list-style: none; margin: var(--space-2) 0 0; padding: 0; }
.cr9-lvl__line {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-3); padding: 5px 0; font-size: 13px;
}
.cr9-lvl__line + .cr9-lvl__line { border-top: 1px solid var(--line); }
.cr9-lvl.is-on .cr9-lvl__line + .cr9-lvl__line {
  border-top-color: color-mix(in oklab, var(--on-accent) 30%, transparent);
}
.cr9-lvl__pillar { font-weight: 600; }
.cr9-lvl__bar { flex-shrink: 0; font-weight: 700; color: var(--muted); font-variant-numeric: tabular-nums; }

/* ── CRESCO90-DE-LADDER-HEEFT-DRIE-STREPEN — een niveau dat nog niet open staat ──
   Gedimd tot 45%, een hangslot en één regel. NOOIT --danger en nooit een kruis:
   wie hier nog niet mag kiezen heeft niets fout gedaan, hij heeft alleen nog iets
   te verdienen, en een rode knop leest als een afwijzing.

   ÉÉN VORM VOOR TWEE PLEKKEN. Dezelfde dimming op de trede-knop (.cr9-seg__btn,
   voeding én kracht) en op de pakketkaart (.cr9-lvl), want het is één regel en
   twee vormen zou de speler laten denken dat het twee verschillende dingen zijn.

   GEEN EIGEN KLEUR, dus geen tweede rekening voor Cresco Nacht en Cresco Dag:
   opacity dempt wat er al staat, en de regel erft de kleur van de knop. Een vast
   grijs hier zou op het donkere blad onleesbaar worden. */
.cr9-seg__btn.is-locked,
.cr9-lvl.is-locked { opacity: .45; cursor: default; }
.cr9-lock {
  display: flex; align-items: center; gap: 4px; margin-top: 4px;
  font-size: 11px; font-weight: 600; line-height: 1.35; text-align: left;
}
.cr9-lock svg { flex-shrink: 0; }
/* Mobiliteit heeft geen trede-knoppen — het niveau volgt uit het aantal
   gewrichten — dus daar staat dezelfde zin als losse regel onder het poppetje. */
.cr9-lockline { margin: var(--space-2) 0 0; }

/* Het samenvattingsblad. Vier rijen die hetzelfde koppatroon dragen als de
   pijlers in het keuzescherm, zodat de lat overal op dezelfde plek staat. */
.cr9-sum__level { margin: var(--space-2) 0 0; font-size: 1.05rem; font-weight: 800; color: var(--accent); }
.cr9-sum__row { padding-top: var(--space-3); border-top: 1px solid var(--line); }
.cr9-sum__name { margin: 0; font-size: 13px; line-height: 1.55; color: var(--ink); }
/* Staat er bij mobiliteit nog niets, dan staat op de plek van de naam een knop
   naar dezelfde gewricht-kiezer. Even breed als de rij, zodat hij als ingang
   leest en niet als een tweede Wissel-pil. */
.cr9-sum__pick { margin: var(--space-2) 0 0; width: 100%; }
.cr9-sum__body { display: flex; flex-direction: column; gap: var(--space-2); }
.cr9-sum__ex:empty { display: none; }
.cr9-swap {
  flex-shrink: 0; padding: 4px 10px; border: 1px solid var(--line); border-radius: var(--radius-pill);
  background: var(--surface); color: var(--muted); font-size: 11px; font-weight: 700; cursor: pointer;
}
.cr9-sum__other {
  display: block; width: 100%; margin: var(--space-2) 0 0;
  background: none; border: none; padding: 6px 0;
  color: var(--muted); font-size: 12px; font-weight: 600; cursor: pointer;
}

/* ── JOUW-KAART-EN-JE-BIJNAAM-OP-HET-BORD: the card, the name, the board ──
   Never red and never a lock: a low band gets exactly the same calm surface as
   a high one, and the board is fully sharp for everyone from day one.

   CRESCO90-JE-KAART-DRAAGT-JE-STREPEN-EN-JE-KROON turned this from a row into a
   PANEL. The card used to be artwork-beside-text; now it is a landscape plate
   with the shield in the middle and four corners: your marks (chevrons, crown,
   level) top left, your card's name top right, who you are and how long you
   have been at it bottom left, your record bottom right.

   The corners are absolute, and the plate layout is SCOPED — :not(--sm) so the
   board row never has to un-say anything, and :not(--demo) because the demo card
   in "Hoe het werkt" is the bare shield with every frame stripped off and would
   otherwise grow 168px of empty plate under the explanation. Both exclusions sit
   in the selector rather than being overridden back further down, so the small
   card and the demo card stay plain inline rows that happen to share every colour
   and font with the big plate — which is the whole point of there being ONE
   drawer for all three. */
.cr9-card {
  position: relative;
  display: flex; align-items: center; gap: var(--space-3);
  margin-top: var(--space-4); padding: var(--space-3);
  border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface);
}
.cr9-card:not(.cr9-card--sm):not(.cr9-card--demo) {
  min-height: 168px;
  flex-direction: column; align-items: stretch; justify-content: flex-end;
  overflow: hidden;
}
.cr9-card--earned { border-color: var(--accent); }
.cr9-card__art { width: 84px; height: auto; flex-shrink: 0; }
.cr9-card__art--sm { width: 30px; }
/* The shield sits BEHIND everything, dead centre, and takes no space in the flow
   — that is what lets the four corners be corners. It is absolutely positioned,
   so it paints in the positioned layer; every reading on top of it is positioned
   too and comes LATER in the DOM, which is what keeps the words above the shape
   without a single z-index having to be invented. */
.cr9-card:not(.cr9-card--sm):not(.cr9-card--demo) > .cr9-card__art {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  opacity: .6;
  pointer-events: none;
}
/* CRESCO90-DE-KAART-IS-NOOIT-LEEG — a band without artwork still has a shape.
   The shield this world already wears, as a MASK filled with --line: the theme
   colours it, there is no second file per theme, and it claims nothing — no
   colour, no badge, no word. Two-class specificity on the small one so it wins
   from .cr9-card__art--sm without either rule knowing about the other. */
.cr9-card__art--silhouette {
  width: 84px; height: 100px; display: block;
  background: var(--line);
  -webkit-mask: url('/assets/img/cresco90/shield.webp') center / contain no-repeat;
          mask: url('/assets/img/cresco90/shield.webp') center / contain no-repeat;
}
.cr9-card__art--silhouette.cr9-card__art--sm { width: 30px; height: 36px; }
/* No mask support: a quiet outline rather than an opaque plate, which is what
   painting the file itself would give — and still never an empty card. */
@supports not ((-webkit-mask: url('x')) or (mask: url('x'))) {
  .cr9-card__art--silhouette {
    background: none;
    border: 1px dashed var(--line); border-radius: var(--radius-sm);
  }
}
.cr9-card__body { min-width: 0; position: relative; }

/* Top left: your marks. Chevrons first, then the crown, then the level pill —
   the crown sits BEFORE the pill because it belongs to the stripe, not to the
   level, and reading it left of the label keeps that order visible. */
.cr9-card__marks {
  display: flex; align-items: center; gap: 8px;
  min-width: 0;
}
.cr9-card:not(.cr9-card--sm):not(.cr9-card--demo) > .cr9-card__marks {
  position: absolute; left: var(--space-3); top: var(--space-3);
}
/* Chevrons stack UPWARD: three of them read as a rank insignia, not as a list.
   Negative margin so they overlap the way chevrons on a sleeve do. */
.cr9-card__stripes { display: flex; flex-direction: column-reverse; align-items: center; }
.cr9-card__stripe {
  display: block; line-height: 0; color: var(--accent);
}
.cr9-card__stripe + .cr9-card__stripe { margin-bottom: -5px; }
.cr9-card--sm .cr9-card__stripe + .cr9-card__stripe { margin-bottom: -4px; }
/* The third one is gold, because the third stripe is the one that ends the
   climb. It is the only place in this world that leaves the accent family, and
   it is a reward, never a warning. */
.cr9-card__stripe--gold { color: #FBBF24; }
/* Tier 0 has no stripes at all, so the space says what it is instead of being
   empty: you are in, you have just not been rated yet. */
.cr9-card__new {
  font-size: 10px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--muted);
}
.cr9-card__crown { display: inline-flex; line-height: 0; color: #FBBF24; }
/* The level pill shows the level you are MEASURED at right now, so it can move
   down as well as up — which is why it is never dressed as an achievement. */
.cr9-card__level {
  display: inline-flex; align-items: center;
  padding: 2px 8px; border-radius: 999px;
  border: 1px solid var(--line);
  font-size: 10px; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted); white-space: nowrap;
}
.cr9-card__level--medium { border-color: var(--accent); color: var(--accent); }
/* Filled accent, so the text has to come from --on-accent: the mock was drawn
   in the theme where that token IS #0B0F14, and every other theme gets its own
   readable counterpart for free instead of one hard-coded near-black. */
.cr9-card__level--hard {
  border-color: var(--accent); background: var(--accent); color: var(--on-accent);
}

/* Top right: the name of your card. Uppercase and letterspaced because it is a
   title, not a sentence. */
.cr9-card__name {
  margin: 0;
  font-size: 11px; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--muted);
}
.cr9-card:not(.cr9-card--sm):not(.cr9-card--demo) > .cr9-card__name {
  position: absolute; right: var(--space-3); top: var(--space-3);
  max-width: 45%; text-align: right;
}

/* Bottom left: who you are, and how long you have kept it up. */
.cr9-card__who { margin: 0; font-size: 1.35rem; font-weight: 800; line-height: 1.15; color: var(--ink); }
.cr9-card__streak {
  margin: 4px 0 0; display: flex; align-items: center; gap: 5px;
  font-size: 11px; line-height: 1.5; color: var(--muted);
}
.cr9-card__streak svg { flex-shrink: 0; }

/* Bottom right: your record, and only while it is ahead of where you are now.
   The moment you match it again it disappears, because then it is not a
   separate thing to look at — it is simply you. */
.cr9-card__best {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 12px; font-weight: 800; color: var(--muted);
}
.cr9-card:not(.cr9-card--sm):not(.cr9-card--demo) > .cr9-card__best {
  position: absolute; right: var(--space-3); bottom: var(--space-3);
}
/* Streak 0: there is no name and nothing to dress. The shield goes quiet rather
   than grey-and-crossed-out, because a fresh card is a beginning. Two classes so
   it outranks the .6 the plate gives every other shield. */
.cr9-card.cr9-card--blank > .cr9-card__art { opacity: .25; }

/* ── DE-KAARTLADDER-KRIJGT-TWAALF-TREDEN: twelve looks for one card ──
   The BAND above names your card and comes from this window's score. These
   twelve rungs are the OTHER reading: how many perfect weeks you have strung
   together. Nothing here says a word — it is purely how the card is dressed,
   which is what "status" means on the board.

   Each rung sets three custom properties and nothing else; the card and the
   small card on the ranking read those. A thirteenth rung is one more block.

   NEVER RED. The original app ran into orange/red at the top rungs; here the
   whole ladder walks up ONE family — the accent — so it reads the same in Dag
   and in Nacht and never signals a warning. color-mix against --surface keeps
   the low rungs barely-there in both themes instead of hard-coding a grey. */
.cr9-streak {
  --cr9-edge: var(--line);
  --cr9-glow: none;
  --cr9-tint: transparent;
}
.cr9-streak--lvl-1  { --cr9-edge: color-mix(in srgb, var(--accent) 16%, var(--line)); }
.cr9-streak--lvl-2  { --cr9-edge: color-mix(in srgb, var(--accent) 30%, var(--line)); }
.cr9-streak--lvl-3  {
  --cr9-edge: color-mix(in srgb, var(--accent) 44%, var(--line));
  --cr9-glow: 0 0 0 1px color-mix(in srgb, var(--accent) 12%, transparent);
}
.cr9-streak--lvl-4  {
  --cr9-edge: color-mix(in srgb, var(--accent) 58%, var(--line));
  --cr9-glow: 0 0 0 1px color-mix(in srgb, var(--accent) 18%, transparent);
  --cr9-tint: color-mix(in srgb, var(--accent) 4%, transparent);
}
.cr9-streak--lvl-5  {
  --cr9-edge: color-mix(in srgb, var(--accent) 72%, var(--line));
  --cr9-glow: 0 0 10px -4px color-mix(in srgb, var(--accent) 45%, transparent);
  --cr9-tint: color-mix(in srgb, var(--accent) 5%, transparent);
}
.cr9-streak--lvl-6  {
  --cr9-edge: color-mix(in srgb, var(--accent) 86%, var(--line));
  --cr9-glow: 0 0 14px -4px color-mix(in srgb, var(--accent) 55%, transparent);
  --cr9-tint: color-mix(in srgb, var(--accent) 6%, transparent);
}
.cr9-streak--lvl-7  {
  --cr9-edge: var(--accent);
  --cr9-glow: 0 0 16px -4px color-mix(in srgb, var(--accent) 60%, transparent);
  --cr9-tint: color-mix(in srgb, var(--accent) 7%, transparent);
}
.cr9-streak--lvl-8  {
  --cr9-edge: var(--accent);
  --cr9-glow: 0 0 18px -3px color-mix(in srgb, var(--accent) 65%, transparent),
              inset 0 0 12px -6px color-mix(in srgb, var(--accent) 55%, transparent);
  --cr9-tint: color-mix(in srgb, var(--accent) 9%, transparent);
}
.cr9-streak--lvl-9  {
  --cr9-edge: var(--accent);
  --cr9-glow: 0 0 20px -3px color-mix(in srgb, var(--accent) 70%, transparent),
              inset 0 0 16px -6px color-mix(in srgb, var(--accent) 60%, transparent);
  --cr9-tint: color-mix(in srgb, var(--accent) 11%, transparent);
}
.cr9-streak--lvl-10 {
  --cr9-edge: var(--accent);
  --cr9-glow: 0 0 24px -3px color-mix(in srgb, var(--accent) 78%, transparent),
              inset 0 0 18px -6px color-mix(in srgb, var(--accent) 65%, transparent);
  --cr9-tint: color-mix(in srgb, var(--accent) 13%, transparent);
}
.cr9-streak--streak-90 {
  --cr9-edge: var(--accent);
  --cr9-glow: 0 0 26px -2px color-mix(in srgb, var(--accent) 85%, transparent),
              inset 0 0 20px -6px color-mix(in srgb, var(--accent) 70%, transparent);
  --cr9-tint: color-mix(in srgb, var(--accent) 15%, transparent);
}
.cr9-streak--streak-180 {
  --cr9-edge: var(--accent);
  --cr9-glow: 0 0 30px -2px color-mix(in srgb, var(--accent) 92%, transparent),
              inset 0 0 24px -6px color-mix(in srgb, var(--accent) 78%, transparent);
  --cr9-tint: color-mix(in srgb, var(--accent) 18%, transparent);
}

/* The card wears the rung. Two-class specificity, so it outranks
   .cr9-card--earned without either of them having to know about the other. */
.cr9-card.cr9-streak {
  position: relative;
  border-color: var(--cr9-edge);
  box-shadow: var(--cr9-glow);
  background-image: linear-gradient(var(--cr9-tint), var(--cr9-tint));
}
/* CRESCO90-JE-KAART-DRAAGT-JE-STREPEN-EN-JE-KROON — the third stripe gilds the
   whole card, and it does that ALWAYS: over Legend, over Black Card, and also
   while the level pill reads MAKKELIJK, because the crown hangs on the stripe
   you earned and not on what you happen to be training at this week.
   It has to live HERE, after .cr9-card.cr9-streak, and not up in the card block:
   same two-class weight, so the only thing that can decide it is source order.
   Move it back up there and every rung from lvl-1 onward would paint over the
   gold — which is exactly the bug this phase exists to prevent. */
.cr9-card.cr9-card--crown { border: 2px solid #FBBF24; }
/* From the sixth rung the edge breathes, slowly. It is a ring of its OWN on a
   pseudo element, so the animation touches opacity only and never repaints the
   card. Everything below the sixth rung stays perfectly still. */
.cr9-card.cr9-streak--lvl-6::after,
.cr9-card.cr9-streak--lvl-7::after,
.cr9-card.cr9-streak--lvl-8::after,
.cr9-card.cr9-streak--lvl-9::after,
.cr9-card.cr9-streak--lvl-10::after,
.cr9-card.cr9-streak--streak-90::after,
.cr9-card.cr9-streak--streak-180::after {
  content: ''; position: absolute; inset: -2px; pointer-events: none;
  border-radius: calc(var(--radius-lg) + 2px);
  border: 1px solid var(--cr9-edge);
  opacity: 0;
  animation: cr9-streak-breathe 4.5s ease-in-out infinite;
}
.cr9-card.cr9-streak--streak-180::after { animation-duration: 3s; }
@keyframes cr9-streak-breathe {
  0%, 100% { opacity: 0; }
  50%      { opacity: 0.55; }
}
/* A crowned card that also breathes breathes in GOLD, so the two decorations
   never disagree about what colour this card is. */
.cr9-card.cr9-card--crown::after { border-color: #FBBF24; }
/* A rung must never mean permanent movement on screen for someone who asked for
   none. The ring stays — at a fixed, readable strength — it just stops moving. */
@media (prefers-reduced-motion: reduce) {
  .cr9-card.cr9-streak::after { animation: none !important; opacity: 0.35; }
}

/* The same rung on the ranking, so you can see who has been at this a long
   time. It changes nothing about the order — status is not a score. */
.cr9-lb__row.cr9-streak { background-image: linear-gradient(var(--cr9-tint), var(--cr9-tint)); }
.cr9-lb__row.cr9-streak .cr9-card__art--sm {
  border-radius: 6px;
  box-shadow: 0 0 0 1.5px var(--cr9-edge);
}

/* ── CRESCO90-JE-KAART-DRAAGT-JE-STREPEN-EN-JE-KROON — what sits UNDER the card ──
   One honest line about who can see this, and then the one thing you are working
   on. Nothing here is a control: this whole stack is a reading. */
.cr9-cardwrap__who {
  margin: var(--space-2) 0 0;
  font-size: 11px; line-height: 1.5; color: var(--muted);
}
/* VOLGENDE STREEP. The dots are the same two dots as the week row at the top of
   the blade, deliberately: one perfect week is one dot everywhere in this world.
   The door ("je staat in de Arena") is the point, and the stripe number is not —
   which is why the number never appears here. */
.cr9-next { margin-top: var(--space-4); }
.cr9-next__title {
  margin: 0;
  font-size: 10px; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--muted);
}
.cr9-next__dots { display: flex; gap: 6px; margin-top: var(--space-2); }
.cr9-next__txt { margin: var(--space-2) 0 0; font-size: 12px; line-height: 1.5; color: var(--ink); }

/* CRESCO90-DE-KAART-IS-NOOIT-LEEG — the ladder under your card, ONE rung ahead.
   Two thumbnails, no scroller: there is nothing to scroll to any more, because
   everything past the next rung lives on the blade behind "Alle treden". Each
   thumbnail is the card SHAPE wearing that rung's edge, glow and tint — the
   same three custom properties the real card reads, so a thirteenth rung needs
   no rule here either. */
.cr9-ladderwrap { margin-top: var(--space-3); }
.cr9-ladder {
  list-style: none; margin: 0; padding: 0;
  display: flex; align-items: center; gap: var(--space-3);
}
.cr9-ladder__step {
  flex: 0 0 auto; width: 64px;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
}
/* The rung you have not reached is dimmed, never greyed out and never crossed
   through: it is where you are going, not something you failed to get. */
.cr9-ladder__step--next { opacity: .55; }
/* CRESCO90-JE-KAART-DRAAGT-JE-STREPEN-EN-JE-KROON — the next rung now carries a
   whole SENTENCE ("Nog 3 perfecte weken en je kaart wordt Color Shift."), so it
   stops being a 64px column and becomes a thumbnail with the promise beside it.
   The "Now" step keeps its column: it needs one word. */
.cr9-ladder__step--next {
  flex: 1 1 auto; width: auto;
  flex-direction: row; align-items: center; gap: var(--space-2);
  min-width: 0;
}
.cr9-ladder__step--next .cr9-ladder__name { text-align: left; font-size: 11px; }
.cr9-ladder__art {
  display: block; position: relative;
  width: 40px; height: 48px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface);
}
.cr9-ladder__art.cr9-streak {
  border-color: var(--cr9-edge);
  box-shadow: var(--cr9-glow);
  background-image: linear-gradient(var(--cr9-tint), var(--cr9-tint));
}
/* The shield inside the thumbnail, at the same weight as the silhouette on the
   card itself: one mark, two sizes. Decoration only — it carries no state. */
.cr9-ladder__art::after {
  content: ''; position: absolute; inset: 7px;
  background: var(--line);
  -webkit-mask: url('/assets/img/cresco90/shield.webp') center / contain no-repeat;
          mask: url('/assets/img/cresco90/shield.webp') center / contain no-repeat;
}
.cr9-ladder__name {
  font-size: 10px; line-height: 1.3; text-align: center; color: var(--muted);
}
.cr9-ladder__step--here .cr9-ladder__name { color: var(--accent); font-weight: 700; }
/* The way to the rest of the ladder. A quiet line, not a button that looks like
   the door: this blade has exactly one accent button and it is not this one. */
.cr9-ladder__all {
  display: block; margin: var(--space-2) 0 0;
  background: none; border: none; padding: 6px 0;
  color: var(--muted); font-size: 12px; font-weight: 600; cursor: pointer;
}

/* HET LADDERBLAD — all twelve rungs, one blade deep. A column, so the twelve
   read as a list of weeks rather than as a wall of thumbnails. */
.cr9-rungs { margin-top: var(--space-3); }
.cr9-rungs__title { margin: 0; font-size: 1.05rem; font-weight: 800; color: var(--ink); }
.cr9-rungs__list {
  list-style: none; margin: var(--space-3) 0 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--space-2);
}
.cr9-rungs__row { display: flex; align-items: center; gap: var(--space-3); opacity: .6; }
.cr9-rungs__row--here { opacity: 1; }
.cr9-rungs__row .cr9-ladder__art { width: 28px; height: 34px; }
.cr9-rungs__row .cr9-ladder__art::after { inset: 5px; }
.cr9-rungs__weeks { font-size: 12px; color: var(--muted); }
.cr9-rungs__row--here .cr9-rungs__weeks { color: var(--accent); font-weight: 700; }
.cr9-rungs__note {
  margin: var(--space-3) 0 0; font-size: 11px; line-height: 1.5; color: var(--muted);
}

/* CRESCO90-JE-LADDER-LAAT-ZIJN-SLOTEN-ZIEN — HET LADDEROVERZICHT.
   Vier rijen onder elkaar, elk met zijn teken rechts. Alle drie de tekens zijn
   TOKENS en nooit een vaste kleur: het blad moet in Cresco Nacht en in Cresco
   Dag hetzelfde verhaal vertellen, en --accent draait met het thema mee. */
.cr9-tiers { margin-top: var(--space-2); }
.cr9-tiers__title { margin: 0; font-size: 1.6rem; font-weight: 900; color: var(--ink); }
.cr9-tiers__list {
  list-style: none; margin: var(--space-4) 0 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--space-3);
}
.cr9-tiers__row {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  background: var(--surface);
}
.cr9-tiers__txt { flex: 1 1 auto; min-width: 0; }
.cr9-tiers__name {
  margin: 0; display: flex; align-items: center; gap: 6px;
  font-size: 14px; font-weight: 900; letter-spacing: .05em;
  text-transform: uppercase; color: var(--ink);
}
.cr9-tiers__what {
  margin: 4px 0 0; font-size: 13px; line-height: 1.45; color: var(--muted);
}
/* De kroon bij VRIJ SPEL, in hetzelfde goud als de kroon op de kaart: één
   goud-signaal per betekenis, en dit is dezelfde betekenis. */
.cr9-tiers__crown { display: inline-flex; color: #FBBF24; }
/* DE TREDE WAAR JE NU STAAT is de enige met een accentrand. Eén rij springt
   eruit, en dat is de rij waar je iets kunt doen. */
.cr9-tiers__row--here { border-color: var(--accent); border-width: 2px; }
.cr9-tiers__row--here .cr9-tiers__name { color: var(--accent); }
/* AFGEROND is een vinkje in de accentkleur. Geen groen, geen medaille: het is
   een vaststelling, niet een beloning die met de vorige concurreert. */
.cr9-tiers__done { flex: 0 0 auto; display: inline-flex; color: var(--accent); }
/* OP SLOT IS GEDIMD (45 %), nooit grijsgemaakt en nooit doorgestreept: die trede
   is waar je naartoe gaat, niet iets dat je bent misgelopen. De hele rij dimt
   mee, zodat het slot niet op een waarschuwing gaat lijken. */
.cr9-tiers__row--locked { opacity: .45; }
.cr9-tiers__lock { flex: 0 0 auto; display: inline-flex; color: var(--muted); }
/* "NU" met de bolletjes erin — dezelfde twee bolletjes als op de bolletjesrij,
   hier op de accentpil. Tekst en bolletjes op --on-accent, want een gevulde
   accentknop leest zijn voorgrond nooit uit --ink (huisregel). */
.cr9-tiers__now {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: 7px 14px; border-radius: 999px;
  background: var(--accent); color: var(--on-accent);
  font-size: 13px; font-weight: 900; letter-spacing: .05em; text-transform: uppercase;
}
.cr9-tiers__dots { display: flex; align-items: center; gap: 6px; }
.cr9-tiers__now .cr9-dot { border-color: var(--on-accent); }
.cr9-tiers__now .cr9-dot--on { background: var(--on-accent); border-color: var(--on-accent); }
.cr9-tiers__note {
  margin: var(--space-4) 0 0; font-size: 13px; line-height: 1.5; color: var(--muted);
}

/* JE KAART op datzelfde blad: die van nu en de volgende. De kaart zelf wordt
   door cr9Card() getekend — dezelfde tekenaar als op de wereld en op het bord —
   dus hier staat alleen waar die twee staan.

   ONDER ELKAAR, en niet naast elkaar zoals in mock M5. De mock tekent daar twee
   SMALLE tegels: alleen het schild, met de naam eronder als bijschrift. Dat is
   een tweede tekenaar, en die mag er niet komen — één kaart, één tekenaar. De
   echte kleine kaart is een RIJ (schild · strepen+kroon+niveaupil · naam) en die
   is op een halve 390px-kolom breder dan zijn doos: de niveaupil ging dwars door
   de kaartnaam heen. Over de volle breedte past hij precies zoals overal elders,
   dus de kolom wint het van de mock-indeling. */
.cr9-mycard { margin-top: var(--space-5); }
.cr9-mycard__title {
  margin: 0; font-size: 12px; font-weight: 800; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted);
}
.cr9-mycard__row {
  display: flex; flex-direction: column;
  gap: var(--space-4); margin-top: var(--space-3);
}
.cr9-mycard__one { min-width: 0; }
/* .cr9-card draagt zelf een margin-top voor zijn plek in de wereld; in deze
   kolom bepaalt de rij de afstand. */
.cr9-mycard__one > .cr9-card { margin-top: 0; }
/* Een lange kaartnaam duwt de rij anders open in plaats van af te breken: een
   flex-kind mag standaard niet onder zijn eigen woordbreedte krimpen. */
.cr9-mycard__one > .cr9-card > .cr9-card__name { min-width: 0; }
.cr9-mycard__cap {
  margin: var(--space-2) 0 0; font-size: 12px; line-height: 1.4; color: var(--muted);
}
/* De volgende kaart is gedimd zoals de volgende trede op de strook: hij is van
   jou zodra je er bent, en nog niet nu. */
.cr9-mycard__one + .cr9-mycard__one { opacity: .55; }
.cr9-mycard__all {
  display: inline-flex; align-items: center; gap: 4px;
  margin: var(--space-3) 0 0; padding: 6px 0;
  background: none; border: none;
  color: var(--accent); font-size: 14px; font-weight: 800; cursor: pointer;
}

/* CRESCO90-JE-EIGEN-MUZIEK-TERUG-MAAR-ALLEEN-ALS-JE-HEM-AANZET — ONE row: a
   word on the left, the app's own .set-toggle on the right. No sub-line, no
   second control; the music under a mobility session has its own speaker in the
   player and is governed there, not here. */
/* CRESCO90-MEEDOEN-ZET-JE-IN-DE-CRESCO-STIJL — .cr9-theme is de uitknop voor de
   huisstijl: dezelfde vorm, een eigen haak. De geluidsrij hierboven is elders
   gepind op precies één schakelaar, en die belofte hoort niet stilletjes te
   verwateren omdat er een tweede rij naast is komen staan. */
.cr9-sound,
.cr9-theme {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  margin-top: var(--space-4);
}
.cr9-sound__label,
.cr9-theme__label { font-size: 0.95rem; font-weight: 700; color: var(--ink); }

.cr9-nick { margin-top: var(--space-4); }
.cr9-nick__title { margin: 0 0 var(--space-2); font-size: 0.95rem; font-weight: 700; color: var(--ink); }
.cr9-nick__row { display: flex; gap: 6px; }
.cr9-nick__input {
  flex: 1; min-width: 0; padding: 10px 12px; font-size: 14px;
  border: 1px solid var(--line); border-radius: var(--radius-md);
  background: var(--surface); color: var(--ink);
}
/* .btn is width:100% by default; next to a field it has to be its own size. */
.cr9-nick__save { flex: 0 0 auto; width: auto; padding: 10px 16px; font-size: 13px; }
.cr9-nick__msg { margin: var(--space-2) 0 0; font-size: 12px; color: var(--muted); }
.cr9-nick__msg--bad { color: var(--danger); }

/* CRESCO90-EEN-GROEP-VRAAGT-PLUS — de groep, in twee stukken op één blad: een
   kaart bovenaan als er een groepslink in de hand is, en twee rijen in dit menu.
   ALLES hier staat in tokens (--accent, --surface, --ink, --muted, --line) en
   nergens in een letterlijke kleur, want dit blad leeft in de Cresco-NACHT én in
   de Cresco-dag: één hex hier zou in de ene stand onleesbaar zijn.
   Er komt GEEN --danger in dit blok voor. De drie dingen die mis kunnen gaan zijn
   een verlopen link, een tweede groep en een mislukt netwerk, en geen van die
   drie is een fout van de speler; ze lezen dus als mededeling en niet als alarm.

   De twee HOUDERS staan op display:contents, precies zoals .cr9-weeknote-slot
   hierboven en om dezelfde reden: .cr9-body is een flexkolom MET gap, dus een
   LEEG bakje levert daar alsnog een volle var(--space-4) aan lucht op. Zonder
   deze regel schuift de hele wereld 16px omlaag voor IEDEREEN die geen
   groepslink heeft — gemeten als 4360 verschilpixels op het ijkbeeld van
   cresco90-open-voor-klanten. Een bakje dat niets toont mag niets kosten. */
.cr9-grp-slot, .cr9-grpmenu-slot { display: contents; }
.cr9-grp {
  margin-top: var(--space-3);
  padding: var(--space-3);
  /* Dezelfde accentrand als de eerste-dagkaart: dit is óók iets dat één keer
     voorbijkomt en waar precies één beslissing in zit. */
  border: 1px solid var(--accent); border-radius: var(--radius-lg);
}
.cr9-grp__head { display: flex; align-items: center; gap: 8px; }
.cr9-grp__head svg { width: 18px; height: 18px; display: block; flex: 0 0 auto; color: var(--accent); }
.cr9-grp__title { font-size: 1rem; font-weight: 700; color: var(--ink); }
.cr9-grp__line { margin: var(--space-2) 0 var(--space-3); font-size: 13px; color: var(--muted); }
.cr9-grp__btn { width: 100%; }
/* De tweede deur is met opzet GEEN knop-die-eruitziet-als-een-knop: één accentknop
   per kaart, anders wegen "koop Plus" en "ik heb al betaald" even zwaar. `.btn` is
   hier niet gebruikt omdat dat width:100% en een kader meebrengt. */
.cr9-grp__code {
  display: block; width: 100%; margin: var(--space-2) 0 0; padding: 8px 0;
  border: 0; background: none; font: inherit; font-size: 13px;
  color: var(--muted); text-decoration: underline; cursor: pointer;
}
.cr9-grp__code:hover { color: var(--accent); }
.cr9-grp__msg { margin: var(--space-2) 0 0; font-size: 12px; color: var(--muted); }

/* De twee menurijen. Zelfde maatvoering als de bijnaamrij erboven, want het is
   hetzelfde soort ding: iets dat je over je eigen deelname ZET. */
.cr9-grpmenu { margin-top: var(--space-4); }
.cr9-grpmenu__title { margin: 0 0 var(--space-2); font-size: 0.95rem; font-weight: 700; color: var(--ink); }
.cr9-grpmenu__row { display: flex; gap: 6px; }
.cr9-grpmenu__input {
  flex: 1; min-width: 0; padding: 10px 12px; font-size: 14px;
  border: 1px solid var(--line); border-radius: var(--radius-md);
  background: var(--surface); color: var(--ink);
}
.cr9-grpmenu__save { flex: 0 0 auto; width: auto; padding: 10px 16px; font-size: 13px; }
.cr9-grpmenu__msg { margin: var(--space-2) 0 0; font-size: 12px; color: var(--muted); }

/* De link. `overflow-wrap: anywhere` omdat een uitnodigingslink één woord zonder
   spaties is: zonder die regel duwt hij op een telefoon het hele blad breder. */
.cr9-grpshare { margin-top: var(--space-4); }
.cr9-grpshare__title { margin: 0 0 var(--space-2); font-size: 0.95rem; font-weight: 700; color: var(--ink); }
.cr9-grpshare__url {
  margin: 0 0 var(--space-2); padding: 10px 12px;
  border: 1px solid var(--line); border-radius: var(--radius-md);
  background: var(--surface); color: var(--muted);
  font-size: 12px; overflow-wrap: anywhere;
}
.cr9-grpshare__btn { width: 100%; }
.cr9-grpshare__msg { margin: var(--space-2) 0 0; font-size: 12px; color: var(--muted); }

/* ── CRESCO90-DE-COACH-ZIET-ZIJN-GROEP-SPELEN ─────────────────────────────────
   DE SCHAKELAAR MIJN GROEP / IEDEREEN, boven het bord.

   Het bakje staat op display:contents om dezelfde reden als .cr9-grp-slot
   hierboven, maar met een ANDERE meting eronder: dit bakje woont niet in de
   flexkolom .cr9-body maar in de gewone blokstroom van <section class="cr9-board">,
   en een leeg blok kost daar niets. De regel staat er dus niet tegen lucht maar
   omdat hij de SOORT houder gelijk houdt aan de twee andere groepsbakjes — drie
   bakjes van dezelfde fase die zich verschillend gedragen is de val waarin de
   volgende hand stapt. Voor de 90 spelers zonder groep is dit bakje leeg en
   verschuift er geen pixel op tests/visual/cresco90-klassement.spec.js.

   De stand hangt aan [aria-pressed] en niet aan een eigen klasse: één waarheid
   voor het oog en voor de schermlezer, en niets dat uit de pas kan lopen. De
   aan-stand gebruikt --accent met --on-accent erop, want dat is de enige
   tekstkleur die op het limoen van deze wereld leesbaar blijft in Nacht ÉN Dag.
   Geen rood aan deze kant van het bord: er valt hier niets fout te kiezen. */
.cr9-grpscope-slot { display: contents; }
.cr9-grpscope { display: flex; gap: 6px; margin: 0 0 var(--space-3); }
.cr9-grpscope__btn {
  flex: 1 1 0; min-width: 0;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 8px 10px;
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  background: transparent; color: var(--ink);
  font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
}
.cr9-grpscope__btn svg { width: 15px; height: 15px; display: block; flex: 0 0 auto; }
.cr9-grpscope__btn[aria-pressed='true'] {
  background: var(--accent); color: var(--on-accent); border-color: var(--accent);
}

/* HET LEDENOVERZICHT van de eigenaar. Zelfde maatvoering als de bordrij, want
   het is hetzelfde soort regel: een naam en een stand, en de hele rij is de deur
   naar hetzelfde profiel. */
.cr9-grpmem { margin-top: var(--space-4); }
.cr9-grpmem__title { margin: 0 0 var(--space-2); font-size: 0.95rem; font-weight: 700; color: var(--ink); }
.cr9-grpmem__list { list-style: none; margin: 0; padding: 0; }
.cr9-grpmem__row {
  display: flex; align-items: center; gap: var(--space-2);
  padding: 8px 0; font-size: 13px;
  position: relative; /* anker voor .cr9-grpmem__open, net als de bordrij */
}
.cr9-grpmem__row + .cr9-grpmem__row { border-top: 1px solid var(--line); }
/* De deur ligt ERBOVEN en niet eromheen, precies als .cr9-lb__open: de rij is
   een flex-rij en die in een knop wikkelen zou de indeling verzetten. Er zitten
   geen andere knoppen in deze rij, dus hij kan er ook geen opslokken. */
.cr9-grpmem__open {
  position: absolute; inset: 0; width: 100%; padding: 0;
  border: 0; background: none; cursor: pointer;
  border-radius: var(--radius-md);
}
.cr9-grpmem__open:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cr9-grpmem__name { flex: 1 1 auto; min-width: 0; color: var(--ink); font-weight: 600; overflow-wrap: anywhere; }
/* Limoen of grijs. NOOIT rood: "nog niet actief deze week" is een stand
   halverwege een week en geen fout — de week loopt nog. */
.cr9-grpmem__dot {
  flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%;
  background: var(--muted); opacity: 0.55;
}
.cr9-grpmem__dot--on { background: var(--accent); opacity: 1; }
.cr9-grpmem__state { flex: 0 0 auto; font-size: 12px; color: var(--muted); }
.cr9-grpmem__waiting { margin: var(--space-2) 0 0; font-size: 12px; color: var(--muted); }

.cr9-lb { list-style: none; margin: 0; padding: 0; }
.cr9-lb__row {
  display: flex; align-items: center; gap: var(--space-2);
  padding: 8px 0; font-size: 13px;
  /* CRESCO90-EEN-TIK-OP-DE-RIJ-OPENT-ZIJN-WEEK — het anker voor .cr9-lb__open. */
  position: relative;
}
.cr9-lb__row + .cr9-lb__row { border-top: 1px solid var(--line); }
/* CRESCO90-DE-ARENA-SORTEERT-OP-JE-REEKS — JOUW RIJ, MET EEN LIMOEN RAND.
   Vet alleen was te weinig: op een bord waar je naar vijf rijen achter elkaar
   kijkt zoek je jezelf, en dikke letters vind je pas als je ze al leest. De rand
   ligt rondom en niet links, want links staat de plek en een streepje daar zou
   als een rangteken lezen. --accent is het limoen van deze wereld (ontwerp §D);
   hij staat nergens anders op een rij, dus hij kan alleen "jij" betekenen. */
.cr9-lb__row--mine {
  font-weight: 700;
  border: 2px solid var(--accent);
  border-radius: var(--radius-2, 10px);
  padding: 8px 10px;
  margin: 2px 0;
}
/* De bovenrand van de buur eronder zou naast die rand een tweede lijn zijn. */
.cr9-lb__row--mine + .cr9-lb__row { border-top: 0; }
/* "Vind mij" laat je rij TWEE KEER oplichten en daarna is het stil. Twee is
   genoeg om je oog te halen en weinig genoeg om niet te gaan kloppen; een
   oneindige puls zou het bord tot een alarm maken. De vlag gaat niet weer uit —
   hij hoeft niet: `animation-iteration-count: 2` stopt zichzelf, en de klasse
   opnieuw zetten (na één frame eraf) start hem opnieuw. Onder
   prefers-reduced-motion knipt het globale blok bovenaan dit bestand de animatie
   weg; cresco90.js zet de rij dan op focus in plaats van te laten oplichten. */
@keyframes cr9LbFound {
  0%, 100% { background: transparent; }
  50%      { background: color-mix(in srgb, var(--accent) 22%, transparent); }
}
.cr9-lb__row--found { animation: cr9LbFound 620ms ease-in-out 2; }
/* De plek: het rangteken (alleen bij 1, 2 en 3) en het nummer, naast elkaar. */
.cr9-lb__rank {
  display: inline-flex; align-items: center; gap: 3px;
  min-width: 46px; flex: 0 0 auto;
  color: var(--muted); font-weight: 700;
}
.cr9-lb__place { font-variant-numeric: tabular-nums; }
.cr9-lb__trophy { display: inline-flex; line-height: 0; }
.cr9-lb__trophy svg { width: 15px; height: 15px; display: block; }
.cr9-lb__trophy--gold   { color: var(--cr9-gold); }
.cr9-lb__trophy--silver { color: var(--cr9-silver); }
.cr9-lb__trophy--bronze { color: var(--cr9-bronze); }
/* De reeks in cijfers, achter de naam. Dezelfde vlam als op de kaart, dezelfde
   demping als het schild: het is een telling en geen onderscheiding. */
.cr9-lb__streak {
  display: inline-flex; align-items: center; gap: 3px; flex: 0 0 auto;
  color: var(--muted); font-weight: 700; font-variant-numeric: tabular-nums;
}
.cr9-lb__streak svg { width: 13px; height: 13px; display: block; }
/* "Jouw buurt" — de regel die het gat tussen de top 3 en je eigen buurt
   overbrugt. Geen kopje met een lijn eronder: het is ÉÉN rangorde en een kop zou
   er twee lijsten van maken. Alleen een gedempt woord op de plek van het gat. */
.cr9-lb__gap {
  padding: 10px 0 4px;
  font-size: 10px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--muted);
  border-top: 1px dashed var(--line);
}
/* De twee knoppen boven de lijst. Omlijnd en klein: ze veranderen wat je ZIET
   en niet wat er staat, dus ze mogen nooit de accentkleur van een handeling
   dragen. */
.cr9-lb__tools {
  display: flex; align-items: center; gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.cr9-lb__toggle,
.cr9-lb__find {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 6px 10px; border: 1px solid var(--line); border-radius: 999px;
  background: transparent; color: var(--muted);
  font: inherit; font-size: 11px; font-weight: 700; cursor: pointer;
}
.cr9-lb__find svg { width: 14px; height: 14px; display: block; }
.cr9-lb__find { margin-left: auto; }
.cr9-lb__main { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.cr9-lb__name { color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* .cr9-lb__band stond hier tot CRESCO90-JE-KAART-DRAAGT-JE-STREPEN-EN-JE-KROON: de
   scoreband onder de naam ("Op koers", "Onderweg"). Die band is een woord over hoe
   iemands WEEK liep en hoort in de maandagbrief, niet in een ranglijstrij waar het
   als een oordeel over de persoon leest. De rij noemt nu één ding: de naam. */
/* CRESCO90-EEN-PAUZEWEEK-HOUDT-JE-REEKS-VAST — het schild in de rij. Gedempt en
   klein: een pauze is een mededeling, geen onderscheiding en geen waarschuwing.
   flex-shrink:0 omdat .cr9-lb__main ernaast op flex:1 staat en een lange naam
   het schild anders wegdrukt. */
.cr9-lb__shield {
  display: inline-flex; align-items: center; flex: 0 0 auto;
  color: var(--muted);
}
.cr9-lb__shield svg { width: 13px; height: 13px; display: block; }
/* .cr9-lb__score stond hier tot CRESCO90-DE-ARENA-SORTEERT-OP-JE-REEKS: het
   cijfer aan het eind van de rij. Iedereen die volhoudt komt op 90 uit, dus dat
   getal onderscheidde niemand en vroeg wel de breedste kolom van de rij op. Het
   cijfer woont nu in het profiel, waar er ruimte is om te laten zien HOE het is
   opgebouwd. Zet hem niet terug: de rij zou dan twee dingen over dezelfde week
   zeggen (de reeks en het cijfer) en de sortering volgt alleen de eerste. */
.cr9-lb__waiting {
  display: flex; align-items: center; gap: var(--space-2);
  margin-top: var(--space-3); padding-top: var(--space-3); border-top: 1px dashed var(--line);
}
.cr9-lb__dots { display: inline-flex; gap: 6px; flex-shrink: 0; }
.cr9-dot {
  width: 10px; height: 10px; border-radius: 50%;
  border: 1.5px solid var(--line); background: transparent;
}
.cr9-dot--on { border-color: var(--accent); background: var(--accent); }
/* CRESCO90-ELKE-PIJLERDAG-IS-EEN-MUNT-IN-ZIJN-KLEUR — HET VOORLOPIGE BOLLETJE.
   De week die NU loopt en al vijftien munten heeft, heeft zijn bolletje nog niet:
   de nachtrun schrijft hem maandag. Tot dat moment staat hij er als STIPPELRAND
   met een doorzichtige vulling — hij is van jou, en hij is nog niet geteld. Een
   stippellijn betekent in deze app al "voorlopig" (zie .cr9-lb__waiting hierboven),
   dus dit leent een betekenis in plaats van er een uit te vinden.
   Hij staat ACHTER .cr9-dot--on in het bestand en wint dus op gelijke
   specificiteit: de klasse wordt er samen mét --on op gezet, zodat een rij zonder
   deze regel (oude gecachte CSS) een gewoon gevuld bolletje toont en niet niets. */
.cr9-dot--soon {
  border-style: dashed;
  border-color: var(--accent);
  background: transparent;
}
/* "Maandag telt hij." — de zin onder de rij die het stippelbolletje uitlegt.
   Klein en gedempt: het is een belofte, geen mededeling, en er staat nergens
   een getal bij dat vooruitloopt op de nachtrun. */
.cr9-dots__soon {
  margin: var(--space-1) 0 0; text-align: center;
  font-size: 12px; font-weight: 700; color: var(--muted);
}
.cr9-lb__waiting-txt { margin: 0; font-size: 11px; line-height: 1.5; color: var(--muted); }
.cr9-board__empty { margin: 0; font-size: 12px; line-height: 1.5; color: var(--muted); }

/* ── CRESCO90-DE-ARENA-SORTEERT-OP-JE-REEKS — DE NEW CONTENDERS ──────── */
/* Onder de Arena, met een stippellijn ertussen en niet met een kader eromheen:
   het is geen tweede ranglijst, het is de aanloop naar dezelfde. De Arena blijft
   voor deze mensen gewoon leesbaar — in de oude app was die vaag gemaakt, en
   zien waar je naartoe gaat motiveert (ontwerp §D). NERGENS een slot, een waas
   of een upgrade-regel. */
.cr9-contenders {
  margin-top: var(--space-4); padding-top: var(--space-3);
  border-top: 1px dashed var(--line);
}
.cr9-contenders__title {
  margin: 0 0 var(--space-2);
  font-size: 10px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--muted);
}
.cr9-contenders__list { list-style: none; margin: 0; padding: 0; }
/* GEEN rangnummer en GEEN niveaupil op deze rij: er is nog niets om te
   rangschikken, en een plek geven aan wie nog niet in de Arena staat zou een
   tweede rangorde maken naast de enige die er is. */
.cr9-contenders__row {
  display: flex; align-items: center; gap: var(--space-2);
  padding: 7px 0; font-size: 13px;
  position: relative; /* anker voor .cr9-lb__open, net als de Arena-rij */
}
.cr9-contenders__row + .cr9-contenders__row { border-top: 1px solid var(--line); }
/* Dezelfde limoen rand als op je Arena-rij, om precies dezelfde reden: dit ben
   jij. Eén teken voor "jij" over het hele bord. */
.cr9-contenders__row--mine {
  font-weight: 700;
  border: 2px solid var(--accent);
  border-radius: var(--radius-2, 10px);
  padding: 7px 10px; margin: 2px 0;
}
.cr9-contenders__row--mine + .cr9-contenders__row { border-top: 0; }
.cr9-contenders__name {
  flex: 1; min-width: 0; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cr9-contenders__dots { display: inline-flex; gap: 5px; flex: 0 0 auto; }

/* HET MUNTBALKJE. Vier groepjes cellen in de vier pijlerkleuren, in de vaste
   orde kracht → cardio → voeding → mobiliteit, gevuld van links. Eén dun balkje
   en geen vier ringen: dit is een lijstrij, en vier ringen per persoon maakt van
   een lijst een dashboard.

   Een LEGE cel is een hairline en nooit rood: niet gehaald is in dit spel niet
   fout, het is nog niet gebeurd (ontwerp §B). Het getal erachter zegt hoeveel
   van hoeveel, want vijftien celletjes tellen doe je niet met je oog. */
.cr9-coinbar { display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto; }
.cr9-coinbar__group { display: inline-flex; gap: 1.5px; }
.cr9-coinbar__cell {
  width: 4px; height: 12px; border-radius: 1px;
  border: 1px solid var(--line); background: transparent;
}
.cr9-coinbar__cell--on { border-color: var(--cr9-coin, var(--accent)); background: var(--cr9-coin, var(--accent)); }
.cr9-coinbar__group--strength  { --cr9-coin: var(--cr9-strength); }
.cr9-coinbar__group--cardio    { --cr9-coin: var(--cr9-cardio); }
.cr9-coinbar__group--nutrition { --cr9-coin: var(--cr9-nutrition); }
.cr9-coinbar__group--mobility  { --cr9-coin: var(--cr9-mobility); }
.cr9-coinbar__count {
  margin-left: 2px; font-size: 11px; font-weight: 700; color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* ══ CRESCO90-EEN-TIK-OP-DE-RIJ-OPENT-ZIJN-WEEK ═══════════════════════════════
 *
 * DE HELE RIJ IS DE TIK. Een knop over de rij in plaats van eromheen: de rij is
 * een flex-rij van vier kinderen (plek, kaart, naam, reeks) en die in een knop
 * wikkelen zou de indeling verzetten en daarmee ook de kaart erin, die met de
 * kaart op je eigen blad identiek hoort te blijven. Onzichtbaar, maar een ECHTE
 * knop: tab-bereikbaar, met een naam, en met een zichtbare focusring — de
 * inhoud eronder blijft leesbaar omdat er niets over gekleurd wordt.
 */
.cr9-lb__open {
  position: absolute; inset: 0;
  background: none; border: 0; padding: 0; margin: 0;
  cursor: pointer;
  border-radius: var(--radius-2, 10px);
}
.cr9-lb__open:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* De tik moet voelbaar zijn, ook zonder dat er iets opent dat je al ziet: een
   rij die niets doet bij aanraking leest als een rij die niet aan te tikken is. */
.cr9-lb__open:active { background: color-mix(in srgb, var(--accent) 10%, transparent); }

/* ── HET PROFIELBLAD (mock M3) ──
 * Kaart, DEZE WEEK met vier pijlerregels, het muntenraster, en de wekenstrook.
 * Eén kolom, want dit is een blad in dezelfde overlay als de wereld. */
.cr9-profile { display: flex; flex-direction: column; gap: var(--space-3); }
.cr9-profile__dots { display: flex; justify-content: center; }
/* De labels DEZE WEEK en WEKEN: kleine kapitalen, gedempt, geen lijn eronder.
   Ze scheiden twee blokken op hetzelfde blad en zijn geen koppen van twee
   pagina's; een lijn zou er twee schermen van maken. */
.cr9-profile__label {
  margin: 0 0 var(--space-2); font-size: 10px; font-weight: 800;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted);
}
.cr9-profile__week, .cr9-profile__weeks { margin: 0; }
/* HET WEEKCIJFER, groot, met zijn plafond erachter — precies de vorm van
   .cr9-score bovenaan de wereld, zodat "70 / 90" hier hetzelfde ding is als
   daar en niemand hoeft te vergelijken of het wel hetzelfde getal is. */
.cr9-profile__score { display: flex; align-items: baseline; gap: 6px; margin: 0 0 var(--space-2); }
.cr9-profile__big { font-size: 2.4rem; font-weight: 800; line-height: 1; color: var(--accent); }
.cr9-profile__max { font-size: 1.05rem; font-weight: 700; line-height: 1; color: var(--muted); }
/* EEN PAUZEWEEK DRAAGT GEEN CIJFER. Het schild en het woord, in de gedempte
   kleur en NOOIT in var(--danger): een beschermde week is geen mislukte week. */
.cr9-profile__pause {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 13px; font-weight: 700; color: var(--muted);
}
.cr9-profile__pause svg { width: 16px; height: 16px; display: block; }

/* DE VIER PIJLERREGELS: naam · telling · cijfer. Eén regel per pijler, met de
   kleur van die pijler als stip ervoor — dezelfde vier kleuren als de ringen en
   het muntbalkje, uit data-pillar, dus Nacht en Dag houden hun eigen palet. */
.cr9-profile__pillars { list-style: none; margin: 0 0 var(--space-2); padding: 0; }
.cr9-pp {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px; font-size: 13px;
  border: 1px solid var(--line); border-radius: var(--radius-2, 10px);
  margin-bottom: 5px;
}
.cr9-pp::before {
  content: ''; flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%;
  background: var(--cr9-coin, var(--accent));
}
.cr9-pp[data-pillar="strength"]  { --cr9-coin: var(--cr9-strength); }
.cr9-pp[data-pillar="cardio"]    { --cr9-coin: var(--cr9-cardio); }
.cr9-pp[data-pillar="nutrition"] { --cr9-coin: var(--cr9-nutrition); }
.cr9-pp[data-pillar="mobility"]  { --cr9-coin: var(--cr9-mobility); }
.cr9-pp__name {
  flex: 1; min-width: 0; color: var(--ink); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.04em; font-size: 11px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cr9-pp__count { flex: 0 0 auto; color: var(--muted); font-variant-numeric: tabular-nums; }
.cr9-pp__sep { flex: 0 0 auto; color: var(--muted); }
.cr9-pp__score {
  flex: 0 0 auto; min-width: 26px; text-align: right;
  color: var(--ink); font-weight: 800; font-variant-numeric: tabular-nums;
}
/* EEN VOLLE PIJLER LICHT OP — de limoenen gloed van de mock. Hij hangt aan
   `days >= bar` en niet aan het cijfer: 90 is wat een volle pijler OPLEVERT, en
   op het cijfer kijken zou een pijler met een verschoven lat stil verkeerd
   tekenen. Niet-vol krijgt GEEN rand in een waarschuwkleur: nog niet gehaald is
   in dit spel niet fout, het is nog niet gebeurd. */
.cr9-pp--full {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent), 0 0 12px color-mix(in srgb, var(--accent) 30%, transparent);
}
.cr9-pp--full .cr9-pp__score { color: var(--accent); }
.cr9-profile__note {
  margin: 0 0 var(--space-3); font-size: 11px; line-height: 1.5; color: var(--muted);
}

/* HET MUNTENRASTER: zeven dagen breed, vier pijlers hoog. Vaste kolommen, want
   een raster waarvan de kolommen per rij verschuiven is geen raster; dan valt
   "op welke dag" niet meer van boven naar onder te lezen. */
.cr9-pgrid { display: flex; flex-direction: column; gap: 5px; }
.cr9-pgrid__dow, .cr9-pgrid__row {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; align-items: center;
}
.cr9-pgrid__day {
  text-align: center; font-size: 10px; font-weight: 800; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--muted);
}
/* VANDAAG IS ONDERSTREEPT en niet gevuld: een vulling zou als een gehaalde dag
   lezen, en vandaag is nog niet af. Limoen, want dat is het "hier ben je" van
   deze wereld, en de streep ligt onder het woord — niet eromheen, want een kader
   om een dag leest als een selectie die je gemaakt hebt. */
.cr9-pgrid__day--today {
  color: var(--accent);
  border-bottom: 2px solid var(--accent);
  padding-bottom: 2px;
}
.cr9-pgrid__coin {
  width: 100%; aspect-ratio: 1 / 1; max-width: 22px; justify-self: center;
  border-radius: 50%;
  /* LEEG IS EEN HAARLIJN-CIRKEL en nooit rood of gekruist: niet gehaald is in dit
     spel niet fout (ontwerp §B). Hetzelfde als de lege cel in het muntbalkje. */
  border: 1px solid var(--line); background: transparent;
}
.cr9-pgrid__coin--on {
  border-color: var(--cr9-coin, var(--accent));
  background: var(--cr9-coin, var(--accent));
}
/* DE DAGEN DIE NOG MOETEN KOMEN zijn gedimd. Ze staan er WEL: een week die op
   donderdag bij vier cellen ophoudt leest als een week van vier dagen, en dan is
   er niets meer te halen te zien. */
.cr9-pgrid__coin--ahead { opacity: 0.35; }
.cr9-pgrid__row[data-pillar="strength"]  { --cr9-coin: var(--cr9-strength); }
.cr9-pgrid__row[data-pillar="cardio"]    { --cr9-coin: var(--cr9-cardio); }
.cr9-pgrid__row[data-pillar="nutrition"] { --cr9-coin: var(--cr9-nutrition); }
.cr9-pgrid__row[data-pillar="mobility"]  { --cr9-coin: var(--cr9-mobility); }
.cr9-profile__gridslot { margin-top: var(--space-2); }

/* ── DE WEKENSTROOK: TEGELS, GEEN BOLLETJES ──
 * Een bolletje kan twee dingen zeggen en een week heeft er vier te vertellen:
 * perfect, niet gehaald, gepauzeerd, en of er een streep aan vast zat. Dus een
 * tegel, met het CIJFER erin — "82" is het verschil tussen een week die net
 * misging en een week waarin niets gebeurde. Ze wikkelen door, want een half
 * jaar zij-scrollen is een half jaar dat je niet in één blik ziet. */
.cr9-pweeks { display: flex; flex-wrap: wrap; gap: 6px; }
.cr9-ptile {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0;
  border: 1px solid var(--line); border-radius: var(--radius-2, 10px);
  background: var(--surface-2, transparent); color: var(--muted);
  font-size: 12px; font-weight: 800; font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.cr9-ptile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cr9-ptile svg { width: 14px; height: 14px; display: block; }
/* PERFECT IS LIMOEN, gevuld, met het cijfer in de leeskleur ván dat vlak —
   var(--on-accent), nooit een eigen hex: op het dagthema is limoen licht en
   staat er dan donkere tekst op. */
.cr9-ptile--perfect {
  border-color: var(--accent); background: var(--accent); color: var(--on-accent);
}
/* NIET GEHAALD IS GRIJS MET ZIJN CIJFER. Geen rood: dat zou van een week waarin
   iemand gewoon minder deed een fout maken (ontwerp §B, huisregel). */
.cr9-ptile--miss { border-color: var(--line); color: var(--ink); }
/* EEN PAUZE DRAAGT HET SCHILD en geen cijfer — er is niets gemeten. */
.cr9-ptile--pause { border-style: dashed; color: var(--muted); }
/* EEN STREEPWEEK draagt er een chevron bij, in de hoek: dezelfde chevron als op
   de kaart, want het is dezelfde streep. */
.cr9-ptile__stripe {
  position: absolute; top: -3px; right: -3px;
  display: inline-flex; line-height: 0; color: var(--cr9-gold, var(--accent));
}
.cr9-ptile__stripe svg { width: 11px; height: 11px; }
/* DE GEKOZEN TEGEL — die waarvan het raster hierboven de week laat zien. Een
   ring eromheen en geen vulling: de vulling zegt al of die week perfect was, en
   die overschrijven zou de uitslag van de week wegpoetsen. */
.cr9-ptile--sel { box-shadow: 0 0 0 2px var(--accent); }

/* ── CRESCO90-EEN-PAUZEWEEK-HOUDT-JE-REEKS-VAST ───────────────────── */
/* De deur onder de ringen. .btn is width:100%, en dat blijft hier zo: de knop
   ligt op volle breedte onder het weekoverzicht en is daarmee duidelijk een
   RUSTPUNT en geen knopje in een rij. Omlijnd (.btn--ghost) en niet in de
   accentkleur — de hoofdhandeling op dit blad is trainen, niet pauzeren.
   NERGENS var(--danger): wie zijn week op pauze zet maakt geen fout (doc §7). */
.cr9-pausedoor { margin-top: var(--space-4); }
.cr9-pausedoor__btn {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 12px 16px; font-size: 12px;
}
.cr9-pausedoor__btn svg { width: 16px; height: 16px; display: block; flex: 0 0 auto; }
/* Staat er al een schild, dan draagt de knop de accentrand — dezelfde knop,
   een andere toestand. Geen tweede knop en geen kruisje. */
.cr9-pausedoor__btn--on { border-color: var(--accent); color: var(--accent); }
.cr9-pausedoor__btn--on:hover { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }

/* CRESCO90-JE-KUNT-IEMAND-AANMOEDIGEN — de knop onder een profiel, en de regel
   op je eigen wereld.

   OMLIJND (.btn--ghost) EN NIET IN DE ACCENTKLEUR, precies zoals de pauzedeur
   hierboven: de hoofdhandeling op dit blad is kijken naar iemands week, niet
   drukken. Een gevulde accentknop zou de bliksem het luidste ding op het scherm
   maken, en dan wordt aanmoedigen een spelletje in plaats van een gebaar.

   NA DE TIK DOOFT HIJ EN GAAT HIJ NIET WEG. Een knop die verdwijnt laat je
   twijfelen of je tik aankwam; een uitgeschakelde knop met "Aangemoedigd"
   erin antwoordt op precies die vraag. Nergens rood en nergens een kruis: twee
   keer tikken is geen fout. */
.cr9-cheer { margin-top: var(--space-4); }
.cr9-cheer__btn {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 12px 16px; font-size: 12px;
}
.cr9-cheer__btn svg { width: 16px; height: 16px; display: block; flex: 0 0 auto; }
.cr9-cheer__btn--done, .cr9-cheer__btn:disabled {
  opacity: .55; cursor: default; border-color: var(--line); color: var(--muted);
}
.cr9-cheer__btn--done:hover, .cr9-cheer__btn:disabled:hover {
  background: transparent; border-color: var(--line); color: var(--muted);
}
.cr9-cheer__hint {
  margin: 6px 0 0; font-size: 11px; line-height: 1.5; color: var(--muted); text-align: center;
}
/* De regel op je eigen wereld, klein en onder je kaart. Het is een mededeling
   over wat anderen deden, dus hij draagt geen getal in de accentkleur: dan zou
   populariteit naast je Cresco-score gaan staan als tweede cijfer. */
.cr9-cheernote {
  margin: var(--space-2) 0 0; font-size: 12px; line-height: 1.6; color: var(--muted);
}

.cr9-pause { margin-top: var(--space-3); }
.cr9-pause__title { margin: 0; font-size: 1.05rem; font-weight: 800; color: var(--ink); }
.cr9-pause__lead {
  margin: var(--space-2) 0 0; font-size: 13px; line-height: 1.6; color: var(--muted);
}
.cr9-pause__weeks {
  display: flex; flex-direction: column; gap: var(--space-2);
  margin-top: var(--space-4);
}
.cr9-pause__pick { font-size: 12px; padding: 12px 16px; }
/* De gezette week: schild, naam, en pas daarna de terugknop. Een rij en geen
   knop, want de week IS al gekozen — er valt hier niets meer te kiezen. */
.cr9-pause__set {
  display: flex; align-items: center; gap: var(--space-2);
  padding: 12px 14px;
  border: 1px solid var(--accent); border-radius: var(--radius-md);
  background: var(--surface);
}
.cr9-pause__set svg { width: 18px; height: 18px; display: block; flex: 0 0 auto; color: var(--accent); }
.cr9-pause__setname {
  flex: 1; min-width: 0; font-size: 13px; font-weight: 700; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* .btn is width:100%; naast de naam moet hij zijn eigen maat hebben. */
.cr9-pause__off { flex: 0 0 auto; width: auto; padding: 8px 12px; font-size: 11px; }
.cr9-pause__quota {
  margin: var(--space-3) 0 0; font-size: 12px; line-height: 1.5; color: var(--muted);
}
/* Ook de foutregel is gedempt en niet rood: het quotum op hebben is geen fout,
   het is een getal. */
.cr9-pause__msg { margin: var(--space-2) 0 0; font-size: 12px; line-height: 1.5; color: var(--muted); }

/* ── CRESCO90-ELKE-STREEP-KRIJGT-ZIJN-CEREMONIE ───────────────────── */
/* De ceremonie hangt IN de wereld-overlay en legt er geen tweede overheen.
   `.cr9-overlay` is al `position: fixed; inset: 0`, dus `absolute` hier is
   precies zijn vlak en geen pixel meer — een eigen tweede scrim zou het zwart
   verdubbelen en de wereld zichtbaar twee keer laten dimmen. */
.cr9-cer {
  position: absolute; inset: 0; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  padding: var(--space-4);
  background: rgba(0, 0, 0, 0.72);
}
/* De woorden staan op een PANEEL en niet los op het zwart. Los op het zwart
   zou var(--ink) betekenen, en dat is in Cresco Dag donkere inkt op een donker
   vlak: onleesbaar precies in het thema waar niemand naar kijkt bij het bouwen.
   Het paneel draagt var(--bg), zodat de kaart erop met var(--surface) nog
   steeds als kaart leest — dezelfde stapeling als het blad eronder. */
.cr9-cer__box {
  width: 100%; max-width: 360px;
  max-height: 100%; overflow-y: auto;
  padding: var(--space-4);
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  background: var(--bg); box-shadow: var(--elev-up);
  text-align: center;
  perspective: 900px;
}
.cr9-cer__stage { position: relative; }
/* .cr9-card draagt een margin-top voor zijn plek in het blad; hier is hij het
   eerste ding en zou die marge een gat bovenin het paneel maken. */
.cr9-cer__stage > .cr9-card { margin-top: 0; }

/* De kaart LANDT: één val van boven, geen stuiter. Alle bewegingsregels hangen
   achter `.cr9-cer--anim`, die het script alleen zet als bewegen mag — zo staat
   de afspraak op één plek en niet verspreid over veertien `animation`-regels. */
.cr9-cer--anim .cr9-cer__stage--land { animation: cr9CerLand 520ms cubic-bezier(.2, .8, .3, 1) both; }
@keyframes cr9CerLand {
  from { opacity: 0; transform: translateY(-18px) scale(.94); }
  to   { opacity: 1; transform: none; }
}
/* Legend / Black Card: het scherm dimt eerst (de vertraging) en dan draait de
   kaart ÉÉN keer om zijn as. */
.cr9-cer--anim .cr9-cer__stage--spin { animation: cr9CerSpin 900ms cubic-bezier(.3, .7, .2, 1) 220ms both; }
@keyframes cr9CerSpin {
  from { opacity: 0; transform: rotateY(0deg); }
  20%  { opacity: 1; }
  to   { opacity: 1; transform: rotateY(360deg); }
}
/* Streep 3: de gouden rand TREKT ZICH ÉÉN KEER EN BLIJFT. Vier randkleuren na
   elkaar, en `forwards` houdt het laatste beeld vast — dat "en blijft" is dus
   het einde van de animatie zelf en geen tweede klasse die er daarna op moet.
   Zonder beweging staat de volle rand er meteen (de regel hieronder). */
.cr9-cer__stage--ring::after {
  content: ''; position: absolute; inset: 0;
  border: 2px solid var(--cr9-gold); border-radius: var(--radius-lg);
  pointer-events: none;
}
.cr9-cer--anim .cr9-cer__stage--ring { animation: cr9CerFade 300ms ease-out both; }
.cr9-cer--anim .cr9-cer__stage--ring::after {
  border-color: transparent;
  animation: cr9CerTrace 900ms linear 200ms forwards;
}
@keyframes cr9CerFade { from { opacity: 0; } to { opacity: 1; } }
@keyframes cr9CerTrace {
  0%   { border-color: transparent; }
  25%  { border-top-color: var(--cr9-gold); }
  50%  { border-top-color: var(--cr9-gold); border-right-color: var(--cr9-gold); }
  75%  { border-top-color: var(--cr9-gold); border-right-color: var(--cr9-gold); border-bottom-color: var(--cr9-gold); }
  100% { border-color: var(--cr9-gold); }
}

/* De vier vonken bij streep 1. VIERPUNTIG via clip-path en niet met een letter
   of een emoji: het is versiering, het staat op aria-hidden, en een glyph zou
   door een schermlezer alsnog uitgesproken worden. Ze staan standaard op
   opacity 0, dus zonder beweging is er niets te zien — geen vlucht, geen rest. */
.cr9-cer__spark {
  position: absolute; width: 14px; height: 14px;
  background: var(--accent); opacity: 0; pointer-events: none;
  clip-path: polygon(50% 0%, 60% 40%, 100% 50%, 60% 60%, 50% 100%, 40% 60%, 0% 50%, 40% 40%);
}
.cr9-cer__spark--1 { top: -6px; left: 12%; }
.cr9-cer__spark--2 { top: 18%; right: -6px; }
.cr9-cer__spark--3 { bottom: 12%; left: -6px; }
.cr9-cer__spark--4 { bottom: -6px; right: 18%; }
.cr9-cer--anim .cr9-cer__spark { animation: cr9CerSpark 900ms ease-out both; }
.cr9-cer--anim .cr9-cer__spark--2 { animation-delay: 120ms; }
.cr9-cer--anim .cr9-cer__spark--3 { animation-delay: 240ms; }
.cr9-cer--anim .cr9-cer__spark--4 { animation-delay: 360ms; }
@keyframes cr9CerSpark {
  0%   { opacity: 0; transform: scale(.4); }
  40%  { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(.8); }
}

/* Streep 2: het slot op Zwaar SPRINGT OPEN. Accentkleur en nergens rood — een
   slot dat opengaat is geen waarschuwing. */
.cr9-cer__lock {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  margin: var(--space-3) 0 0;
  font-size: 12px; font-weight: 700; color: var(--accent);
}
.cr9-cer__lock svg { width: 18px; height: 18px; display: block; flex: 0 0 auto; }
.cr9-cer--anim .cr9-cer__lock--pop svg {
  transform-origin: 50% 70%;
  animation: cr9CerPop 520ms cubic-bezier(.2, 1.3, .4, 1) 260ms both;
}
@keyframes cr9CerPop {
  from { opacity: 0; transform: rotate(-12deg) scale(.7); }
  to   { opacity: 1; transform: none; }
}

.cr9-cer__say {
  margin: var(--space-3) 0 0; font-size: 1.05rem; font-weight: 800;
  line-height: 1.4; color: var(--ink);
}
.cr9-cer__note { margin: var(--space-3) 0 0; font-size: 12px; line-height: 1.6; color: var(--muted); }
/* Het tabelletje bij vrij spel: hoeveel oefeningen welke zwaarte zijn. Links
   uitgelijnd binnen een middengecentreerd paneel, want twee kolommen die elk
   apart gecentreerd staan zijn geen tabel meer. */
.cr9-cer__table {
  display: grid; grid-template-columns: 1fr auto; gap: 6px var(--space-3);
  margin: var(--space-3) 0 0; padding: var(--space-3);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  text-align: left;
}
.cr9-cer__tkey { margin: 0; font-size: 12px; line-height: 1.5; color: var(--muted); }
.cr9-cer__tval { margin: 0; font-size: 12px; line-height: 1.5; font-weight: 700; color: var(--ink); text-align: right; }
/* .btn is width:100%, dus de twee deuren staan onder elkaar en even breed: op
   390px is een rij van twee knoppen twee halve knoppen. */
.cr9-cer__btns { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-4); }

/* GEEN vluchten, draaien of openspringende sloten. Het script zet `--anim` al
   niet als de voorkeur aan staat; dit is de tweede grendel, voor het geval de
   voorkeur pas NA het openen van de wereld omgaat — dan is de klasse er nog en
   de animatie moet alsnog uit. De gouden rand krijgt zijn volle kleur terug,
   want zonder animatie is er geen `forwards` die hem vasthoudt. */
@media (prefers-reduced-motion: reduce) {
  .cr9-cer__stage,
  .cr9-cer__stage::after,
  .cr9-cer__spark,
  .cr9-cer__lock svg { animation: none !important; }
  .cr9-cer--anim .cr9-cer__stage--ring::after { border-color: var(--cr9-gold); }
}

/* ── Your / Browse sections + cards ───────────────────────────────── */
.challenges .ch-sec + .ch-sec { margin-top: var(--space-4); }
.ch-challenge + .ch-challenge { margin-top: var(--space-4); }
.ch-challenge--moving { opacity: 0; transform: translateY(-8px); }
@media (prefers-reduced-motion: no-preference) {
  .ch-challenge--moving { transition: opacity var(--dur-base) var(--ease-standard), transform var(--dur-base) var(--ease-standard); }
}
.ch-join { margin-top: var(--space-3); padding-top: var(--space-3); border-top: 1px solid var(--line); }
.ch-join__btn { width: 100%; padding: 12px 16px; font-size: 13px; }
.ch-join__note { margin: 0 0 var(--space-2); font-size: 0.8rem; }
.ch-leave { margin-top: var(--space-3); width: auto; padding: 8px 14px; font-size: 12px; color: var(--muted); }

/* ELKE-CHALLENGE-HEEFT-EEN-EIGEN-PAGINA — on /challenges/:id the full list is
   demoted to one small doorway at the bottom; it must never compete with the
   challenge you actually opened. */
.ch-browse-entry { margin-top: var(--space-4); text-align: center; }
.ch-browse-entry__btn { width: auto; padding: 8px 16px; font-size: 12px; color: var(--muted); }

/* ── Compact browse rows ──────────────────────────────────────────── */
.ch-browse-row { border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); overflow: hidden; }
.ch-browse-row + .ch-browse-row { margin-top: var(--space-2); }
.ch-browse-row__head {
  display: flex; align-items: center; gap: var(--space-2);
  width: 100%; padding: var(--space-2) var(--space-3);
  background: none; border: none; text-align: left; cursor: pointer; color: var(--ink);
}
.ch-browse-row__thumb { width: 40px; height: 40px; border-radius: var(--radius-sm); object-fit: cover; flex-shrink: 0; }
.ch-browse-row__main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.ch-browse-row__title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ch-browse-row__meta { color: var(--muted); font-size: 0.8rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ch-browse-row__head svg { color: var(--muted); flex-shrink: 0; transition: transform var(--dur-base) var(--ease-standard); }
.ch-browse-row.is-open .ch-browse-row__head svg { transform: rotate(180deg); }
.ch-browse-row__detail { padding: var(--space-3); border-top: 1px solid var(--line); }
@media (prefers-reduced-motion: reduce) {
  .ch-browse-row__head svg { transition: none; }
}

/* ── MEMBER-CLASSES-PAGE-AVAILABLE-LESSONS — entry card + /classes page ── */
/* Entry card on /challenges (same card family as ch-* surfaces). */
.cls-entry {
  display: flex; align-items: center; gap: var(--space-3); width: 100%; text-align: left;
  padding: var(--space-3); border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink); cursor: pointer;
}
.cls-entry__icon { display: inline-flex; color: var(--accent); flex-shrink: 0; }
.cls-entry__main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.cls-entry__title { font-weight: 600; color: var(--ink); }
.cls-entry__teaser { color: var(--muted); font-size: 0.85rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cls-entry__chevron { display: inline-flex; color: var(--muted); flex-shrink: 0; }

/* /classes page. */
.cls-header { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-3); }
.cls-back {
  display: inline-flex; align-items: center; justify-content: center; padding: 4px;
  border: none; background: none; color: var(--ink); cursor: pointer;
}
.cls-header__title { margin: 0; font-size: 1.5rem; color: var(--ink); }
.cls-body { display: flex; flex-direction: column; }
.cls-group {
  margin: var(--space-4) 0 var(--space-2); font-size: 0.8rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted);
}
.cls-group:first-child { margin-top: 0; }
.cls-muted { color: var(--muted); }
.cls-error { color: var(--danger); }
.cls-need { color: var(--danger); font-size: 0.85rem; }
.cls-ok-text { color: var(--ok); font-size: 0.85rem; }

.cls-class {
  padding: var(--space-3); border: 1px solid var(--line); border-radius: var(--radius-sm);
  margin-bottom: var(--space-3); background: var(--surface);
}
.cls-class:last-child { margin-bottom: 0; }
.cls-class__head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-2); }
.cls-class__title { font-weight: 600; color: var(--ink); }
.cls-class__time { color: var(--ink); font-weight: 600; white-space: nowrap; }
.cls-class__meta { display: flex; flex-wrap: wrap; gap: var(--space-2); color: var(--muted); font-size: 0.85rem; margin-top: var(--space-1); }
.cls-class__cost { color: var(--ink); font-weight: 600; }
.cls-class__cost--free { color: var(--accent); }
.cls-class__loc { display: inline-flex; align-items: center; gap: 4px; color: var(--muted); text-decoration: none; }
.cls-class__loc:hover { color: var(--ink); text-decoration: underline; }
.cls-class__loc svg { flex: 0 0 auto; }
.cls-class__capacity { margin-top: var(--space-2); }
.cls-class__capacity-label { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--space-2); color: var(--muted); font-size: 0.8rem; margin-bottom: var(--space-1); }
.cls-bar { height: 4px; border-radius: var(--radius-pill); background: var(--line); overflow: hidden; }
.cls-bar__fill { height: 100%; border-radius: var(--radius-pill); background: color-mix(in srgb, var(--accent) 55%, var(--line)); }
/* COLOR-3 — the sRGB midpoint of the cyan accent and the near-neutral track
   line lands noticeably greyer than half-strength accent looks like it should;
   in oklch the mix keeps its chroma. Declared as an override so the sRGB line
   above stays the fallback. */
@supports (color: color-mix(in oklch, red, blue)) {
  .cls-bar__fill { background: color-mix(in oklch, var(--accent) 55%, var(--line)); }
}
.cls-class__cutoff { display: inline-flex; align-items: center; gap: 4px; color: var(--muted); font-size: 0.8rem; margin-top: var(--space-2); }
.cls-class__cutoff--urgent { color: var(--danger); }
.cls-class__actions { margin-top: var(--space-3); }
.cls-class__actions .btn { padding: 12px 16px; font-size: 13px; }

@media (prefers-reduced-motion: no-preference) {
  .cls-entry { transition: border-color var(--dur-fast) var(--ease-standard); }
  .cls-entry:hover { border-color: var(--accent); }
}

/* ── Motion (all gated on prefers-reduced-motion) ─────────────────── */
@media (prefers-reduced-motion: no-preference) {
  @keyframes ch-rise { from { transform: scaleY(0); } to { transform: scaleY(1); } }
  @keyframes ch-fade-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
  .ch-podium__bar { animation: ch-rise var(--dur-slow) var(--ease-standard) backwards; }
  .ch-podium__spot--r2 .ch-podium__bar { animation-delay: 0.1s; }
  .ch-podium__spot--r3 .ch-podium__bar { animation-delay: 0.18s; }
  .ch-lb__row { animation: ch-fade-in var(--dur-slow) var(--ease-standard) backwards; animation-delay: calc(0.25s + var(--row-i, 0) * 0.06s); }
  .ch-goal { animation: ch-fade-in var(--dur-slow) var(--ease-standard) 0.9s backwards; }
}

/* ── Submit flow ──────────────────────────────────────────────────── */
.ch-submit { margin-top: var(--space-3); padding-top: var(--space-3); border-top: 1px solid var(--line); }
.ch-submit__reveal { width: 100%; padding: 12px 16px; font-size: 13px; }
.ch-submit__btn { width: 100%; padding: 14px 16px; font-size: 14px; }
.ch-submit__form {
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--accent) 5%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--line));
  position: relative;
}
.ch-submit__label { display: block; color: var(--ink); font-size: 0.9rem; font-weight: 600; margin-bottom: var(--space-2); }
.ch-submit__inputwrap { display: flex; align-items: stretch; gap: var(--space-2); margin-top: var(--space-1); }
.ch-submit__inputwrap .ch-submit__input { flex: 1; min-width: 0; font-size: 1.05rem; padding: 12px 14px; }
.ch-submit__timesep {
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 1.4rem; font-weight: 700; color: var(--ink); padding: 0 2px;
}
.ch-submit__unit {
  display: inline-flex; align-items: center; padding: 0 14px;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--accent) 12%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--line));
  color: var(--ink); font-weight: 600; font-size: 0.9rem; white-space: nowrap;
}
.ch-submit__reqnote {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: var(--space-2); padding: 4px 10px; border-radius: var(--radius-pill);
  font-size: 0.78rem; font-weight: 600;
  color: color-mix(in srgb, var(--accent) 82%, var(--ink));
  background: color-mix(in srgb, var(--accent) 12%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--line));
}
.ch-submit__photo { display: flex; align-items: center; gap: var(--space-2); margin: var(--space-3) 0; flex-wrap: wrap; }
.ch-submit__photobtn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  width: auto; padding: 11px 16px; font-size: 13px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line));
  background: var(--surface); color: var(--ink);
}
.ch-submit__photoname { color: var(--muted); font-size: 0.85rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ch-submit__feedback { margin-top: var(--space-2); font-size: 0.85rem; }
.ch-submit__pending {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 10px 16px; border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--ok) 14%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--ok) 45%, var(--line));
  color: var(--ok); font-weight: 600; font-size: 0.9rem;
}

/* ── Submit celebration ───────────────────────────────────────────── */
.ch-celebrate {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column; align-items: center; gap: var(--space-2);
  text-align: center; padding: var(--space-3) var(--space-2);
}
.ch-celebrate__check {
  color: var(--ok);
  border-radius: var(--radius-pill);
  padding: 8px;
  background: color-mix(in srgb, var(--ok) 14%, var(--surface));
  border: 2px solid color-mix(in srgb, var(--ok) 45%, var(--line));
}
.ch-celebrate__title { color: var(--ink); font-weight: 700; font-size: 1.05rem; }
.ch-celebrate__msg { color: var(--accent); font-weight: 600; font-size: 0.9rem; }
.ch-confetti { position: absolute; inset: 0; pointer-events: none; }
.ch-confetti__bit {
  position: absolute; top: 40%; width: 8px; height: 8px; border-radius: var(--radius-sm);
  opacity: 0;
}
@media (prefers-reduced-motion: no-preference) {
  @keyframes ch-check-pop { 0% { transform: scale(0.4); opacity: 0; } 60% { transform: scale(1.12); } 100% { transform: scale(1); opacity: 1; } }
  @keyframes ch-confetti-fly {
    0% { opacity: 1; transform: translate(0, 0) rotate(0); }
    100% { opacity: 0; transform: translate(var(--dx, 0), -70px) rotate(var(--rot, 180deg)); }
  }
  @keyframes ch-check-draw { from { stroke-dashoffset: 32; } to { stroke-dashoffset: 0; } }
  .ch-celebrate__check { animation: ch-check-pop var(--dur-slow) var(--ease-standard) backwards; }
  .ch-celebrate__check polyline { stroke-dasharray: 32; animation: ch-check-draw var(--dur-slow) var(--ease-standard) 0.2s backwards; }
  .ch-celebrate__title { animation: ch-fade-in var(--dur-slow) var(--ease-standard) 0.25s backwards; }
  .ch-celebrate__msg { animation: ch-fade-in var(--dur-slow) var(--ease-standard) 0.4s backwards; }
  .ch-confetti__bit { animation: ch-confetti-fly var(--dur-slow) var(--ease-standard) forwards; }
}

/* ════════════════════════════════════════════════════════════════════════
   NUTRITION-M3-WAVE-2 — touch feedback + focus (2026-07-16).
   Builds on the WAVE-1 shape/type/scrim tokens. Purely additive: no glyph,
   colour or layout change beyond the press-overlay and keyboard focus-ring.
   ──────────────────────────────────────────────────────────────────────── */

/* (2) M3 state-layer: a currentColor ink-overlay that fades in on press.
   ~10% opacity keeps the tint within the M3 "pressed" band. Token-free —
   currentColor tracks each surface's own ink, so it works across all themes. */
.nut-meal__head,
.nut-libitem__main,
.nut-tab,
.nut-addmenu__tile,
.nut-utile { position: relative; }

.nut-meal__head::after,
.nut-libitem__main::after,
.nut-tab::after,
.nut-addmenu__tile::after,
.nut-utile::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: currentColor;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease-standard);
}
.nut-meal__head:active::after,
.nut-libitem__main:active::after,
.nut-tab:active::after,
.nut-addmenu__tile:active::after,
.nut-utile:active::after { opacity: .10; }

@media (prefers-reduced-motion: reduce) {
  .nut-meal__head::after,
  .nut-libitem__main::after,
  .nut-tab::after,
  .nut-addmenu__tile::after,
    .nut-utile::after { transition: none; }
}

/* (3) Keyboard focus-ring — one generic rule across the interactive nut- and mo-
   surfaces. Pointer taps never trigger :focus-visible, so this is invisible on
   touch and only appears for keyboard/AT users. */
.nut-meal__head:focus-visible,
.nut-meal__menu:focus-visible,
.nut-meal__caret:focus-visible,
.nut-libitem__main:focus-visible,
.nut-libitem__add:focus-visible,
.nut-tab:focus-visible,
.nut-addmenu__tile:focus-visible,
.nut-utile:focus-visible,
.nut-groc__toggle:focus-visible,
.nut-entry-act:focus-visible,
.mo-close:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ═══ IN-APP-INTAKE-WOW-FLOW-AND-SETTINGS-INTAKE-CATEGORY ═══════════════════
   The in-app twin of the public /scan funnel. Everything that draws a step —
   stage, progress bar, fields, reward beat, photo tile, scan line, count-up
   result — comes from scan-core.css, which intake-wow.js links on first open.
   This block is only the two things scan-core.css deliberately does NOT carry:
   the full-screen layer that hosts it, and the dark palette it assumes.

   The palette is scoped, not global. Sassu's two presets are Dag (light) and
   Nacht (dark); the funnel is premium-dark by design, so a Dag user would
   otherwise get lime-on-white — which fails contrast and reads as a different
   product. Re-declaring the volt rungs on .scan-layer keeps the funnel looking
   like the funnel without touching the user's chosen theme anywhere else.
   Values are copied verbatim from :root[data-theme-preset="volt"] above. */
.scan-layer {
  --bg:        #121212;
  --surface:   #1E1E1E;
  --surface-2: #1C1C1E;
  --ink:       #E8E8E8;
  --ink-2:     #B0B0B0;
  --muted:     #878787;
  --line:      #333333;
  --accent:    #ADFF00;
  --on-accent: #0A0A0A;
  --accent-subtle: rgba(173, 255, 0, 0.20);
  --danger:    #FF6B6B;
  color-scheme: dark;

  position: fixed;
  inset: 0;
  /* Above the bottom nav and any open sheet: this is a takeover, and a visible
     tab bar would invite a mid-scan tab switch that loses the picked photo. */
  z-index: 3000;
  display: flex;
  justify-content: center;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background: var(--bg);
  color: var(--ink);
  animation: scan-rise var(--dur-base) var(--ease-standard) both;
}

.scan-layer__shell {
  position: relative;
  width: 100%;
  max-width: 460px;
  min-height: 100%;
  padding: calc(env(safe-area-inset-top, 0px) + var(--space-6)) var(--space-5)
           calc(env(safe-area-inset-bottom, 0px) + var(--space-5));
  display: flex;
  flex-direction: column;
  background:
    radial-gradient(120% 60% at 50% 0%, rgba(173, 255, 0, 0.07), transparent 70%),
    var(--bg);
}

/* The only way out that is always present. Absolute rather than in-flow so it
   never competes with .scan-nav for the thumb zone at the bottom. */
.scan-layer__close {
  position: absolute;
  top: calc(env(safe-area-inset-top, 0px) + var(--space-3));
  right: var(--space-3);
  width: 40px;
  height: 40px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink-2);
  cursor: pointer;
}

.scan-layer__close:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--accent-subtle);
}

.scan-layer__close svg {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
}

@media (prefers-reduced-motion: reduce) {
  .scan-layer { animation: none !important; }
}

/* ══════════ DIARY-METRIC-DETAIL-TAP — full-screen metric detail ══════════
   Tapping a sparkline in the Diary opens this. Full-screen variant of the
   existing .mo-sheet, so the open/close transition and the reduced-motion
   guard above are inherited rather than restated. Tokens only. */

.mo-overlay--full { align-items: stretch; }
.mo-sheet--full {
  max-width: 720px;
  max-height: 100%;
  height: 100%;
  border-radius: 0;
  padding-bottom: 0;
  display: flex;
  flex-direction: column;
}

.mdx__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-2);
  border-bottom: 1px solid var(--line);
  flex: 0 0 auto;
}
.mdx__iconbtn {
  background: none;
  border: 0;
  color: var(--ink-2);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  border-radius: var(--radius-md);
}
.mdx__iconbtn:hover { color: var(--ink); }
.mdx__title {
  flex: 1 1 auto;
  font-size: var(--text-title);
  line-height: var(--lh-title);
  font-weight: 500;
  letter-spacing: 0.04em;
  margin: 0;
}
.mdx__headacts { display: flex; align-items: center; flex: 0 0 auto; }

.mdx__selectors {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  flex: 0 0 auto;
}
.mdx__metricwrap { position: relative; }
.mdx__metric {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  color: var(--ink);
  font-size: var(--text-body);
  line-height: var(--lh-body);
  padding: var(--space-2) var(--space-3);
  min-height: 36px;
  cursor: pointer;
}
.mdx__metriclist {
  position: absolute;
  z-index: 2;
  top: calc(100% + var(--space-1));
  left: 0;
  min-width: 180px;
  max-height: 260px;
  overflow-y: auto;
  margin: 0;
  padding: var(--space-1);
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--elev-2);
}
.mdx__metricopt {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  color: var(--ink);
  font-size: var(--text-body);
  line-height: var(--lh-body);
  padding: var(--space-2) var(--space-3);
  min-height: 40px;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.mdx__metricopt:hover { background: var(--surface-2, var(--bg)); }
.mdx__metricopt--on { color: var(--accent); font-weight: 600; }

.mdx__ranges { display: inline-flex; gap: 2px; }
.mdx__range {
  background: none;
  border: 0;
  color: var(--ink-2);
  font-size: var(--text-label);
  line-height: var(--lh-label);
  letter-spacing: 0.06em;
  padding: var(--space-2);
  min-width: 38px;
  min-height: 32px;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.mdx__range--on { background: var(--accent); color: var(--on-accent); font-weight: 600; }

.mdx__addrow {
  display: flex;
  gap: var(--space-2);
  padding: 0 var(--space-4) var(--space-3);
  flex: 0 0 auto;
}
.mdx__addfield {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  color: var(--ink);
  font-size: var(--text-body);
  line-height: var(--lh-body);
  padding: var(--space-2) var(--space-3);
  min-height: 44px;
  min-width: 0;
}
.mdx__addfield--date { flex: 1 1 auto; }
.mdx__addfield--val { flex: 0 1 96px; }
.mdx__addsave {
  background: var(--accent);
  color: var(--on-accent);
  border: 0;
  border-radius: var(--radius-md);
  font-size: var(--text-body);
  line-height: var(--lh-body);
  font-weight: 600;
  padding: var(--space-2) var(--space-4);
  min-height: 44px;
  cursor: pointer;
}
.mdx__addsave:disabled { opacity: 0.6; cursor: default; }

.mdx__note {
  padding: var(--space-5) var(--space-4);
  margin: 0;
  color: var(--ink-2);
  font-size: var(--text-body);
  line-height: var(--lh-body);
}
.mdx__note--err { color: var(--danger, var(--ink)); }

.mdx__kpis {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4) var(--space-4);
  flex: 0 0 auto;
}
.mdx__kpi { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.mdx__kpi-lbl {
  font-size: var(--text-label);
  line-height: var(--lh-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.mdx__kpi-val {
  font-size: var(--text-headline);
  line-height: var(--lh-headline);
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.mdx__kpi-sub {
  font-size: var(--text-label);
  line-height: var(--lh-label);
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

.mdx__chart { padding: 0 var(--space-4); flex: 0 0 auto; }
.mdx__plotrow { display: flex; gap: var(--space-2); }
.mdx__ygutter {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  font-size: var(--text-label);
  line-height: var(--lh-label);
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
  flex: 0 0 auto;
  padding: 2px 0;
}
.mdx__plot { position: relative; flex: 1 1 auto; min-width: 0; }
.mdx__svg { display: block; width: 100%; height: 168px; overflow: visible; }
.mdx__grid { stroke: var(--line); stroke-width: 1; vector-effect: non-scaling-stroke; }
.mdx__line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.mdx__dot { fill: var(--accent); }
/* Scrub: reuses .spark-cursor / .spark-cursor__dot / .spark-tip from the trend
   card. pan-y keeps a vertical swipe that starts on the chart scrolling the
   page, while horizontal movement belongs to the scrubber. */
.mdx__svg { touch-action: pan-y; cursor: ew-resize; }
.mdx__chart--scrub .spark-cursor { opacity: 0.55; }
.mdx__chart--scrub .spark-cursor__dot { opacity: 1; }
.mdx__chart--scrub .mdx__tip { opacity: 1; }
/* The trend card hangs its tip ABOVE the plot; here that would sit on top of
   the KPI row, so the label lives inside the plot instead. */
.mdx__tip { bottom: auto; top: 4px; }
.mdx__xaxis {
  display: flex;
  justify-content: space-between;
  font-size: var(--text-label);
  line-height: var(--lh-label);
  color: var(--ink-2);
  padding-top: var(--space-2);
}

.mdx__entries {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: var(--space-5) var(--space-4) var(--space-6);
}
.mdx__sec {
  font-size: var(--text-label);
  line-height: var(--lh-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-2);
  margin: 0 0 var(--space-2);
  font-weight: 500;
}
.mdx__list { list-style: none; margin: 0; padding: 0; }
.mdx__entry {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--line-2);
}
.mdx__entry-val {
  font-size: var(--text-body);
  line-height: var(--lh-body);
  color: var(--ink);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}
.mdx__entry-date {
  font-size: var(--text-body);
  line-height: var(--lh-body);
  color: var(--ink-2);
}
/* DIARY-EIGEN-METRIEK-TYPE-TEKST — a text entry wraps onto as many lines as it
   needs and the date stays put at the top: the answer is the content here, not
   a figure to line up in a column. */
.mdx__entry--text { align-items: flex-start; }
.mdx__entry-val--text {
  font-variant-numeric: normal;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  min-width: 0;
}
.mdx__entry--text .mdx__entry-date { flex: 0 0 auto; }

/* The tap affordance on the sparklines that lead here. */
.spark-mini--tappable, .trend-card--tappable { cursor: pointer; }
.spark-mini--tappable:focus-visible,
.trend-card--tappable:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ═══════════════════════════════════════════════════════════════════════════
   NUTRITION-LIQUID-GLASS-PLATE (2026-07-27)

   The Log tab gets a plate photo as WALLPAPER and every card above it becomes
   frosted glass. Two things make this survivable rather than a legibility
   disaster:

   1. The plate is pinned (sticky, zero-height wrapper) so it never scrolls with
      the content. The glass slides over a stationary picture — that is the
      whole effect, and it also means the backdrop under any given card changes
      as you scroll, so contrast must hold against the WORST pixel of the photo,
      not the average.

   2. Because of (1), --glass-tint is deliberately heavier than a designer would
      pick by eye, and secondary text on glass is lifted off --muted onto
      --glass-ink-2. --muted (#5C6B80 dark / #8593A1 light) is mid-luminance and
      therefore CANNOT reach 4.5:1 against a mid-luminance blurred photo at any
      tint — the backdrop would have to be brighter than white or darker than
      black. Lifting the text is not a polish step, it is the only fix that
      exists. tests/visual/nutrition-glass-plate.spec.js samples the composited
      pixels and fails the build if any run drops under 4.5:1.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  --glass-tint:   rgba(0, 0, 0, 0.62);
  --glass-tint-2: rgba(0, 0, 0, 0.52);   /* secondary panels: less to hide */
  --glass-ink:    #EAF1F7;
  --glass-ink-2:  #BFCBD8;
  --glass-edge:   rgba(255, 255, 255, 0.14);
  --glass-hair:   rgba(255, 255, 255, 0.10);
}
html[data-theme-mode="light"] {
  --glass-tint:   rgba(255, 255, 255, 0.80);
  --glass-tint-2: rgba(255, 255, 255, 0.72);
  --glass-ink:    #0B1320;
  --glass-ink-2:  #333F4C;
  --glass-edge:   rgba(255, 255, 255, 0.70);
  --glass-hair:   rgba(11, 19, 32, 0.10);
}

/* ── The plate ───────────────────────────────────────────────────────────── */
/* Zero-height so it claims no layout: the cards sit exactly where they sat
   before this ship. Sticky (not fixed) on purpose — sticky is measured against
   whichever ancestor actually scrolls, so it survives the app shell moving its
   scroll container without a JS probe. */
.nut-plate-wrap {
  position: sticky;
  top: min(423px, 50vh);
  height: 0;
  z-index: 0;
  pointer-events: none;
  overflow: visible;
}
.nut-plate {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  /* 390x260 = 1.5, the source PNG's own 480x320 ratio. Sized against the
     VIEWPORT, not the padded mount, so the plate is the full 390 the mocks were
     drawn at rather than 390-minus-gutters. */
  width: min(390px, 100vw);
  height: 260px;
  border-radius: 28px;
  overflow: hidden;
}
/* LAADTIJD-VOEDING-OPENT-ZONDER-OMWEG — zie de tweelingregel bij
   .nut-empty__hero: de <picture> mag geen eigen doos krijgen, anders staat er
   een inline-element van nul hoogte tussen .nut-plate en zijn afbeelding en
   vult `height: 100%` hieronder niets meer. */
.nut-plate > picture { display: contents; }
.nut-plate__img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
/* Feather the edges so the plate reads as light in the page rather than as a
   pasted rectangle with corners. */
.nut-plate::after {
  content: '';
  position: absolute; inset: 0;
  background: radial-gradient(ellipse at center, transparent 38%, var(--bg) 78%);
}

/* Everything the user reads must paint ABOVE the wallpaper. */
/* .nut-fab is excluded BY NAME: this rule and `.nut-fab` carry identical
   specificity (one class each) and this one is later in the file, so `position:
   relative` silently beat `position: fixed` and dropped the "+" into the flow at
   the bottom-left of the mount. Its own z-index (40) already clears the
   wallpaper, so it needs nothing from this rule. */
.nut-mount > *:not(.nut-plate-wrap):not(.nut-fab) { position: relative; z-index: 1; }

/* ── The glass ───────────────────────────────────────────────────────────── */
/* .nut-ns and .nut-meal are the panels the eye lands on, so they get the full
   24px blur. The weekstrip/header and the hydration card drop to 16px: the
   performance guard for this ship is eight simultaneous blurred panels, and a
   day with several meals blows past that on the meal rows alone. Halving the
   radius on the two supporting panels keeps the composite cheap without the
   user being able to name what changed. */
.nut-ns,
.nut-meal {
  background: var(--glass-tint);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
  backdrop-filter: blur(24px) saturate(160%);
  border: 1px solid var(--glass-edge);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.14) inset;
}
/* A waiting meal slot — and the repeat offer that hangs under it as one panel
   (.nut-slotgrp) — is not logged food, so it takes the SECOND step of glass:
   lighter than a real meal card, with the photo still showing through. Before
   this both were missed by the glass sprint: the slot stayed opaque --surface
   (a dense plate between frosted cards) and the offer had no background at all,
   which put its text straight on the plate photo, unreadable. */
.nut-mealslot,
.nut-slotgrp,
.hydration-card,
.nut-bal {
  background: var(--glass-tint-2);
  -webkit-backdrop-filter: blur(16px) saturate(150%);
  backdrop-filter: blur(16px) saturate(150%);
  border: 1px solid var(--glass-edge);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.14) inset;
}
/* The date strip is NAVIGATION, not a card — it never had a border-radius, so
   as glass it read as a rectangular slab among rounded cards. It also sits
   ABOVE the plate's sticky top (min(423px, 50vh)), so it is over plain --bg and
   needs no over-photo tint at all: dropping the glass entirely both fixes the
   corners and keeps --ink/--ink-2 at their tuned contrast. */
.nut-head {
  background: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  border: 0;
  box-shadow: none;
}
/* The north-star card is the one panel that is on screen for the entire scroll,
   so it is the only one worth promoting. Promoting the meal rows too is what
   turns this effect into jank on a mid-range phone. */
.nut-ns { will-change: backdrop-filter; }

/* Meal rows are glass containers; the entry rows inside them must NOT stack a
   second blur on top (blur-on-blur is both expensive and muddy) — they simply
   let their parent's glass show through. */
.nut-meal .nut-entry { background: transparent; }
.nut-meal__head { background: transparent; }
/* Same rule for the rows inside a slot panel: the wrapper carries the one sheet
   of glass, they only let it through. */
.nut-slotgrp .nut-mealslot,
.nut-slotgrp .nut-slotsug,
.nut-slotgrp .nut-slotadd {
  background: transparent; border: 0;
  -webkit-backdrop-filter: none; backdrop-filter: none;
  box-shadow: none;
}
.nut-slotgrp .nut-mealslot:hover { border: 0; }

/* Text on glass. Anything that was --muted is lifted; see the header note for
   why this is mandatory rather than cosmetic. These are the LEAF elements that
   actually carry a `color` — setting it on the container does nothing, because
   every one of these sets its own. */
.nut-ns__hero-label,
.nut-ns__hero-goal,
.nut-ns__hero-micro,
.nut-ns__band-cal-l,
.nut-ns__calnum,
.nut-mac__lbl,
.nut-ns__fiber-lbl,
.nut-meal__name,
.nut-entry__name,
.nut-mealslot__name,
/* Measured, not chosen: over the bright middle of the plate the slot hint and
   the repeat items sat at 3.70:1 and 4.05:1 on --glass-ink-2 in Nacht, and the
   accent title at 4.44:1 in Dag. All three lift to --glass-ink — the same call
   .nut-addmeal already makes, which is why that button is not accent-coloured
   over glass either. */
.nut-mealslot__hint,
.nut-slotsug__ico,
.nut-slotsug__ttl,
.nut-slotsug__names,
/* The add-food row is the same accent text on the same glass as the repeat
   title above it, so it takes the same measured lift — otherwise it ships at
   the 4.44:1 the repeat title was caught at. */
.nut-slotadd__ico,
.nut-slotadd__ttl,
.hydration-btn { color: var(--glass-ink); }

.nut-ns__band-cal-r,
.nut-ns__calgoal,
.nut-ns__rem,
.nut-ns__rem--met,
.nut-ns__fiber-val,
.nut-mac__val,
.nut-meal__kcal,
.nut-meal__caret,
.nut-meal__menu,
.nut-entry__meta,
.nut-mealslot__cat,
.nut-mealslot__chev,
.nut-entry__kcal { color: var(--glass-ink-2); }

/* Hairlines inside glass have to come off --line (a solid token tuned for a
   solid surface) or they vanish over a bright patch of the photo. */
.nut-meal .nut-entry-wrap:not(:first-child)::before { background: var(--glass-hair); }
.nut-slotgrp .nut-slotsug::before,
.nut-slotgrp .nut-slotadd::before { background: var(--glass-hair); }

/* The dashed add-meal button reads as a hole in the glass, which is right — but
   its border needs the glass edge token to stay visible over the photo. */
.nut-addmeal {
  border-color: var(--glass-edge);
  color: var(--glass-ink);
  background: var(--glass-tint-2);
  -webkit-backdrop-filter: blur(16px) saturate(150%);
  backdrop-filter: blur(16px) saturate(150%);
}

/* ── Swipe tray over glass ───────────────────────────────────────────────── */
/* Before this ship .nut-entry was opaque and physically covered the action
   tray parked behind it. Glass does not cover anything, so the tray has to be
   hidden until a swipe has actually locked — otherwise the delete button is a
   permanent red ghost on the right of every row. renderEntry() adds
   .is-swiping on gesture lock and removes it on gesture end; .is-open keeps it
   up once the tray is parked open. */
.nut-entry-actions { opacity: 0; pointer-events: none; transition: opacity var(--dur-fast, 120ms) linear; }
.nut-entry-wrap.is-swiping .nut-entry-actions,
.nut-entry-wrap.is-open .nut-entry-actions { opacity: 1; pointer-events: auto; }
/* While the tray is showing, the sliding row must be opaque or the buttons read
   straight through the glass it is sliding over. */
.nut-entry-wrap.is-swiping .nut-entry,
.nut-entry-wrap.is-open .nut-entry { background: var(--surface); }

@media (prefers-reduced-transparency: reduce) {
  /* .nut-head is deliberately absent: it is transparent in BOTH modes now, and
     listing it here would hand the strip back its rectangular slab. */
  .nut-ns, .nut-meal, .hydration-card, .nut-addmeal, .nut-mealslot, .nut-slotgrp {
    -webkit-backdrop-filter: none; backdrop-filter: none; background: var(--surface);
  }
  .nut-plate-wrap { display: none; }
}

/* ══════ DAYCYCLE-HELP-BUTTON-OPENS-EXPLAINER-SHEET ══════
   Explainer body for the "?" on the check-in day-cycle strip. Tokens only, so it
   inherits Dag/Nacht; the sheet shell itself is the shared .mo-* bottom-sheet. */
.dc-explain__p {
  margin: 0 0 var(--space-4);
  color: var(--ink-2);
  font-size: 14px;
  line-height: 1.55;
}
.dc-explain__actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-5);
}
.dc-explain__actions .btn { width: 100%; }

/* ── NUTRITION-LOW-VISION-BIG-BUTTON-SURFACE ────────────────────────────────
   The Nutrition tab collapses to a few giant, vertically stacked buttons when
   users.pref_low_vision is on. Deliberately NOT a grid: a dense grid of tiles
   is the pattern senior-launcher research says fails. Every colour here is a
   theme token, so the two high-contrast presets (Dag / Nacht) are the only
   palettes this mode ever uses. Sub-text uses --ink, never --ink-2: --ink-2
   measures 5.1:1 (nacht) / 5.5:1 (dag) against --surface and misses the 7:1
   bar this mode holds itself to, while --ink clears 14.5:1 / 18.6:1. */
html[data-low-vision="1"] .nut-lv {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
html[data-low-vision="1"] .nut-lv__readout {
  padding: var(--space-4);
  border-radius: var(--radius-lg);
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--ink);
  text-align: center;
}
html[data-low-vision="1"] .nut-lv__kcal {
  font-size: 40px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.01em;
}
html[data-low-vision="1"] .nut-lv__sub {
  font-size: 18px;
  line-height: 1.45;
  letter-spacing: 0.04em;
  color: var(--ink);
}
html[data-low-vision="1"] .nut-lv__title {
  margin: 6px 0 0;
  font-size: 24px;
  font-weight: 600;
  line-height: 1.45;
  letter-spacing: 0.04em;
  color: var(--ink);
}
html[data-low-vision="1"] .nut-lv__btn,
html[data-low-vision="1"] .nut-lv__back {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 14px;
  width: 100%;
  min-height: 72px;
  padding: 14px 18px;
  border-radius: var(--radius-lg);
  border: 2px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  font-family: inherit;
  font-size: 24px;
  font-weight: 600;
  line-height: 1.45;
  letter-spacing: 0.04em;
  text-align: left;
  cursor: pointer;
}
html[data-low-vision="1"] .nut-lv__btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
/* The escape hatch is outlined, never accent-filled: it must be findable
   without competing with the action the user came here to take. */
html[data-low-vision="1"] .nut-lv__btn--ghost {
  background: transparent;
  border-color: var(--ink);
  color: var(--ink);
}
html[data-low-vision="1"] .nut-lv__back {
  min-height: 56px;
  font-size: 20px;
  align-self: flex-start;
  width: auto;
  padding: 10px 18px;
}
html[data-low-vision="1"] .nut-lv__back .nut-ic svg { transform: rotate(90deg); }
html[data-low-vision="1"] .nut-lv__label { flex: 1 1 auto; }
html[data-low-vision="1"] .nut-lv__btn:active,
html[data-low-vision="1"] .nut-lv__back:active {
  outline: 3px solid var(--ink);
  outline-offset: -3px;
  transform: scale(0.985);
}
/* On the accent fill the ink outline vanishes in Dag (accent is near-black navy
   and so is --ink) — the pressed ring has to ride --on-accent there. */
html[data-low-vision="1"] .nut-lv__btn--primary:active { outline-color: var(--on-accent); }
@media (prefers-reduced-motion: reduce) {
  html[data-low-vision="1"] .nut-lv__btn:active,
  html[data-low-vision="1"] .nut-lv__back:active { transform: none; }
}

/* ── COACH-KLANT-GEEFT-ZELF-TOESTEMMING ───────────────────────────────────────
   The client's consent gate. It sits ABOVE the ordinary settings cards and has
   to read like a decision, not a row: paragraphs with air around them, the two
   answers side by side and equally reachable under a thumb. Deliberately no
   pre-selected button and no visual weight steering toward yes — a consent
   screen that nudges is not consent. */
.set-group--consent .set-card--consent {
  padding: var(--space-4);
  border: 1px solid var(--accent, var(--line));
}
/* set-card draws a hairline between its children. Inside a consent card the
   children are the paragraphs of one single sentence-block, so those rules
   would slice the legal text into rows. */
.set-card--consent > * + *::before { content: none; }
.set-consent__text {
  margin: 0 0 var(--space-3) 0;
  line-height: 1.55;
  color: var(--ink);
}
.set-consent__excluded,
.set-consent__meta {
  margin: 0 0 var(--space-3) 0;
  color: var(--muted);
  font-size: 0.875rem;
}
.set-consent__meta { margin-bottom: var(--space-4); }
.set-row--consent {
  display: flex;
  gap: var(--space-3);
}
.set-row--consent .btn { flex: 1 1 0; min-height: 44px; }

/* VOEDING-TREDE-FOTO-ZONDER-CIJFERS — the meal-photo strip on measure form 4.
   Card chrome mirrors .hydration-card; a horizontally scrolling row of square
   thumbnails ending in the add tile. No progress bar, no counter, no goal:
   there is nothing here to complete and nothing to lose. */
.mealphoto-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5); margin-bottom: var(--space-4);
  display: flex; flex-direction: column; gap: var(--space-3);
}
.mealphoto-card > header { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
.mealphoto-card h3 { margin: 0; font-size: var(--text-title); line-height: var(--lh-title); font-weight: 600; color: var(--ink); }
.mealphoto-strip {
  display: flex; gap: var(--space-2); overflow-x: auto; padding-bottom: 2px;
  scrollbar-width: thin;
}
.mealphoto-cell { position: relative; flex: 0 0 88px; }
.mealphoto-cell__img {
  width: 88px; height: 88px; object-fit: cover; display: block;
  border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-muted);
}
.mealphoto-cell__del {
  position: absolute; top: 4px; right: 4px; width: 24px; height: 24px;
  display: inline-flex; align-items: center; justify-content: center;
  border: none; border-radius: var(--radius-pill); cursor: pointer;
  background: rgba(0, 0, 0, .55); color: #fff; padding: 0;
}
.mealphoto-cell__del svg { width: 14px; height: 14px; }
.mealphoto-add {
  flex: 0 0 88px; width: 88px; height: 88px; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  border: 1px dashed var(--line); border-radius: var(--radius-md);
  background: var(--surface-muted); color: var(--ink-2);
  font-size: 0.72rem; font-weight: 600; text-align: center; padding: var(--space-2);
}
.mealphoto-add:hover { border-color: var(--accent); color: var(--accent); }
.mealphoto-add svg { width: 20px; height: 20px; }
.mealphoto-consent { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); flex-wrap: wrap; }
.mealphoto-consent__txt { margin: 0; font-size: var(--text-body-sm); color: var(--ink-2); }
.mealphoto-consent__btn {
  padding: var(--space-2) var(--space-4); cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  background: var(--surface-muted); color: var(--ink);
  font-size: 0.82rem; font-weight: 600; white-space: nowrap;
}
/* Accent button text rides --on-accent, never a hardcoded white. */
.mealphoto-consent__btn.is-primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }

/* DE-KNOPPEN-VOLGEN-DE-TELLER — de kostenregel onder een gemeten AI-knop. Geen
   badge en geen slot: een prijs hoort te lezen als informatie, niet als een
   waarschuwing, dus gedempte inkt op één regel die nooit afbreekt. Leeg is echt
   leeg — een span zonder tekst neemt geen ruimte in. */
.fz-ai-cost {
  display: block;
  font-size: 11px; line-height: 1.3; font-weight: 500;
  color: var(--ink-2); white-space: nowrap;
}
.fz-ai-cost:empty { display: none; }
.mood-ai-chat-btn__cost { margin-top: 2px; }

/* ── AFSPRAAK-DE-COACHWEEK-ALS-VRIJ-EN-BEZET — de vrij/bezet-kiezer ─────────
   Het raster-idioom van de coach-agenda, maar kaal: tijd verticaal, één kolom
   per dag, een werkvenster als lichte band en bezet als effen vlak. Kolommen
   van 74px zodat het woord "Bezet" er heel in past; op 390 breed staan er zo
   vier in beeld en schuift de rest opzij. */
.cwp { display: flex; flex-direction: column; gap: var(--space-3); }
.cwp__head { display: flex; flex-direction: column; gap: 2px; }
.cwp__title { margin: 0; font-size: 1.05rem; font-weight: 700; color: var(--ink); }
.cwp__sub { margin: 0; font-size: 0.78rem; color: var(--ink-2); }

.cwp__legend { display: flex; gap: var(--space-4); align-items: center;
  font-size: 0.75rem; color: var(--ink-2); }
.cwp__leg { display: inline-flex; align-items: center; gap: 6px; }
.cwp__swatch { width: 14px; height: 14px; border-radius: var(--radius-sm); display: inline-block; }
.cwp__swatch--free { background: color-mix(in srgb, var(--accent) 18%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent); }
.cwp__swatch--busy { background: color-mix(in srgb, var(--ink) 16%, var(--surface));
  border: 1px solid var(--line); }

.cwp__scroll { overflow-x: auto; overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch; border: 1px solid var(--line);
  border-radius: var(--radius-lg); background: var(--surface); }
.cwp__grid { position: relative; display: flex; align-items: flex-start;
  padding: 26px 8px 12px 0; min-width: min-content; }

.cwp__axis { position: relative; flex: 0 0 46px; align-self: stretch;
  position: sticky; left: 0; z-index: 2; background: var(--surface); }
.cwp__tick { position: absolute; right: 6px; transform: translateY(-50%);
  font-size: 0.68rem; color: var(--ink-2); font-variant-numeric: tabular-nums; }

.cwp__col { flex: 0 0 74px; position: relative; align-self: stretch;
  border-left: 1px solid var(--line); }
.cwp__daylabel { position: absolute; top: -22px; left: 0; right: 0; text-align: center;
  font-size: 0.72rem; font-weight: 600; color: var(--ink-2); text-transform: capitalize; }
.cwp__lane { position: relative; height: 100%; margin: 0 3px; }

.cwp__win { position: absolute; left: 0; right: 0; padding: 0; cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--accent) 14%, transparent); }
.cwp__win:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.cwp__busy { position: absolute; left: 0; right: 0; display: flex;
  align-items: center; justify-content: center; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--ink) 16%, var(--surface)); }
.cwp__busy__label { font-size: 0.68rem; font-weight: 600; color: var(--ink-2);
  letter-spacing: .01em; }

.cwp__empty { margin: 0; font-size: 0.82rem; color: var(--ink-2); }

/* AFSPRAAK-VERZETTEN-IS-EEN-VOORSTEL — de kiezer in VOORSTEL-modus, en wat de
   agenda van een lopend voorstel laat zien.

   De regel "Tot Jayy bevestigt, staat je sessie nog op di 18:00" krijgt een
   eigen regel op VOLLE breedte en niet een plekje naast de tijd: op 390 breed
   is .cal-coach__row al verdeeld tussen tijd, titel en chip, en alles wat daar
   nog bij komt knijpt de titel weg (de les van de bronchip, 2026-09-06). */
.cal-coach__reqline {
  flex: 1 0 100%;
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-2);
}
.cal-coach__chip--wait {
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.cal-sheet__wait {
  margin: var(--space-3) 0 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink);
}

/* De derde knop in het sessieblad. --on-accent en niet 'wit': op een
   accentkleur die van thema verandert is wit een gok. */
.ui-dlg__btn--accent {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

/* Het blad waarin het weekraster past. Vol scherm op de telefoon, want het
   raster is breder dan een dialoogje en scrollt zelf al horizontaal. */
.cwp-dlg {
  position: relative;
  width: min(680px, 96vw);
  max-height: 92vh;
  overflow-y: auto;
}
.cwp-dlg__close {
  position: absolute;
  top: 6px;
  right: 8px;
  z-index: 3;
  border: 0;
  background: transparent;
  font-size: 24px;
  line-height: 1;
  color: var(--ink-2);
  cursor: pointer;
}
.cwp__loading { margin: 0; font-size: 0.82rem; color: var(--ink-2); }

.cwp-foot { display: flex; flex-direction: column; gap: var(--space-2);
  margin-top: var(--space-3); }
.cwp-foot__chips { display: flex; flex-wrap: wrap; gap: 6px; }
/* Een chip IS de weghaal-knop: er is niets anders dat je met een gekozen moment
   wilt doen, dus een tweede kruisje ernaast zou alleen ruimte kosten. */
.cwp-chip {
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--ink);
  border-radius: 999px;
  padding: 4px 10px;
  font-size: 0.78rem;
  cursor: pointer;
}
.cwp-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cwp-foot__hint { margin: 0; font-size: 0.72rem; color: var(--ink-2); }
.cwp-foot__note {
  width: 100%;
  resize: vertical;
  padding: 8px 10px;
  font: inherit;
  font-size: 0.85rem;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}
.cwp-foot__send {
  padding: 12px;
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
  cursor: pointer;
}
.cwp-foot__send:disabled { opacity: .45; cursor: default; }

/* ── AFSPRAAK-RUILEN-MET-EEN-ANDERE-KLANT ──
   De onderrand die verschijnt als er op een BEZET blok van gelijke duur is
   getikt. Bewust geen tweede dialoog: de klant staat al in de week te kijken,
   en hem daaruit tillen om één zin te bevestigen is een scherm te veel. */
.cwp-swap {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid var(--line);
}
.cwp-swap__h { margin: 0; font-size: 0.9rem; font-weight: 600; color: var(--ink); }
.cwp-swap__line { margin: 0; font-size: 0.82rem; color: var(--ink); }
.cwp-swap__lbl { font-size: 0.72rem; color: var(--ink-2); }
.cwp-swap__pick {
  width: 100%;
  padding: 8px 10px;
  font: inherit;
  font-size: 0.85rem;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}

/* De kaart van klant B, bovenaan de agenda. Twee tijden, een regeltje en twee
   knoppen — en geen enkel element dat naar een persoon wijst. */
.cal-swapreqs { display: flex; flex-direction: column; gap: var(--space-2); }
.swapreq { display: flex; flex-direction: column; gap: 6px; padding: var(--space-3); }
.swapreq__ask { margin: 0; font-size: 0.88rem; font-weight: 600; color: var(--ink); }
.swapreq__msg { margin: 0; font-size: 0.82rem; color: var(--ink); font-style: italic; }
/* De zin die de hele kaart draagt: tot de coach tekent verandert er niets. Hij
   staat er altijd, ook nadat B ja heeft gezegd. */
.swapreq__rule { margin: 0; font-size: 0.72rem; color: var(--ink-2); }
.swapreq__row { display: flex; gap: var(--space-2); margin-top: 4px; }
.swapreq__yes, .swapreq__no {
  flex: 1 1 0;
  padding: 10px;
  border-radius: var(--radius-sm);
  font-weight: 600;
  cursor: pointer;
}
.swapreq__yes {
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--on-accent);
}
.swapreq__no {
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
}
.swapreq__yes:disabled, .swapreq__no:disabled { opacity: .45; cursor: default; }

/* Een aantikbaar bezet-blok in de kiezer ziet er EXACT hetzelfde uit als een
   blok dat dat niet is — geen rand, geen cursor-verschil, geen hover. Dat is
   geen slordigheid maar de kern van §5: elk zichtbaar verschil zou verraden
   welk blok een solo-klant is die ruilen aan heeft staan. Alleen wat de muis
   niet ziet mag anders zijn. */
button.cwp__busy {
  appearance: none;
  -webkit-appearance: none;
  font: inherit;
  text-align: inherit;
  padding: 0;
  cursor: default;
}
button.cwp__busy:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── PARTNER-DEEL-JE-VOORTGANG-MET-EEN-PROGRAMMA ────────────────────────────
   Het toestemmingsblok. Eén opmaak voor twee plekken: de eenmalige inline
   kaart bovenaan het Dagboek en de Instellingen-sectie. Bewust GEEN overlay —
   zelfde redenering als de zachte-start-kaart: een toestemmingsvraag is geen
   noodmelding, en een scrim zou dat wel zeggen. */
.shrc { display: flex; flex-direction: column; gap: var(--space-3); }
.shrc__title {
  margin: 0; font-size: var(--text-title); line-height: var(--lh-title);
  font-weight: 700; color: var(--ink);
}
.shrc__intro { margin: 0; font-size: var(--text-body-sm); line-height: 1.45; color: var(--ink-2); }
/* De schakelaarregel: de zin mag lang zijn en moet volledig leesbaar blijven,
   dus hij wikkelt en de knop blijft bovenaan uitgelijnd in plaats van in het
   midden van drie regels te zweven. */
.shrc__row { align-items: flex-start; gap: var(--space-3); }
.shrc__row .set-row__label { white-space: normal; line-height: 1.4; }
/* De nooit-regel is de zin waar mensen op scannen. Hij staat daarom apart,
   met een rustige rand die hem van de schakelaars scheidt zonder te alarmeren. */
.shrc__never {
  margin: 0; padding: var(--space-3);
  background: var(--surface-muted); border-radius: var(--radius-md);
  font-size: var(--text-body-sm); line-height: 1.45; color: var(--ink);
}
.shrc__acts { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }
.shrc__link {
  padding: 0; cursor: pointer; background: none; border: 0;
  color: var(--accent); font-size: 0.95rem; font-weight: 600; text-align: left;
}
.shrc__stop {
  padding: var(--space-2) var(--space-4); cursor: pointer;
  background: var(--surface-muted); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  font-size: 0.95rem; font-weight: 600;
}
.shrc__stop[disabled] { opacity: 0.6; cursor: default; }
.shrc__preview {
  display: flex; flex-direction: column; gap: var(--space-3);
  padding: var(--space-3); border: 1px solid var(--line); border-radius: var(--radius-md);
}
.shrc__prevhint { margin: 0; font-size: var(--text-body-sm); line-height: 1.4; color: var(--ink-2); }
.shrc__prevblk { display: flex; flex-direction: column; gap: 4px; }
.shrc__prevh { margin: 0; font-size: 0.95rem; font-weight: 700; color: var(--ink); }
.shrc__prevstate { margin: 0; font-size: var(--text-body-sm); color: var(--ink-2); }
.shrc__prevlist { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 2px; }
.shrc__prevrow { display: flex; justify-content: space-between; gap: var(--space-3); font-size: 0.95rem; }
.shrc__prevlabel { color: var(--ink-2); }
.shrc__prevval { color: var(--ink); font-weight: 600; }
.shrc__meta { margin: 0; font-size: var(--text-body-sm); color: var(--ink-2); }
.shrc__draft { margin: 0; font-size: var(--text-body-sm); color: var(--ink-2); font-style: italic; }

.shrc-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5); margin-bottom: var(--space-4);
  display: flex; flex-direction: column; gap: var(--space-3);
}
.shrc-card__done {
  align-self: flex-start; padding: var(--space-3) var(--space-5); cursor: pointer;
  background: var(--accent); color: var(--on-accent);
  border: 1px solid var(--accent); border-radius: var(--radius-md);
  font-size: 0.95rem; font-weight: 600;
}
.shrc-card__done[disabled] { opacity: 0.6; cursor: default; }
.shrc-card__note { margin: 0; font-size: var(--text-body-sm); color: var(--ink-2); }

/* ───────── DAGRITME-JE-ZIET-JE-EIGEN-LAATSTE-28-DAGEN — /day-cycle/history ─────────
   Read-only look-back: a grid of days and one strip of dots per step. No score,
   no streak, no red. The dots are CSS circles (never emoji) and the grid is a
   fixed 7 columns so every column holds the same weekday across the four rows. */
.fz-day-cycle-page__hist {
  background: transparent; border: 0; padding: 6px; color: var(--ink);
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; min-width: 24px; min-height: 24px;
  border-radius: var(--radius-md);
  transition: background var(--dur-fast) var(--ease-standard);
}
.fz-day-cycle-page__hist:hover { background: var(--line-2); }
.fz-day-cycle-page__hist svg { width: 20px; height: 20px; }

/* ───────── DAGRITME-DE-LOSSE-DEUREN-STAAN-ACHTER-EEN-MENU — het ⋮ en zijn blad ─────────
   De ⋮-knop is de tweelingbroer van de historieknop ernaast: zelfde maat, zelfde
   raakvlak, zelfde hover. Twee knoppen die hetzelfde gewicht hebben moeten er ook
   hetzelfde uitzien, anders leest de zwaarste als de belangrijkste. */
.fz-day-cycle-page__more {
  background: transparent; border: 0; padding: 6px; color: var(--ink);
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; min-width: 24px; min-height: 24px;
  border-radius: var(--radius-md);
  transition: background var(--dur-fast) var(--ease-standard);
}
.fz-day-cycle-page__more:hover { background: var(--line-2); }
.fz-day-cycle-page__more svg { width: 20px; height: 20px; }

/* Het blad zelf leunt op .fz-day-cycle-sheet (achtergrond, kaart, kop, sluitkruis).
   Alleen de regels zijn nieuw: gelijkwaardige deuren, dus geen accentknop
   tussen hen in — wie ze even zwaar aanbiedt mag er niet één laten schreeuwen. */
.fz-dc-doors__body { gap: 4px; padding: 8px 8px 16px; }
.fz-dc-doors__row {
  display: grid;
  grid-template-columns: 34px 1fr 18px;
  align-items: center;
  gap: 12px;
  width: 100%;
  background: transparent;
  border: 0;
  border-radius: var(--radius-md);
  padding: 10px;
  color: var(--ink);
  font: inherit;
  font-size: var(--text-body);
  text-align: left;
  cursor: pointer;
  /* 48px raakvlak ook als de regel één tekstregel draagt — dit blad is voor een
     duim, niet voor een muis. */
  min-height: 48px;
  transition: background var(--dur-fast) var(--ease-standard);
}
.fz-dc-doors__row:hover,
.fz-dc-doors__row:active { background: var(--line-2); }
.fz-dc-doors__row:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Het vlakje links draagt het icoon in de accentkleur op een tint daarvan —
   dezelfde taal als het soortvlak van een dagritme-rij. */
.fz-dc-doors__ico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent);
}
/* DAGRITME-ELKE-ZIN-ZEGT-WAT-JE-DOET — de middenkolom draagt nu één of TWEE
   regels: de naam, en eronder desnoods een grijze voorbeeldregel. Dat is een
   kolom in de kolom; het raster van de rij zelf verandert niet mee. */
.fz-dc-doors__mid {
  min-width: 0;
  display: flex; flex-direction: column; gap: 2px;
}
.fz-dc-doors__lbl { min-width: 0; }
.fz-dc-doors__why {
  min-width: 0;
  color: var(--ink-2);
  font-size: var(--text-caption);
  line-height: 1.35;
}
.fz-dc-doors__go { display: inline-flex; color: var(--ink-2); }
@media (prefers-reduced-motion: reduce) {
  .fz-day-cycle-page__more,
  .fz-dc-doors__row { transition: none; }
}

.fz-dch__grid {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px;
  /* The page body is full-bleed on purpose (the player's own blocks carry their
     inset), so the grid and the strips carry the same 16px the sleep block does
     — without it the outer day cells sit flush against both screen edges. */
  padding: 0 16px; margin: 0 0 var(--space-5);
}
.fz-dch__cell {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 6px 2px; border-radius: var(--radius-md);
  background: var(--surface); border: 1px solid var(--line);
}
.fz-dch__cell--part { background: var(--accent-subtle); border-color: var(--accent-subtle); }
.fz-dch__cell--full { background: var(--accent); border-color: var(--accent); }
.fz-dch__cell--full .fz-dch__cell-day,
.fz-dch__cell--full .fz-dch__cell-ratio { color: var(--on-accent); }
.fz-dch__cell--none { background: transparent; }
.fz-dch__cell--none .fz-dch__cell-day,
.fz-dch__cell--none .fz-dch__cell-ratio { color: var(--muted); }
.fz-dch__cell-day { font-size: 11px; font-weight: 600; color: var(--ink); line-height: 1; }
.fz-dch__cell-ratio { font-size: 10px; color: var(--ink-2); line-height: 1; }

.fz-dch__tasks { display: flex; flex-direction: column; gap: var(--space-4); padding: 0 16px; }
.fz-dch__task { display: flex; flex-direction: column; gap: 6px; }
.fz-dch__task-head { display: flex; align-items: baseline; gap: var(--space-3); }
.fz-dch__task-name {
  flex: 1 1 auto; font-size: 0.95rem; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fz-dch__task-total { flex: 0 0 auto; font-size: var(--text-body-sm); color: var(--ink-2); }
.fz-dch__strip { display: grid; grid-template-columns: repeat(28, 1fr); gap: 2px; align-items: center; }
.fz-dch__dot { width: 100%; aspect-ratio: 1 / 1; max-width: 8px; border-radius: 50%; justify-self: center; }
.fz-dch__dot--done { background: var(--accent); }
.fz-dch__dot--seen { background: transparent; box-shadow: inset 0 0 0 1px var(--line-2); }
.fz-dch__dot--absent { background: transparent; }

.fz-dch__empty {
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-4);
  padding: var(--space-6) 16px; color: var(--ink-2);
  font-size: 0.95rem; line-height: 1.5;
}
.fz-dch__empty-txt { margin: 0; }
.fz-dch__empty-btn {
  padding: var(--space-3) var(--space-5); cursor: pointer;
  background: var(--accent); color: var(--on-accent);
  border: 1px solid var(--accent); border-radius: var(--radius-md);
  font-size: 0.95rem; font-weight: 600;
}

/* ── CONTRACT-HET-DOSSIER-BEWAART-DE-OVEREENKOMST ───────────────────────────
   Instellingen → Jouw gegevens → Documenten. Dezelfde klassenamen en dezelfde
   vorm als het Documenten-blad in het coachpaneel (coach/assets/css/coach.css):
   titel boven, datum eronder in --ink-2, rechts één deur naar de pdf-bytes.
   Twee schermen die hetzelfde papier tonen horen er hetzelfde uit te zien. */
.cdocs__list { display: flex; flex-direction: column; margin-top: var(--space-3); }
.cdocs__row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) 0; border-bottom: 1px solid var(--hairline);
}
.cdocs__row:last-child { border-bottom: none; }
.cdocs__txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cdocs__title {
  margin: 0; font-size: 0.95rem; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cdocs__meta { margin: 0; font-size: 0.8rem; color: var(--ink-2); }
.cdocs__open {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: var(--radius-md);
  border: 1px solid var(--hairline-strong); background: transparent;
  color: var(--ink); text-decoration: none;
}
.cdocs__open svg { width: 20px; height: 20px; }

/* ── DAGRITME-JE-TIJDEN-PAS-JE-ALTIJD-OPNIEUW-AAN ───────────────────────────
   Het tijdenblad leunt volledig op .fz-day-cycle-sheet (achtergrond, kaart,
   kop, sluitkruis) en op .fz-day-cycle-page__controls binnenin: de twee
   tijdREGELS zijn NIET opnieuw gevormd, ze staan hier alleen in een ander
   omhulsel. Daarom staat hier geen enkele regel die een veld, een label of
   de vastzet-knop aanraakt — als de besturing morgen verandert, verandert
   dit blad vanzelf mee. */
.fz-dc-times__body { gap: 12px; padding: 12px 16px 20px; }
.fz-dc-times__lead {
  margin: 0;
  font-size: var(--text-body-sm);
  color: var(--ink-2);
}
/* De besturing tekent zijn eigen kader; in het blad zou dat kader een tweede
   rand naast de kaartrand zijn. De doos eromheen is dus leeg met opzet. */
.fz-dc-times__box { display: block; }
.fz-dc-times__done {
  width: 100%;
  min-height: 48px;
  background: var(--accent);
  color: var(--on-accent);
  border: 0;
  border-radius: var(--radius-md);
  font: inherit;
  font-size: var(--text-body);
  font-weight: 600;
  cursor: pointer;
}
.fz-dc-times__done:hover { filter: brightness(0.96); }
.fz-dc-times__done:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* De tijdregel van het slaapblok is een knop geworden. Hij moet er nog precies
   zo uitzien als de zin die hij was — anders verschuift het slaapblok voor wie
   hem alleen leest — maar hij moet wél een duim kunnen dragen. Vandaar: alle
   knop-eigenschappen terug naar nul, en alleen een raakvlak toegevoegd. */
.fz-day-cycle-sleep__times {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* 44px raakvlak, en bewust GEEN negatieve marge om die hoogte te verstoppen:
     .fz-day-cycle-sleep__plan lijnt op de BASISLIJN uit, en een negatieve marge
     verschuift precies die basislijn — dan hangt "Naar bed …" een paar pixels
     hoger dan "8 u gepland" ernaast en leest één regel als twee. */
  min-height: 44px;
  padding: 0;
  background: transparent;
  border: 0;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.fz-day-cycle-sleep__times:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.fz-day-cycle-sleep__times svg { width: 14px; height: 14px; flex: 0 0 auto; color: var(--ink-2); }
.fz-day-cycle-sleep__times-txt { min-width: 0; }

/* Het potlood bij Slaap gaat over HOE LANG; deze regel wijst naar het blad dat
   over WANNEER gaat. Bewust een stille tekstknop: hij is een wegwijzer naast de
   stepper, geen tweede handeling die om aandacht vraagt. */
.fz-dce__sleep-times {
  align-self: flex-start;
  min-height: 44px;
  padding: 0;
  background: transparent;
  border: 0;
  color: var(--accent);
  font: inherit;
  font-size: var(--text-body-sm);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.fz-dce__sleep-times:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ═══ DAGRITME-S-OCHTENDS-STAAT-AFGELOPEN-NACHT-BOVENAAN ═══════════════════
   's Ochtends staat de nacht die net voorbij is BOVENAAN de ingebedde check-in,
   en de avond die nog komt eronder als voorvertoning. Vier blokken in de orde
   van je dag: afgelopen nacht → ochtend → vanavond → komende nacht. Vandaar
   drie standen voor hetzelfde slaapblok en één ingetogen voorvertoning. */

/* Een BED bij elk nachtblok — met de maan op zowel 'Vanavond' als 'Komende
   nacht' droegen twee verschillende dingen hetzelfde teken. */
.fz-day-cycle-sleep__ico {
  display: inline-flex;
  align-items: center;
  margin-right: 8px;
  color: var(--ink-2);
}
.fz-day-cycle-sleep__ico svg { width: 16px; height: 16px; }

/* Zolang de nacht niet is ingevuld is DIT de volgende stap. Zelfde accentrand
   als .fz-day-cycle__row--next, want het is dezelfde belofte: hier ben je. */
.fz-day-cycle-sleep--next {
  background: color-mix(in srgb, var(--accent) 7%, var(--day-cycle-sleep-bg));
  border-color: transparent;
  box-shadow: inset 0 0 0 1.5px var(--accent);
}

/* Ingevuld klapt het blok in tot één regel: teken, woord, getal, vinkje. Een
   knop, want één tik opent hem weer om te wijzigen. */
.fz-day-cycle-sleep--folded { padding: 10px 16px; }
.fz-day-cycle-sleep--folded .fz-day-cycle-sleep__head {
  width: 100%;
  min-height: 32px;
  padding: 0;
  background: transparent;
  border: 0;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.fz-day-cycle-sleep--folded .fz-day-cycle-sleep__head:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 8px;
}
.fz-day-cycle-sleep__val {
  margin-left: 10px;
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
}
.fz-day-cycle-sleep__ok {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-accent);
}
.fz-day-cycle-sleep__ok svg { width: 14px; height: 14px; }

/* 'Komende nacht': één regel die een uitkomst noemt, geen deur en geen vraag.
   Daarom gewoon tekst — de tijden pas je aan in het blok waar je NU in zit. */
.fz-day-cycle-sleep__span {
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
}

/* De voorvertoning van vanavond. Bewust GEEN opacity om hem "later" te laten
   lijken: zijn kop draagt --muted op 3,9:1, en 0,82 dekking haalt dat naar
   ±3,2:1 — onder de drempel, en de contrastpoorten meten dekking niet, dus het
   zou stil wegzakken. Dat dit een vooruitblik is zegt hij met zijn VORM: hij
   staat dicht, hij zegt "vanaf 22:55", zijn teller staat op 0 en zijn rijen
   hebben geen bediening. Afvinken kan pas vanaf 15:00, als dit de lopende
   cyclus wordt. */
.fz-day-cycle-card__from {
  margin-left: 8px;
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--muted);
}
/* Geen vinkje, geen schuif, geen pijl — dus ook geen ruimte waar er een stond
   en geen aanwijzer die zegt dat er iets gebeurt als je tikt. */
.fz-day-cycle__row--preview { cursor: default; }
.fz-day-cycle__row--preview .fz-day-cycle__name { color: var(--ink); }
