/* base.css — K-Fit design system.
 *
 * Greyscale with ONE accent that follows the person, plus red kept back for a
 * single job, in two modes that follow the phone.
 *
 *   --accent  the person's colour: the active tab, the one filled pill, the
 *             focus ring, their own estimates in charts. It changes when the
 *             person does, and it is IDENTITY: it never marks a data category
 *             and never warns.
 *   --red     past a limit, and destructive. It never means "this is the
 *             important thing"; it means "this went past a limit".
 *
 * Everything else is greyscale. Tabs add only their own prefixed classes on top.
 *
 * LIGHT AND DARK (round 12, docs/round-12/REVAMP-DESIGN.md §4.2). The light set
 * is the base on :root; the dark set follows under `prefers-color-scheme: dark`
 * on `:root:not([data-theme="light"])`, and again under `:root[data-theme=
 * "dark"]`, so the page is in the phone's mode before any script runs and
 * Me › Appearance can pin either (js/router.js writes `data-theme`). The two
 * dark blocks are the same set twice; cli/test_theme_contrast.py holds them
 * equal and measures every text/ground pair of both modes.
 *
 * The rules the measured numbers impose (the test checks them):
 *   - --ink-3 and --red text never sit on --raised (4.32 and 4.08 in dark).
 *   - --edge never sits on --raised (2.83 in dark).
 *   - text on --accent-wash is --ink or --ink-2 only; the accent's own ink
 *     measures ~4.1 there.
 *   - red text never sits on --red-wash; a "past the limit" tag is red text
 *     on --surface-2.
 *   - text on an accent FILL is --on-accent (near-black, both modes); text on
 *     a red fill is --on-red.
 */

/* ------------------------------------------------------------ the typeface --
 * ONE self-hosted face: IBM Plex Sans Var (REVAMP-DESIGN §4.4). This REVERSES
 * the ratified "system fonts only" decision, on the owner's explicit
 * instruction for round 12 (one self-hosted typeface). The file is IBM's own,
 * UNMODIFIED: `IBM Plex Sans Var-Roman-Latin1.woff2` from the npm package
 * @ibm/plex-sans-variable 0.2.0 (github.com/IBM/plex), renamed on disk only.
 *   68,988 bytes; SHA-256 a18d94ea83c93f0a465d0976d8bc8ecbb031225dbaf76fd201d4e724a972034f
 *   axes wght 100–700 (default 400) and wdth 85–100 (default 100), one file
 *   SIL OFL 1.1, © 2017 IBM Corp., Reserved Font Name "Plex" — the licence
 *   ships beside it as fonts/OFL.txt. Because the file is unmodified it keeps
 *   its name; a subset cut here would be a Modified Version and would have to
 *   drop "Plex", which is why cli/test_font_budget.py pins the hash (and the
 *   72 KB budget, the family name and the axes).
 * The Latin-1 split has no ₂ ≈ ≤ ≥ → ✓: write "VO2" with the font's own
 * subscripts, say "about" / "at most" / "at least", draw ticks and arrows as
 * SVG. A stray one falls through to the system faces in --font, never tofu.
 * Its figures are tabular by default (every digit is 600 units), so no mono
 * face is needed beside it. The URL is relative so both bases (/ and
 * /atelier/) resolve it; index.html preloads the same URL, sw.js precaches it,
 * and cli/serve.py serves it as font/woff2. */
@font-face {
  font-family: "IBM Plex Sans Var";
  src: url("../fonts/IBMPlexSansVar-Roman-Latin1.woff2") format("woff2");
  font-weight: 100 700;
  font-stretch: 85% 100%;
  font-style: normal;
  font-display: swap;
}

:root {
  /* ------------------------------------------------ the light set (base) --
   * A neutral paper, not cream. Measured with cli/accents.py contrast() over
   * bg / surface / surface-2 / raised / pane:
   *   --ink    15.81 / 17.72 / 14.43 / 17.72 / 17.72
   *   --ink-2   7.72 /  8.65 /  7.05 /  8.65 /  8.65
   *   --ink-3   5.08 /  5.70 /  4.64 /  5.70 /  5.70
   *   --red     5.00 /  5.60 /  4.56 /  5.60 /  5.60
   *   --edge    3.31 /  3.71 /  3.03 /  3.71 /  3.71   (a control's boundary, 3:1) */
  color-scheme: light;
  --bg: #F2F2F0;
  --surface: #FFFFFF;
  --surface-2: #E8E8E5;
  --raised: #FFFFFF;       /* the selected segment, the switch's thumb */
  --pane: #FFFFFF;         /* the wide pane */
  --line: #D6D6D2;         /* hairlines — decorative only */
  --edge: #84848B;         /* control boundaries: chips, ghost pills, empty ticks */
  --grid: #E4E4E0;         /* chart gridlines — decorative */
  --ink: #18181B;
  --ink-2: #4B4B52;
  --ink-3: #66666D;
  /* The plate loader's iron (Train, js/plates.js; design V3). Each plate has
   * its own numeral ink at 4.5:1 or better on its own face, in both themes,
   * and the set avoids red (red is past a limit) and keeps cobalt off Jordan's
   * slate and lemon off Alex's amber. Plates are never colour-only: every chip
   * carries its numeral. */
  --plate-45: #2F58C8; --plate-45-ink: #FFFFFF; --plate-45-edge: #23479F;
  --plate-35: #E6BE32; --plate-35-ink: #18181B; --plate-35-edge: #B08E1A;
  --plate-25: #237A4B; --plate-25-ink: #FFFFFF; --plate-25-edge: #185C38;
  --plate-10: #FFFFFF; --plate-10-ink: #18181B; --plate-10-edge: #84848B;
  --plate-5: #3A3A40; --plate-5-ink: #F5F5F7; --plate-5-edge: #2A2A2F;
  --plate-2_5: #C4C7CD; --plate-2_5-ink: #18181B; --plate-2_5-edge: #84848B;
  --plate-steel: #A9ACB2; --plate-steel-2: #7B7E86;
  --plate-db: #2E2E33; --plate-db-edge: #2E2E33;
  --plate-shine: #FFFFFF;   /* the dumbbell head's highlight, at 0.2; one value, both themes */
  --red: #C8261C;
  --tissue-fat: #D9A400;   /* DEXA tissue colours: one palette for everyone, never an accent */
  --tissue-lean: #2F6BD6;
  --tissue-bone: #6E6E73;
  --on-red: #FFFFFF;       /* a label on a red fill: 5.60:1 */
  --red-dim: color-mix(in srgb, var(--red) 24%, var(--surface));
  --scrim: color-mix(in srgb, var(--ink) 34%, transparent);
  /* Light has one elevation, and it is the sheet's (the A pass: "reserve
   * lift for sheets and dialogs"). A white tile, callout or card on the paper
   * gets a crisp hairline ring instead of a drop shadow, so nothing on a face
   * floats; dark has none (its levels are surface steps). The thumb of a
   * segmented control or the switch keeps a small shadow: it IS on top. */
  --lift: 0 0 0 1px color-mix(in srgb, var(--ink) 7%, transparent);
  --thumb-lift: 0 1px 2px color-mix(in srgb, var(--ink) 14%, transparent),
                0 0 0 .5px color-mix(in srgb, var(--ink) 10%, transparent);
  --lift-sheet: 0 -8px 40px color-mix(in srgb, var(--ink) 16%, transparent);
  /* A sheet shown as a dialog on wide: lifted in light, edged in dark. */
  --lift-dialog: var(--lift-sheet), 0 20px 60px color-mix(in srgb, #000000 28%, transparent);
  --dialog-edge: transparent;
  /* A soft pill, a set cell, the + on a food row (§4.6). Round 12's A pass:
   * a surface step in BOTH modes, flat. White discs with a lift on a light
   * ground read as a column of floating buttons and made a quiet action look
   * like the primary one; lift is kept for sheets and dialogs. */
  --quiet-bg: var(--surface-2);
  /* The quiet pill's pressed step, and the rail's spine (a structural line
   * that must read in light, where --line almost vanishes on the paper). */
  --quiet-press: color-mix(in srgb, var(--ink) 6%, var(--surface-2));
  --line-strong: color-mix(in srgb, var(--edge) 45%, var(--line));
  /* The selected segment and the switch's thumb: --raised in light (white on
   * the grey track). Only --ink sits on it. */
  --thumb: #FFFFFF;
  --thumb-edge: color-mix(in srgb, var(--ink) 8%, transparent);
  /* A text field: white on the paper in light, so an empty field reads as a
   * field. Its outline is --edge like every other control boundary (3:1,
   * WCAG 1.4.11), and its focus is ONE 2px ring in the person's ink, never
   * the field's border plus an outline beside it. */
  --field-bg: var(--surface);
  /* The ground a chart's on-plot labels are haloed in (base.css, charts). */
  --chart-ground: var(--bg);
  /* Specimens of BOTH modes, whatever mode is on screen: Me › Appearance
   * draws a small light phone and a small dark one side by side. Declared
   * here only (never in the dark set), so they never follow the theme; each
   * is the very value of its mode's token above or below. */
  --specimen-light-bg: #F2F2F0;
  --specimen-light-surface: #FFFFFF;
  --specimen-light-ink: #18181B;
  --specimen-light-line: #D6D6D2;
  --specimen-dark-bg: #0A0A0A;
  --specimen-dark-surface: #1F1F22;
  --specimen-dark-ink: #F5F5F7;
  --specimen-dark-line: #2E2E33;

  /* The two default accents' tiers in this mode (cli/accents.py ACCENT_TIERS),
   * so the first frame is right before /api/accents answers. js/router.js
   * then paints each person's chosen name over these, inline, for whichever
   * mode the page is showing. */
  --accent-alex-mark: #B4781D;
  --accent-alex-ink: #8E5E17;
  --accent-jordan-mark: #4A82BD;
  --accent-jordan-ink: #3D6B9B;

  /* ---------------------------------------------------- in both modes -- */
  --on-accent: #0A0A0A;    /* a label on an accent fill: 4.92 to 8.49:1 */
  --on-brand: #FFFFFF;     /* the K knocked out of the brand tile, both modes */
  --good: var(--ink);      /* never green: ink means "on track" */

  /* ---------------------------------------------------- person accents --
   * The FILL of each person's accent is the ratified hex from cli/accents.py,
   * the same in both modes; near-black text sits on it. Each fill has two
   * derived tiers per mode (REVAMP-DESIGN §4.3):
   *
   *   --accent-mark  graphics drawn in the accent (meter segments, the ring,
   *                  dots, bars, a control's accent edge): at least 3:1 on
   *                  every ground of the mode
   *   --accent-ink   text and lines drawn in the accent (the active tab, the
   *                  focus ring, a chart's trend line): at least 4.5:1
   *
   * In dark every mark is the fill and every ink is too, except slate's
   * (#558AC1, 6 % toward white). In light all eight need their own ink.
   *
   *   Alex    #B87A1E  amber   5.51:1 on the dark ground   passes
   *   Jordan  #4A82BD  slate   4.92:1 on the dark ground   passes
   *
   * SUBSTITUTION ON RECORD: the review specified #2E5A8F for Jordan. That
   * measures 2.80:1 on the dark ground, under the 3:1 floor a non-text UI
   * accent has to clear (WCAG 1.4.11), so it would fail as a ring, a tab
   * marker or a focus outline. It has been corrected upward to #4A82BD — the
   * nearest step that still reads as slate rather than as a generic blue —
   * and this comment is the record of that change.
   *
   * Every accent TINT below is a color-mix() of a fill or a tier. No other hex
   * enters the palette, which is what keeps the no-new-hues rule enforceable. */
  /* THE BRAND, which is not an accent. Kaire's teal, from the mark itself
   * (kaire-playground logo-icon-color.svg). It dresses the wordmark and the
   * lock screen and nothing else: the palette law is one ACCENT per person —
   * that is IDENTITY, it says whose ledger you are looking at — plus red for
   * "past a limit". A brand colour doing a third job in the chrome would make
   * the accent stop meaning "you". */
  --brand: #0d9488;

  --accent-alex: #B87A1E;
  --accent-jordan: #4A82BD;
  --accent: var(--accent-alex);
  --accent-mark: var(--accent-alex-mark);
  --accent-ink: var(--accent-alex-ink);
  --accent-dim: color-mix(in srgb, var(--accent) 34%, var(--bg));
  --accent-wash: color-mix(in srgb, var(--accent) 16%, var(--surface));
  --accent-wash-bg: color-mix(in srgb, var(--accent) 13%, var(--bg));
  --accent-band: color-mix(in srgb, var(--accent-mark) 20%, transparent);
  --accent-fan: color-mix(in srgb, var(--accent-mark) 11%, transparent);
  --red-wash: color-mix(in srgb, var(--red) 16%, transparent);   /* zones only */

  /* Softer than they were (16/10/8). Both apps the principals picked are
   * round — Dropset's cards and Cal AI's tiles are nearer 20-26 than 16 — and
   * at phone size a larger radius is what makes a dark card read as an object
   * rather than as a panel. --radius-lg is the hero's, used sparingly. */
  --radius-lg: 26px;
  --radius: 20px;
  --radius-sm: 12px;
  --radius-xs: 8px;
  --tap: 44px;             /* minimum tap target */
  /* The phone column (§3.3): full width to 600, then centred at 600. The wide
   * layout has its own columns (the rail 232, the face 420, the pane). */
  --shell: 600px;
  --pad: 16px;             /* the phone's side gutter; 22 in the wide face */
  --tabbar-h: 55px;        /* the bar above the safe area: its 1px hairline + 6 + 44 + 4 */

  /* Space (§4.6): 4 to 48, named by their multiple of 4. */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 20px;
  --s-6: 24px;
  --s-8: 32px;
  --s-10: 40px;
  --s-12: 48px;

  /* Radii grow with the object (§4.6). Rows and sections have none. */
  --r-field: 12px;         /* a field, a set cell, a selected row's wash */
  --r-tile: 16px;          /* a tile, a callout */
  --r-capture: 14px;       /* a capture tile */
  --r-dialog: 24px;
  --r-sheet: 28px;         /* a sheet's top corners */

  /* The type scale before round 12. No half-steps (11.5, 12.5, 13.5, 14.5
   * were the tell that no scale had been agreed) and no one-off orphans.
   *
   * NO TEXT UNDER 13px (the owner's decision for round 12; REVAMP-DESIGN
   * §4.5): body, labels, tab labels, badges and chart axes included. --fs-1
   * and --fs-2 were 11 and 12; they are held at the floor here, so every
   * stylesheet that still names them clears it without a per-tab edit, and
   * cli/test_layout_rules.py fails any size under 13 in this file. They retire
   * into the §4.5 scale below as the tabs are rebuilt (WP13 removes them). */
  --fs-1: 13px;            /* was 11: micro labels, table headers */
  --fs-2: 13px;            /* was 12: captions, pills */
  --fs-3: 13px;            /* secondary body */
  --fs-4: 14px;            /* body */
  --fs-5: 15px;            /* primary body, buttons */
  --fs-6: 16px;            /* lead — and the floor below which iOS zooms a
                              focused input, so text fields never go under it */
  --fs-7: 18px;            /* sheet titles, glyph buttons */
  --fs-8: 22px;            /* stat values */
  --fs-9: 26px;            /* the app title */
  --fs-10: 30px;           /* the rest clock */
  --fs-11: 34px;           /* the weight hero */

  /* The round-12 type scale (REVAMP-DESIGN §4.5), in the one face. A size and
   * a `font` shorthand for each step; the width (Plex's wdth axis) is set
   * beside it with font-stretch, since the shorthand cannot carry 85%:
   *   hero, display  font-stretch: 85%    stat  92%    the rest 100%
   * Titles track at -0.02em. Inputs are 17 (iOS zooms a focused field under
   * 16). The hero drops to 48 under 360 wide or 720 tall (below). */
  --t-hero-size: 56px;
  --t-display-size: 44px;
  --t-title-size: 30px;
  --t-stat-size: 24px;
  --t-head-size: 20px;
  --t-row-size: 17px;
  --t-body-size: 15px;
  --t-sub-size: 14px;
  --t-cap-size: 13px;      /* the floor: captions, tags, tab labels, axes */
  --t-hero: 600 var(--t-hero-size)/0.95 var(--font);
  --t-display: 600 var(--t-display-size)/1 var(--font);
  --t-title: 620 var(--t-title-size)/1.1 var(--font);
  --t-stat: 600 var(--t-stat-size)/1.1 var(--font);
  --t-head: 600 var(--t-head-size)/1.25 var(--font);
  --t-row: 560 var(--t-row-size)/1.3 var(--font);
  --t-body: 400 var(--t-body-size)/1.45 var(--font);
  --t-sub: 400 var(--t-sub-size)/1.4 var(--font);
  --t-cap: 450 var(--t-cap-size)/1.35 var(--font);

  /* The one face (see @font-face above), then the system faces for the first
   * paint (font-display: swap) and for any glyph outside the Latin-1 split.
   * There is no serif in this app and no font toggle. */
  --font: "IBM Plex Sans Var", -apple-system, "Segoe UI Variable", system-ui,
          sans-serif;

  /* One family (REVAMP-DESIGN §4.4): Plex's condensed width sets the large
   * numerals and its tabular figures (.num) are the old mono register. There
   * is no display or mono stack. */

  --ease: cubic-bezier(.32, .72, 0, 1);
}

/* ------------------------------------------------------------ the dark set --
 * The ground the app was born on, kept, with the inks lifted to clear AA on
 * every ground (the old --ink-3 measured 2.76 to 3.32). Over bg / surface /
 * surface-2 / raised / pane:
 *   --ink    18.18 / 16.60 / 15.10 / 12.76 / 17.32
 *   --ink-2  10.03 /  9.15 /  8.33 /  7.04 /  9.55
 *   --ink-3   6.16 /  5.63 /  5.12 /  4.32 /  5.87   (never on --raised)
 *   --red     5.81 /  5.30 /  4.83 /  4.08 /  5.54   (never on --raised)
 *   --edge    4.03 /  3.68 /  3.35 /  2.83 /  3.84   (never on --raised)
 * The same set twice: once for the phone's own dark mode (unless Appearance
 * pinned light), once for Appearance pinning dark. Keep them identical. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: #0A0A0A;
    --surface: #161618;
    --surface-2: #1F1F22;
    --raised: #2C2C31;
    --pane: #111113;
    --line: #2E2E33;
    --edge: #707078;
    --grid: #232327;
    --ink: #F5F5F7;
    --ink-2: #B8B8BE;
    --ink-3: #8F8F96;
    --plate-45: #3D63D2; --plate-45-ink: #FFFFFF; --plate-45-edge: #5A7EE6;
    --plate-35: #D9B43A; --plate-35-ink: #0A0A0A; --plate-35-edge: #D9B43A;
    --plate-25: #268050; --plate-25-ink: #FFFFFF; --plate-25-edge: #3A9A66;
    --plate-10: #E4E4E8; --plate-10-ink: #0A0A0A; --plate-10-edge: #E4E4E8;
    --plate-5: #48484F; --plate-5-ink: #F5F5F7; --plate-5-edge: #8F8F96;
    --plate-2_5: #A4A7AE; --plate-2_5-ink: #0A0A0A; --plate-2_5-edge: #A4A7AE;
    --plate-steel: #8C8F97; --plate-steel-2: #5F6269;
    --plate-db: #3A3A40; --plate-db-edge: #707078;
    --red: #FF453A;
    --tissue-fat: #FFCC33;
    --tissue-lean: #4C8DF6;
    --tissue-bone: #F4F4F6;
    --on-red: #0A0A0A;
    --red-dim: #7A2119;
    --scrim: color-mix(in srgb, #000000 58%, transparent);
    --lift: none;
    --thumb-lift: none;
    --lift-sheet: none;
    --lift-dialog: none;
    --dialog-edge: var(--line);
    --quiet-bg: var(--surface-2);
    --quiet-press: color-mix(in srgb, #FFFFFF 5%, var(--surface-2));
    --line-strong: color-mix(in srgb, var(--edge) 22%, var(--line));
    --thumb: color-mix(in srgb, #FFFFFF 7%, var(--raised));
    --thumb-edge: color-mix(in srgb, #FFFFFF 6%, transparent);
    --field-bg: var(--surface-2);
    --accent-alex-mark: #B87A1E;
    --accent-alex-ink: #B87A1E;
    --accent-jordan-mark: #4A82BD;
    --accent-jordan-ink: #558AC1;
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0A0A0A;
  --surface: #161618;
  --surface-2: #1F1F22;
  --raised: #2C2C31;
  --pane: #111113;
  --line: #2E2E33;
  --edge: #707078;
  --grid: #232327;
  --ink: #F5F5F7;
  --ink-2: #B8B8BE;
  --ink-3: #8F8F96;
  --plate-45: #3D63D2; --plate-45-ink: #FFFFFF; --plate-45-edge: #5A7EE6;
  --plate-35: #D9B43A; --plate-35-ink: #0A0A0A; --plate-35-edge: #D9B43A;
  --plate-25: #268050; --plate-25-ink: #FFFFFF; --plate-25-edge: #3A9A66;
  --plate-10: #E4E4E8; --plate-10-ink: #0A0A0A; --plate-10-edge: #E4E4E8;
  --plate-5: #48484F; --plate-5-ink: #F5F5F7; --plate-5-edge: #8F8F96;
  --plate-2_5: #A4A7AE; --plate-2_5-ink: #0A0A0A; --plate-2_5-edge: #A4A7AE;
  --plate-steel: #8C8F97; --plate-steel-2: #5F6269;
  --plate-db: #3A3A40; --plate-db-edge: #707078;
  --red: #FF453A;
  --tissue-fat: #FFCC33;
  --tissue-lean: #4C8DF6;
  --tissue-bone: #F4F4F6;
  --on-red: #0A0A0A;
  --red-dim: #7A2119;
  --scrim: color-mix(in srgb, #000000 58%, transparent);
  --lift: none;
  --thumb-lift: none;
  --lift-sheet: none;
  --lift-dialog: none;
  --dialog-edge: var(--line);
  --quiet-bg: var(--surface-2);
  --quiet-press: color-mix(in srgb, #FFFFFF 5%, var(--surface-2));
  --line-strong: color-mix(in srgb, var(--edge) 22%, var(--line));
  --thumb: color-mix(in srgb, #FFFFFF 7%, var(--raised));
  --thumb-edge: color-mix(in srgb, #FFFFFF 6%, transparent);
  --field-bg: var(--surface-2);
  --accent-alex-mark: #B87A1E;
  --accent-alex-ink: #B87A1E;
  --accent-jordan-mark: #4A82BD;
  --accent-jordan-ink: #558AC1;
}

/* The accent follows the ACTIVE PERSON, and js/router.js stamps that person on
 * the root element before the first paint. Declaring it here — above base.css's
 * own rules, above every tab stylesheet, above the inline SVG the charts draw —
 * is what lets a single attribute swap re-theme the whole app with no repaint
 * and no per-tab cooperation. */
:root[data-active-person="jordan"] {
  --accent: var(--accent-jordan);
  --accent-mark: var(--accent-jordan-mark);
  --accent-ink: var(--accent-jordan-ink);
}

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

/* A grid that declares no `grid-template-columns` puts its items in an IMPLICIT
 * column, and an implicit column is sized by `grid-auto-columns` — whose `auto`
 * default can never shrink below the widest item's max-content. One long word or
 * one row of inputs then pushes the whole track past 390px, where `overflow-x:
 * hidden` clips it instead of scrolling. `minmax(0, 1fr)` is the same track with
 * a floor of zero: it still stretches to the container and still sizes to
 * max-content when the container is indefinite. Zero specificity, so any
 * explicit track definition wins. */
* { grid-auto-columns: minmax(0, 1fr); }

html {
  -webkit-text-size-adjust: 100%;
  overflow-x: hidden;
}

body {
  background: var(--bg);
  color: var(--ink);
  font: 400 var(--fs-5)/1.45 var(--font);
  /* The face spans 100–700 upright; a faked bold or slanted italic would be
   * a face the design never drew. Inherited, so it holds for the whole page. */
  font-synthesis: none;
  min-height: 100dvh;
  /* No overflow here: html already clips the viewport horizontally, and an
   * overflow on body would make body the containing scrollport for every
   * position:sticky descendant — pinning nothing, since body is as tall as its
   * content. The Workout tab's rest clock sticks to the viewport because of
   * this omission. */
  -webkit-font-smoothing: antialiased;
  /* §4.5: multi-line text wraps "pretty" (no orphaned last word); inherited,
   * so every paragraph, row sub-line and note gets it. Subtitles and titles
   * say `balance` on their own rules. */
  text-wrap: pretty;
  padding-bottom: calc(var(--tap) + 34px + env(safe-area-inset-bottom));
}

/* fb_4fe00bb8: under viewport-fit=cover the page scrolls beneath the status
 * bar, and the system's translucent band blurred the header and rows into
 * it. The safe-area inset is capped in the page's own ground, so content
 * reads as starting below it; 0 tall wherever there is no inset (a desktop,
 * a phone in a browser tab). Under the sheets (60), over the face. */
body::before {
  content: "";
  position: fixed;
  inset: 0 0 auto 0;
  height: env(safe-area-inset-top, 0px);
  z-index: 45;
  background: var(--bg);
  pointer-events: none;
}

img, svg, video { max-width: 100%; }

/* Figures that sit in a column must not dance as they change. `.num` is the
 * explicit opt-in a tab puts on a cell; the rules further down apply the same
 * thing unconditionally to every structural place a number lands — stat values,
 * axis labels, chart tooltips, progress readouts, table cells, the date nav —
 * so no tab has to remember.
 *
 * The two !important flags are load-bearing, and they are here for a cascade
 * reason rather than a stylistic one. `font:` is a SHORTHAND: it cannot SET
 * font-variant-numeric or font-feature-settings, but it RESETS both to their
 * initial values. The tab stylesheets load after this one and carry ~246 `font:`
 * declarations, most of them a single class at the same specificity as `.num`,
 * so source order handed them the property — and `.trends-ceiling-value`,
 * `.body-tape-now`, `.wk-timer-clock`, `.today-item-macros`, every `.set-lb`
 * input and 70-odd more silently computed `font-variant-numeric: normal` while
 * carrying the class that exists to prevent exactly that. Even `.hint` below
 * beat it, from inside this file. `.num` is an opt-in a caller writes on
 * purpose; no rule may legitimately override it, which is the one case where
 * !important is the correct instrument rather than a shortcut. Nothing else in
 * this stylesheet uses it except the queue badge, which has its own note. */
.num {
  font-variant-numeric: tabular-nums !important;
  font-feature-settings: "tnum" 1 !important;
}

/* A number and its unit, kept on one line (ui.keepUnits): "120 kcal" never
 * breaks into "120 / kcal". */
.nw { white-space: nowrap; }

/* Marks from js/icons.js. They belong on the tab bar and on section headers.
 * Never inside a data row — the numbers are the ornament there. */
.icon { display: block; flex: none; }

/* Every keyboard user sees where they are. The outline already follows the
 * element's own border-radius, so this rule must not set one — doing so
 * reshapes pills into rectangles the moment they take focus. */
:focus-visible {
  outline: 2px solid var(--accent-ink);
  outline-offset: 2px;
}
:focus:not(:focus-visible) { outline: none; }

/* ------------------------------------------------------------------ shell
 *
 * The chrome (REVAMP-DESIGN §3, §5; WP4): the face header, the person switch,
 * the tab bar, and at 1024px the same DOM laid out as rail + face + pane.
 * One design system at every width; the breakpoints (§3.3):
 *
 *   under 360     narrow: the switch shows initials, the hero drops to 48
 *   360 to 599    the phone (390 is the reference)
 *   600 to 1023   the phone, its column centred at 600 (--shell)
 *   1024 and up   wide: side rail 232, face column 420, pane the rest
 *   1440 and up   the pane caps at 820; rail, face and pane centre together
 *
 * and one height rule: under 720 tall the hero compacts to 48, so a morning
 * block still shows on a 375 x 667 phone. The face column (#view) and the
 * pane are size containers (`face`, `pane`), so a component can answer to
 * the column it sits in: the same at 390 on a phone and 420 on wide. */

/* The face header: the title row, then ONE subtitle line that ends in the
 * status tags. */
.top {
  max-width: var(--shell);
  margin: 0 auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2px;
  padding: calc(6px + env(safe-area-inset-top)) var(--pad) 6px;
}

.top-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-width: 0;
  min-height: 48px;
}

/* The title yields to the switch: one line, ellipsised, never wrapped. */
.app-title {
  flex: 1 1 auto;
  min-width: 0;
  font: var(--t-title);
  letter-spacing: -.02em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.top-actions { flex: none; display: flex; align-items: center; gap: 8px; }

/* The person switch: two segments, each with its own person's dot, the
 * current one the thumb on --raised (§5). One tap; both previews always
 * visible, so the switch says whose colour you are about to get. */
.person-switch {
  flex: none;
  display: inline-flex;
  padding: 3px;
  border-radius: 999px;
  background: var(--surface-2);
}

/* 36 tall inside the track's 3px; ::after reaches the hit area to 44. The dot
 * is the person's FILL in both modes. `--btn-accent` is scoped per button, so
 * it is independent of whichever person is driving the document's --accent. */
.person-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 36px;
  /* 11px, not 13: the longest weekday ("Wednesday", Today's title with its
   * caret) fits beside the switch at 390 without an ellipsis. */
  padding: 0 11px;
  font: 560 var(--t-body-size)/1 var(--font);
  color: var(--ink-2);
  background: none;
  border: 0;
  cursor: pointer;
  border-radius: 999px;
  white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
  transition: color .15s var(--ease), background-color .15s var(--ease);
}
.person-btn[data-person="alex"] { --btn-accent: var(--accent-alex); }
.person-btn[data-person="jordan"] { --btn-accent: var(--accent-jordan); }
.person-btn::before {
  content: "";
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--btn-accent, var(--ink-3));
}
.person-btn::after {
  content: "";
  position: absolute;
  inset: -4px 0;
}
/* The current half is the switch's thumb: --thumb (white in light, a clear
 * step up from the track in dark, where --raised measured 1.2:1 against it
 * and the choice was hard to see). Only --ink sits on it. */
.person-btn[aria-pressed="true"] {
  color: var(--ink);
  background: var(--thumb);
  box-shadow: var(--thumb-lift), inset 0 0 0 1px var(--thumb-edge);
}
.person-btn:hover { color: var(--ink); }
/* The initial js/router.js derives from the name; shown under 360 only. */
.person-ini { display: none; }

/* A title that opens a picker (Today's day, Train's session) is a button
 * with a visible caret: a tappable title nobody can see is tappable is not a
 * feature (§5). The caret is drawn (icons.js "caret"), so the title's text
 * stays the title. */
.title-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  min-height: var(--tap);
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.title-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.title-caret {
  flex: none;
  width: 20px;
  height: 20px;
  margin-top: 4px;
  color: var(--ink-3);
}

.page-sub {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
  font: 400 var(--t-body-size)/1.4 var(--font);
  color: var(--ink-2);
  text-wrap: balance;
}
.page-sub[hidden] { display: none; }

/* Said, not shown: the subtitle's status tags for a screen reader
 * (#page-status, a polite live region; js/router.js announceStatus). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* The status tags at the end of the subtitle: "As of 7:10", "Offline".
 * Neutral — stale and offline are words, never red and never the accent. */
.page-tag {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  padding: 0 9px;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--ink-2);
  font: 500 var(--t-cap-size)/1 var(--font);
  white-space: nowrap;
}

main#view {
  max-width: var(--shell);
  margin: 0 auto;
  padding: 6px var(--pad) 28px;
  display: grid;
  /* Explicit, and explicitly floored at zero: a single `auto` track would be
   * sized to the widest card's max-content and blow the page past 390px. */
  grid-template-columns: minmax(0, 1fr);
  gap: 12px;
  align-content: start;
  /* The face column, for container queries (§3.3). Inline-size only: the
   * column's height still follows its content. */
  container: face / inline-size;
}
main#view:focus { outline: none; }

/* Each tab renders into its own root (js/router.js) and a switch only HIDES
 * the others, stacks and all (§3.2). A root is `display: contents`, so its
 * cards are #view's own grid items with #view's gap, exactly as before. */
[data-tab-root] { display: contents; }
[data-tab-root][hidden],
[data-pane-root][hidden] { display: none; }

/* The first visit's skeleton, in the face's own shape (§6 "Loading"). Blocks
 * on --surface-2, no words; a screen reader hears the one label. */
.skel-face { display: grid; gap: 12px; }
.skel-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}
.skel {
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  animation: skel-breathe 1.4s var(--ease) infinite alternate;
}
.skel-hero { height: 96px; }
.skel-meter { height: 12px; border-radius: 999px; }
.skel-row { height: 52px; }
.skel-board { height: 248px; }
.skel-chart { height: 200px; }
.skel-tiles { height: 132px; }
.skel-swatches { height: 44px; border-radius: 999px; }
@keyframes skel-breathe { from { opacity: .55; } to { opacity: 1; } }

/* The pane host (wide only) and its quiet default until a tab supplies one
 * (a module's `pane(ctx)`, WP7 on). */
#pane { display: none; }
/* Nothing open yet: a heading on the face title's line and one sentence,
 * never a stray caption in a corner. */
.pane-empty { display: grid; gap: 6px; padding-top: 4px; }
.pane-empty-title { font: var(--t-title); letter-spacing: -.02em; color: var(--ink-2); }
.pane-empty-sub { font: var(--t-body); color: var(--ink-2); }

/* The rail's head (the wordmark, the second person switch) and foot (the
 * queue's state, Report a problem, Lock) are wide-only. */
.rail-head,
.rail-foot { display: none; }

/* The tab bar: five destinations, marks over 13px labels, on --bg at 88 %
 * with a blur and a top hairline, above the home indicator (§3.1). */
.tabbar {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 40;
  display: flex;
  justify-content: center;
  padding: 6px 4px calc(4px + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-top: 1px solid var(--line);
}

/* The destinations share the width equally, however many there are. The bar
 * and the side rail are one element (§3.4). */
.tabbar-tabs {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 2px;
  width: 100%;
  max-width: var(--shell);
}

/* Mark above word. The grid is declared with both rows so the bar keeps the
 * same height whether or not js/icons.js has painted its marks yet — no
 * reflow when the module lands. Inactive tabs are --ink-2 (7.05:1 or better
 * on every ground; the old grey measured 3.52). */
.tabbar .tab {
  min-width: 0;
  min-height: var(--tap);
  border: 0;
  background: none;
  cursor: pointer;
  font: 500 var(--t-cap-size)/1 var(--font);
  color: var(--ink-2);
  padding: 0 2px;
  border-radius: var(--radius-sm);
  display: grid;
  grid-template-rows: 24px auto;
  align-content: center;
  justify-items: center;
  gap: 3px;
  -webkit-tap-highlight-color: transparent;
  transition: color .15s var(--ease);
}
.tab-icon { width: 24px; height: 24px; grid-row: 1; }
/* fb_4a0fa189: at the tab's line-height of 1 the clip box ended at the
 * baseline's em box and cut the g of "Progress"; the label keeps its own
 * line-height so a descender is inside what overflow clips (24 + 3 + 16 =
 * the 44 tap row, still). */
.tab-label {
  grid-row: 2;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  line-height: 16px;
}
/* The active tab: the person's ink at 620 and a 2.3 stroke, its mark on a
 * soft pill of the person's wash (the A pass), so where you are reads at a
 * glance without a second colour. The pill sits behind the mark in the same
 * grid cell and grows in; its row stays 24, so the bar keeps its height. */
.tabbar .tab::before {
  content: "";
  grid-row: 1;
  grid-column: 1;
  align-self: center;
  justify-self: center;
  width: 54px;
  height: 30px;
  border-radius: 999px;
  background: transparent;
  transform: scaleX(.6);
  transition: background-color .2s var(--ease), transform .24s var(--ease);
}
.tabbar .tab .tab-icon { grid-column: 1; position: relative; }
.tabbar .tab-label { grid-column: 1; }
.tabbar .tab[aria-current="page"]::before { background: var(--accent-wash-bg); transform: none; }
.tabbar .tab[aria-current="page"] { color: var(--accent-ink); font-weight: 620; }
.tabbar .tab[aria-current="page"] .tab-icon { stroke-width: 2.3; }
.tabbar .tab:hover { color: var(--ink); }
.tabbar .tab[aria-current="page"]:hover { color: var(--accent-ink); }
.tabbar .tab:active .tab-icon { transform: scale(.92); }
.tabbar .tab .tab-icon { transition: transform .12s var(--ease); }

/* A mark js/router.js puts in a chrome control ([data-icon]). */
.chrome-icon { width: 18px; height: 18px; flex: none; }

/* ------------------------------------------------------------- containers */

.card {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 18px;
  box-shadow: var(--lift);   /* light mode only; dark has no elevation */
}
.card-title {
  font: 600 var(--fs-5)/1.3 var(--font);
  margin-bottom: 12px;
}
/* icons.iconTitle() heads a section with one of the eleven marks:
 *   ui.card(iconTitle("flame", "Calories"), ...rows)
 * The mark is ink-3 so it sits behind the words rather than competing with
 * them. Section headers and the tab bar are the ONLY places an icon belongs —
 * a data row's ornament is its numbers. */
.card-head { display: inline; }
.card-head.has-icon {
  display: inline-flex;
  align-items: center;
  gap: 9px;
}
.card-icon { width: 17px; height: 17px; color: var(--ink-3); }
.card-actions { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; margin-top: 14px; }
.card-actions.is-split { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }

.row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* Every track below is floored at zero. A bare `1fr` takes its automatic
 * minimum from the item's min-content — for a text input that is ~186px, which
 * is how two fields end up demanding 382px inside a 354px sheet. */
.list { display: grid; grid-template-columns: minmax(0, 1fr); }
.list > * + * { border-top: 1px solid var(--line); }

.hint { color: var(--ink-3); font: 400 var(--fs-3)/1.45 var(--font); }
.empty { color: var(--ink-2); font: 400 var(--t-sub-size)/1.5 var(--font); text-wrap: pretty; }

/* A designed empty state (ui.emptyState): the screen's mark on a soft disc,
 * a headline, one line, one quiet action — centred, never a grey sentence
 * stranded at the top of a blank page. */
.empty-state {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 6px;
  padding: var(--s-8) var(--s-4);
  text-align: center;
  animation: fade .3s var(--ease);
}
.empty-state.is-compact { padding: var(--s-5) var(--s-2); }
.empty-mark {
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  margin-bottom: 10px;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--ink-3);
}
.empty-state.is-compact .empty-mark { width: 52px; height: 52px; margin-bottom: 6px; }
.empty-mark .icon { width: 28px; height: 28px; stroke-width: 1.75; }
.empty-title { font: 600 var(--t-row-size)/1.3 var(--font); letter-spacing: -.01em; color: var(--ink); text-wrap: balance; }
.empty-text { max-width: 34ch; font: var(--t-sub); color: var(--ink-2); text-wrap: balance; }
.empty-act { margin-top: 12px; }
.empty-detail {
  margin-top: 6px;
  font-size: var(--fs-2);
  color: var(--ink-3);
  word-break: break-word;
}

/* ------------------------------------------------------------------ atoms */

.stat { display: grid; gap: 2px; }
/* A stat value is always a figure, so it always gets tabular lining — a caller
 * that forgets `.num` still lines up with the one beside it. */
.stat-v {
  font: 700 var(--fs-8)/1.15 var(--font);
  letter-spacing: -.01em;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
/* Units are annotation, not prose: mono, the same register as the chips. */
.stat-v .stat-unit {
  font: 600 var(--fs-3)/1 var(--font);
  letter-spacing: -.01em;
  color: var(--ink-2);
}
.stat-k { font: 500 var(--fs-2)/1.4 var(--font); color: var(--ink-3); }
.stat-sub { font: 400 var(--fs-2)/1.4 var(--font); color: var(--ink-3); }
.stat-red .stat-v { color: var(--red); }
.stat-accent .stat-v { color: var(--accent-ink); }
.stat-absent .stat-v { color: var(--ink-3); font-weight: 450; }
.stat-absent .stat-sub { color: var(--ink-3); }

/* A pill is a DATA CHIP — EVT, S/XL/ADD, a tier, a condition, a slot code — so
 * it takes the mono face. That is what separates a chip from a label at a
 * glance, and it makes the codes read as codes rather than as words. */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  border-radius: 999px;
  font: 600 var(--fs-2)/1.3 var(--font);
  letter-spacing: -.01em;
  font-variant-numeric: tabular-nums;
  background: var(--surface-2);
  color: var(--ink-2);
  white-space: nowrap;
}
.pill-red { background: var(--red-dim); color: var(--ink); }
.pill-accent { background: var(--accent-dim); color: var(--ink); }
.pill-muted { background: transparent; color: var(--ink-3); border: 1px solid var(--line); }
.pill-ghost { background: transparent; color: var(--ink-2); }

/* Conditions travel with every measurement (fasted / post-exercise / hydration)
 * and they are metadata about a reading, never the reading. Same mono register
 * as the chips, one step quieter. */
.conditions {
  font: 500 var(--fs-2)/1.5 var(--font);
  letter-spacing: -.01em;
  color: var(--ink-3);
}

/* ----------------------------------------------------------------- inputs */

.field { display: grid; gap: 6px; }
.field-label { font: 500 var(--fs-2)/1.4 var(--font); color: var(--ink-2); }
.field-hint { font: 400 var(--fs-2)/1.4 var(--font); color: var(--ink-3); }

/* Zero specificity (:where), so any tab's own field rule wins outright: the
 * kit supplies the default look, never a fight. */
:where(.input, textarea, select) {
  /* 17 (§4.5 "inputs"): under 16 a phone zooms the page on focus. */
  font: 400 var(--t-row-size)/1.35 var(--font);
  color: var(--ink);
  background: var(--field-bg);
  border: 1px solid var(--edge);
  border-radius: var(--r-field);
  padding: 11px 12px;
  min-height: var(--tap);
  width: 100%;
  /* An input's default min-content width is its `size` attribute (~186px at
   * 15px), which is what makes it refuse to shrink inside a grid or flex
   * track. Zero here, and the track decides. */
  min-width: 0;
  transition: border-color .14s var(--ease), box-shadow .14s var(--ease);
}
.input::placeholder, textarea::placeholder { color: var(--ink-3); opacity: 1; }
/* Focus is ONE ring: the border turns the person's ink and thickens to 2px
 * inside the same box, so nothing shifts and there is no second outline
 * floating 2px outside the field (the Note screen's double border). */
:where(.input, textarea, select):focus {
  border-color: var(--accent-ink);
  box-shadow: inset 0 0 0 1px var(--accent-ink);
}
:where(.input, textarea, select):focus-visible { outline: none; }
:where(.input, textarea, select):disabled {
  color: var(--ink-3);
  background: transparent;
  border-style: dashed;
  cursor: default;
}
/* A number is typed, never spun: the native spin buttons are a desktop
 * widget that crowds a phone field and reads as a stray control. Callers
 * pass inputmode="decimal" (or numeric) so the phone opens its number pad. */
input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
/* A text area grows with what is written (field-sizing where the browser has
 * it; ui.autoGrow() everywhere else) and never shows the corner grip. */
/* `textarea[class]` so a tab's own `.x-note { resize: vertical }` cannot
 * bring the grip back. */
textarea, textarea[class] { resize: none; }
textarea {
  min-height: 112px;
  line-height: 1.5;
  field-sizing: content;
  max-height: 60dvh;
}
/* A clock or a date field reads as a value, not a form widget: the value in
 * tabular figures, the platform's own picker mark kept but quiet. */
input[type="time"], input[type="date"], .clock-input { font-variant-numeric: tabular-nums; }
/* A clock field (ui.clockInput) is a value, not a paragraph: as wide as
 * "17:30" wants, not the whole row. */
.clock-input { max-width: 9ch; min-width: 7.5ch; text-align: center; letter-spacing: .01em; }
input[type="time"]::-webkit-calendar-picker-indicator,
input[type="date"]::-webkit-calendar-picker-indicator { opacity: .55; cursor: pointer; }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  min-height: var(--tap);
  padding: 13px 16px;
  border: 0;
  /* the same corner as the inputs and chips it sits beside */
  border-radius: var(--radius-sm);
  cursor: pointer;
  font: 700 var(--fs-5)/1 var(--font);
  text-align: center;
  transition: filter .15s var(--ease), background-color .15s var(--ease);
}
/* The primary action carries the person's accent FILL. Its label is
 * --on-accent, near-black in both modes: white on the amber measures 3.60:1
 * and would fail AA at this size, while the near-black label measures 5.51:1
 * on Alex's amber and 4.92:1 on Jordan's slate (4.92 to 8.49 across the set). */
.btn-primary { background: var(--accent); color: var(--on-accent); }
/* Destructive keeps red, with --on-red on it: near-black on the dark red
 * (6.15:1), white on the light one (5.60:1). White on #FF453A measured 3.1. */
.btn-danger { background: var(--red); color: var(--on-red); }
.btn-grey { background: var(--surface-2); color: var(--ink); font-weight: 600; }
.btn-ghost {
  background: transparent;
  color: var(--ink);
  font-weight: 600;
  border: 1px solid var(--edge);
}
.btn-grey:hover { background: var(--quiet-press); }
.btn-ghost:hover { background: var(--surface-2); }
.btn:active { filter: brightness(1.12); }
/* Disabled is a state, not a dimmed version of the action: a neutral step
 * with a quiet label (never a muddy accent under 3:1). It keeps its label
 * and, with a reason, says why on tap (blockedAttrs). */
.btn:disabled, .btn[aria-disabled="true"] {
  background: var(--surface-2);
  color: var(--ink-3);
  border-color: transparent;
  cursor: default;
  filter: none;
}
.btn-full { width: 100%; }

.segmented {
  display: flex;
  background: var(--surface-2);
  border-radius: 999px;
  padding: 3px;
  gap: 2px;
}
/* 38 tall in a 44 track; the ::after takes each segment's hit to the track's
 * full 44. One line always: a label that would wrap is ellipsised, so two
 * segments are never two heights (put a date range in the caption below). */
.segmented-btn {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  min-height: 38px;
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--ink-2);
  font: 520 var(--t-sub-size)/1 var(--font);
  border-radius: 999px;
  padding: 0 10px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  -webkit-tap-highlight-color: transparent;
  transition: color .15s var(--ease), background-color .15s var(--ease), box-shadow .15s var(--ease);
}
.segmented-btn::after { content: ""; position: absolute; inset: -3px 0; }
.segmented-btn:hover:not([aria-pressed="true"]) { color: var(--ink); }
.segmented-btn[aria-pressed="true"] {
  color: var(--ink);
  font-weight: 620;
  background: var(--thumb);
  box-shadow: var(--thumb-lift), inset 0 0 0 1px var(--thumb-edge);
}
/* In a roomy column (the pane, a tablet) a two- or three-way choice does not
 * stretch to 880: it keeps a control's width. */
@container (min-width: 520px) {
  .segmented { max-width: 420px; }
}

/* js/meal_fields.js — the optional model fields on a food form (fb_53beb428),
 * shared by Today and Meals, so it lives here rather than in either tab's
 * sheet. Seven provenance classes do not fit one row at 320px: the group
 * wraps into rows of three rather than scrolling sideways. */
.meal-fields { display: grid; gap: 10px; }
.meal-fields-body { display: grid; gap: 10px; }
.meal-fields-body[hidden] { display: none; }
.meal-fields-toggle { justify-self: start; }
.meal-f-class .segmented,
.meal-f-form .segmented { flex-wrap: wrap; }
.meal-f-class .segmented-btn { flex: 1 1 30%; white-space: normal; }
.meal-fields-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

/* The chart tables are the accessibility affordance — they are exactly the
 * control that must not be a 32px sliver. A full tap target, drawn as a
 * tertiary row (a hairline above, the words, a count, a chevron that turns
 * as it opens) so it reads as a control and never as an orphaned heading. */
.chart-table { border-top: 1px solid var(--line); }
.chart-table > summary {
  list-style: none;
  cursor: pointer;
  color: var(--ink-2);
  font: 500 var(--t-sub-size)/1.2 var(--font);
  min-height: var(--tap);
  display: flex;
  align-items: center;
  gap: 8px;
  -webkit-tap-highlight-color: transparent;
}
.chart-table > summary::-webkit-details-marker { display: none; }
.chart-table > summary:hover { color: var(--ink); }
.chart-table-count { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-3); font-weight: 450; }
.chart-table-chev {
  width: 18px;
  height: 18px;
  margin-left: auto;
  color: var(--ink-3);
  transition: transform .2s var(--ease);
}
.chart-table[open] > summary .chart-table-chev { transform: rotate(90deg); }

/* --------------------------------------------------------------- date nav */

/* ‹  Sat 29 Aug  ›  — built by ui.dateNav(). One row, three tap targets, and a
 * transparent native date input under the middle one. */
.datenav {
  display: grid;
  gap: 4px;
  background: var(--surface);
  border-radius: var(--radius);
  padding: 4px;
}

.datenav-row { display: flex; align-items: center; gap: 4px; }

.datenav-btn {
  flex: none;
  display: grid;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--ink-2);
  font: 400 var(--fs-7)/1 var(--font);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color .15s var(--ease), background-color .15s var(--ease);
}
.datenav-btn .icon { width: 22px; height: 22px; }
.datenav-btn:hover:not(:disabled) { color: var(--ink); background: var(--surface-2); }
/* The future holds no data, so forward is genuinely unavailable — inert and
 * in --ink-3 at full strength, never hidden and never faded to nothing, so
 * the edge of the record is legible in light as well as dark. */
.datenav-btn:disabled { color: var(--ink-3); opacity: .5; cursor: default; }

/* The positioning context for the transparent input. */
.datenav-day-wrap {
  position: relative;
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
}

.datenav-day {
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--tap);
  display: grid;
  gap: 1px;
  align-content: center;
  justify-items: center;
  padding: 4px 8px;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--ink);
  font: 600 var(--fs-5)/1.2 var(--font);
  cursor: pointer;
}
.datenav-day:hover { background: var(--surface-2); }
/* "Sat 29 Aug" changes under the finger as you step through days; tabular
 * figures stop the whole row shifting when 9 becomes 10. */
.datenav-label {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
/* Sentence case, not an eyebrow (REVAMP-DESIGN §4.5: no all-caps labels). */
.datenav-note {
  font: 600 var(--fs-3)/1.2 var(--font);
  color: var(--ink-3);
}
.datenav-note:empty { display: none; }

/* Rendered (showPicker() needs a rendered element) but invisible and inert:
 * the button above it is what the finger and the keyboard reach. The tag is in
 * the selector so this beats `input.input` from the field styles above. */
input.datenav-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  min-height: 0;
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  opacity: 0;
  pointer-events: none;
}

/* ----------------------------------------------------------- warn badge -- */

/* A caution, not an alarm and not a hue: the palette has exactly one accent, so
 * a warning is red the way everything urgent here is red — as a 3px edge and a
 * small mark, not a filled amber slab that would out-shout the primary action. */
.badge-warn {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  border-left: 3px solid var(--red);
  background: var(--surface-2);
  color: var(--ink-2);
  font: 500 var(--fs-3)/1.45 var(--font);
}
.badge-warn::before {
  content: "!";
  flex: none;
  width: 17px;
  height: 17px;
  margin-top: 1px;
  border-radius: 999px;
  background: var(--red);
  color: var(--on-red);
  font: 700 var(--fs-1)/17px var(--font);
  text-align: center;
}
.badge-warn strong { color: var(--ink); font-weight: 600; }

/* ------------------------------------------------------------------ sheet */

.sheet-backdrop {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: var(--scrim);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  animation: fade .18s var(--ease);
}

.sheet {
  width: 100%;
  max-width: var(--shell);
  max-height: 88dvh;
  /* A size container like the face and the pane (§3.3): what a sheet holds
   * lays out for the sheet's width, a phone's or the 480 dialog's. */
  container: sheet / inline-size;
  /* The head stays, the body scrolls (ui.sheet): a column whose body takes
   * what is left and scrolls inside it, with its own bottom padding, so the
   * last control never touches the edge. */
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--surface);
  border-radius: var(--r-sheet) var(--r-sheet) 0 0;
  border-top: 1px solid var(--line);
  box-shadow: var(--lift-sheet);
  padding: 8px 0 0;
  animation: rise .3s var(--ease);
}
.sheet::before {
  content: "";
  flex: none;
  display: block;
  width: 36px;
  height: 5px;
  margin: 4px auto 8px;
  border-radius: 999px;
  background: var(--edge);
  opacity: .55;
}
/* ONE header on every sheet: the title left, the action (when there is one)
 * and a round × right. */
.sheet-head {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: var(--tap);
  padding: 0 14px 10px 20px;
  transition: box-shadow .15s var(--ease);
}
.sheet.is-untitled .sheet-head { min-height: 0; padding-bottom: 2px; }
.sheet.is-scrolled .sheet-head { box-shadow: 0 1px 0 var(--line); }
.sheet-title {
  flex: 1 1 auto;
  min-width: 0;
  font: 620 19px/1.25 var(--font);
  letter-spacing: -.015em;
  text-wrap: balance;
}
.sheet-corner { flex: none; display: flex; align-items: center; gap: 6px; }
/* The ×: a 36px disc in a 44px hit. If a caller relabels it with a word
 * ("Close" once a save cannot be repeated) it becomes a quiet pill. */
.sheet-x {
  position: relative;
  flex: none;
  display: inline-grid;
  place-items: center;
  min-width: 36px;
  height: 36px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--ink-2);
  font: 560 var(--t-sub-size)/1 var(--font);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color .15s var(--ease), background-color .15s var(--ease);
}
.sheet-x::before { content: ""; position: absolute; inset: -4px; }
.sheet-x:not(:has(.icon)) { padding: 0 14px; color: var(--ink); }
.sheet-x:hover { color: var(--ink); background: var(--quiet-press); }
.sheet-x .icon { width: 18px; height: 18px; stroke-width: 2.25; }
.sheet-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: grid;
  align-content: start;
  gap: 12px;
  padding: 2px 20px calc(24px + env(safe-area-inset-bottom));
}
/* More below: the last lines fade into the edge instead of being cut by it. */
.sheet.has-more .sheet-body {
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 36px), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 36px), transparent);
}

.confirm { display: grid; gap: 16px; }
.confirm-msg { font: 400 var(--fs-5)/1.5 var(--font); color: var(--ink-2); }
.confirm-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }

@keyframes rise { from { transform: translateY(24px); opacity: .4; } to { transform: none; opacity: 1; } }
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes dialog-in { from { transform: translateY(8px) scale(.985); opacity: 0; } to { transform: none; opacity: 1; } }

/* ------------------------------------------------------------------ toast */

#toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--tap) + 40px + env(safe-area-inset-bottom));
  z-index: 80;
  transform: translateX(-50%) translateY(8px);
  max-width: calc(100vw - 32px);
  background: var(--surface-2);
  border: 1px solid var(--line);
  color: var(--ink);
  font: 600 var(--fs-3)/1.4 var(--font);
  border-radius: var(--radius-sm);
  padding: 12px 18px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .18s var(--ease), transform .18s var(--ease);
}
#toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ------------------------------------------------------------ queue badge */

/* js/queue.js mounts this before any stylesheet is guaranteed to exist, so it
 * carries an inline fallback of its own. Once base.css is here the stylesheet
 * is the authority for the two things the inline string gets wrong: the font
 * (it hardcodes a stack that drops SF Pro / Segoe UI Variable / Roboto) and the
 * offset (72px, which lands under the toast — the toast occupies 84–126px, so
 * the badge starts above it). Hence the two !important flags: they beat the
 * element's own style attribute, and nothing else in the app uses them. */
.queue-badge {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(132px + env(safe-area-inset-bottom, 0px)) !important;
  z-index: 60;
  max-width: calc(100vw - 32px);
  padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--ink);
  font: 500 var(--fs-3)/1.2 var(--font) !important;
  box-shadow: 0 6px 20px color-mix(in srgb, var(--scrim) 80%, transparent);
  pointer-events: none;
}

/* ----------------------------------------------------------------- charts */

.chart { display: grid; gap: 8px; }
.chart-plot { position: relative; }
.chart-svg { display: block; width: 100%; height: auto; }

/* charts.js writes font-size as an SVG presentation attribute; the face and the
 * figure style are the stylesheet's business. An axis is a ruler — its numbers
 * have to sit on a fixed pitch or the gridlines look crooked. */
.chart-axis {
  font-family: var(--font);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
}
/* A label drawn ON the plot (a goal, a rule, the last value) is cut out of
 * the gridlines under it by a halo in the ground it sits on, so "Goal 1,750"
 * never reads as struck through by the 2,000 line. */
.card, .tile, .callout, .sheet { --chart-ground: var(--surface); }
#pane { --chart-ground: var(--pane); }
.chart-svg text.chart-label,
.chart-svg .chart-goal text,
.chart-svg text.chart-endlabel {
  paint-order: stroke;
  stroke: var(--chart-ground);
  stroke-width: 4px;
  stroke-linejoin: round;
}
.chart-point { transition: r .12s var(--ease); }
.chart-hover { cursor: crosshair; }
.chart-cell { cursor: default; }
.chart-bar, .chart-cell, .chart-dot, .chart-cross { transition: opacity .12s var(--ease); }
.chart-grid { shape-rendering: crispEdges; }

.chart-tip {
  position: absolute;
  z-index: 5;
  transform: translate(-50%, -130%);
  pointer-events: none;
  opacity: 0;
  white-space: nowrap;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-xs);
  padding: 5px 9px;
  font: 600 var(--fs-2)/1.4 var(--font);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  transition: opacity .12s var(--ease);
}
.chart-tip.is-visible { opacity: 1; }

.chart-legend { display: flex; flex-wrap: wrap; gap: 14px; }
.chart-legend-item { display: inline-flex; align-items: center; gap: 6px; }
.chart-swatch { width: 12px; height: 12px; flex: none; }
.chart-legend-label { font: 500 var(--fs-2)/1 var(--font); color: var(--ink-2); }

/* The ring asks for a hard pixel width; cap it so three of them in a row can
 * never out-measure their track. The SVG inside is width:100% and scales. */
.chart-ring-plot { margin: 0 auto; max-width: 100%; }

/* ---- the ring, per Build Spec 4 C4 --------------------------------------
 * Round 12 retired the ring everywhere but the Lab's Adherence report
 * (tab_trends.js, three rings); ui.ringTile is a different, smaller mark.
 * charts.js paints the arc with a `stroke` presentation attribute; a stylesheet
 * rule outranks a presentation attribute, so the line below colours the ring
 * in the person's mark tier without anyone editing charts.js, over budget
 * included (charts.js switches its own stroke to red there and this outranks
 * it: over budget is information, not an alarm). */
.chart-ring .chart-ring-arc { stroke: var(--accent-mark); }

/* A ring at zero draws no bead at 12 o'clock: a round cap on a zero-length
 * dash is still rendered. charts.js labels every ring `"<value> of <max>
 * <label>"` for screen readers, so a ring whose value formats to zero says so
 * in its own accessible name, at every `size`. Two forms because the kit's
 * `dp` may be 0 or 1. */
.chart-ring-svg[aria-label^="0 of "] .chart-ring-arc,
.chart-ring-svg[aria-label^="0.0 of "] .chart-ring-arc { stroke-linecap: butt; }

/* The one big numeral. */
.chart-ring-value {
  font-family: var(--font);
  letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
}
.chart-ring-sub { font-variant-numeric: tabular-nums; }

/* The heatmap is a calendar, not a rubber sheet: a cell is 13px whatever the
 * window length, so the box is sized from the artwork (height 116px = 7 rows of
 * 13 + 3.5 gap) and long windows scroll inside the card instead of squashing
 * into a vertical slab. `width: max-content` keeps the plot exactly as wide as
 * the strip while it fits, which is what the hover tooltip's percentages are
 * measured against. */
.chart-heatmap-plot {
  width: max-content;
  max-width: 100%;
  overflow-x: auto;
  overscroll-behavior-x: contain;
}
.chart-heatmap-svg {
  width: auto;
  height: 116px;
  max-width: none;
  flex: none;
}

.chart-spark { display: block; width: 100%; height: 34px; }

.chart-table { margin-top: 2px; }
.chart-table-scroll { overflow-x: auto; margin-top: 8px; }
.chart-table table { width: 100%; border-collapse: collapse; font-size: var(--fs-3); }
.chart-table th, .chart-table td {
  text-align: right;
  padding: 6px 8px;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.chart-table th:first-child, .chart-table td:first-child { text-align: left; }
.chart-table th { color: var(--ink-3); font-weight: 600; }
.chart-table td { color: var(--ink-2); }

.progress { display: grid; gap: 6px; }
.progress-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.progress-label { font: 500 var(--fs-3)/1.4 var(--font); color: var(--ink-2); }
/* "142 / 175 g" — a running figure against a fixed one, so tabular. */
.progress-value {
  font: 600 var(--fs-3)/1.4 var(--font);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
.progress-track {
  height: 6px;
  border-radius: 3px;
  background: var(--surface-2);
  overflow: hidden;
}
.progress-fill {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: var(--good);   /* on track is white, never green */
  transition: width .25s var(--ease);
}
.progress-red, .progress .is-over .progress-fill { background: var(--red); }
.progress.is-over .progress-value { color: var(--red); }

/* ------------------------------------------------------------- responsive */

/* WIDTH RULES ANSWER TO THE COLUMN, NOT THE VIEWPORT (§3.3). #view (the
 * face), #pane and a sheet are size containers, so a component laid out for
 * the phone is laid out the same in the 420 wide face column, and gets its
 * roomier layout only where its own column is roomy (the pane, a tablet).
 * A container is its content box: a viewport of V on a phone is a column of
 * V - 32 (the 16px gutters), so the old `min-width: 720px` is 688 here. The
 * old `max-width: 390px` ("a phone") is 376: the 390 phone's column is 358
 * and the wide face column's is 376 (420 less its 22px gutters), and the two
 * must lay out alike. Every charts.js chart now draws at the width it is
 * shown at (.chart-fitted), so no chart needs the old plot cap. */
@container (max-width: 376px) {
  .card { padding: 16px; }
}

@container (min-width: 688px) {
  .card { padding: 20px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}


/* ------------------------------------------------- narrow and short --- */

/* Under 360 wide (§3.3): the face header's switch shows initials, which
   frees the room a 30px title needs at 320 ("Saturday" beside the switch).
   The rail's switch on wide keeps its names. The hero compacts to 48 here
   and under 720 tall, where Alex's morning block must still show weigh-in,
   movement and doses on a 375 x 667 phone. */
@media (max-width: 359.98px) {
  .top .person-name { display: none; }
  .top .person-ini { display: inline; }
  .top .person-btn { padding: 0 9px; }
  .top-row { gap: 8px; }
}

@media (max-width: 359.98px), (max-height: 719.98px) {
  :root { --t-hero-size: 48px; }
}

/* --------------------------------------------------------- the PIN pad --- */

/* js/pin.js — the entry that lives inside a sheet. The same idea as the login
   pad, one size down: dots, ten keys, no input. Keys are 52px, well over the
   44px floor, and square so the radius makes circles. */
.pin { display: grid; justify-items: center; gap: 14px; padding: 4px 0 2px; }

.pin-label { font: 500 var(--fs-4)/1.3 var(--font); color: var(--ink-2); }

.pin-dots { display: flex; gap: 14px; min-height: 14px; align-items: center; }

.pin-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 1.5px solid var(--ink-3);
  transition: background-color 120ms var(--ease), border-color 120ms var(--ease);
}
.pin-dot.is-on { background: var(--ink); border-color: var(--ink); }
/* a fifth-or-later digit: the last dot wears the accent so a long PIN is
   visibly "more than four" without drawing more dots */
.pin-dot.is-extra { background: var(--accent-mark); border-color: var(--accent-mark); }

.pin-msg {
  min-height: 18px;
  font: 400 var(--fs-3)/1.3 var(--font);
  color: var(--red);
  text-align: center;
  text-wrap: balance;
}

.pin-pad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  width: 100%;
  max-width: 232px;
}

.pin-key {
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  min-height: 52px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--ink);
  font: 400 var(--fs-8)/1 var(--font);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
  transition: background-color 120ms var(--ease), transform 120ms var(--ease);
}
.pin-key:active { background: var(--line); transform: scale(.95); }
.pin-key[disabled] { opacity: .35; pointer-events: none; }
.pin-key-delete { border-color: transparent; background: none; color: var(--ink-2); font-size: var(--fs-7); }
.pin-key-ok { border-color: transparent; background: var(--accent); color: var(--on-accent); font-size: var(--fs-7); }
.pin-key-ok[hidden] { display: none; }

/* current, new, again. Three dashes, the current one in the accent. */
.pin-sheet { display: grid; gap: 4px; }

.pin-steps {
  display: flex;
  justify-content: center;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.pin-step {
  width: 28px;
  height: 3px;
  border-radius: 2px;
  background: var(--line);
  transition: background-color 160ms var(--ease);
}
.pin-step.is-done { background: var(--ink-3); }
.pin-step.is-now { background: var(--accent-mark); }

/* ---------------------------------------------------------- feedback --- */

/* js/feedback.js. Two kinds, one box, a line saying what the phone already
   knows, one button. The context line is --ink-3: it is reassurance, not
   something to read. */
.feedback { display: grid; gap: 12px; }

.feedback-field { display: grid; gap: 4px; }

.feedback-text {
  min-height: 132px;
  line-height: 1.5;
}

.feedback-count {
  justify-self: end;
  font: 400 var(--fs-1)/1.3 var(--font);
  color: var(--ink-3);
  min-height: 1.3em;
}

.feedback-where {
  font: 400 var(--fs-2)/1.45 var(--font);
  color: var(--ink-3);
  overflow-wrap: anywhere;
}

@media (prefers-reduced-motion: reduce) {
  .pin-dot, .pin-key, .pin-step { transition: none; }
}


/* ================================================================ screens =
 *
 * 2026-09-02, from Jordan's references. A screen is what a row opens: a back
 * arrow and the corner actions on one line, a LARGE title, one subtitle, the
 * content. The app header hides while a screen is up — the screen owns the
 * top — and the tab bar stays, because a tab is always a way out.
 */

:root[data-screen] .top { display: none; }

.screen {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 8px;
  padding-top: calc(8px + env(safe-area-inset-top));
}
/* A push (§4.6): a 24px slide and a fade, 260ms, on the push only. */
.screen.is-entering { animation: screen-in .26s var(--ease) both; }
@keyframes screen-in { from { transform: translateX(24px); opacity: 0; } to { transform: none; opacity: 1; } }

.screen-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-height: var(--tap);
}

/* The back link says where it goes, in the person's ink: "‹ Today",
 * "‹ Upper B"; the first level of the wide pane reads "× Close" (§5 "Screen
 * header"). The mark is drawn (icons.js "back", "close"). 44px tall; as wide
 * as its words. */
.screen-back {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  min-width: var(--tap);
  max-width: 70%;
  min-height: var(--tap);
  margin-left: -6px;
  padding: 0 10px 0 0;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--accent-ink);
  font: 500 var(--t-row-size)/1 var(--font);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.screen-back:active { background: var(--surface); }
.screen-back:hover .screen-back-label { text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
.screen-back-mark { flex: none; display: inline-flex; }
.screen-back-mark .icon { width: 24px; height: 24px; }
.screen-back-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.screen-actions { display: flex; align-items: center; gap: 8px; }

.screen-title {
  /* The title sits right under the back row (§10 mockups: its centre about
   * 38 below the back link's): the grid's 8px gap, less 6. */
  margin-top: -6px;
  font: var(--t-title);
  letter-spacing: -.02em;
  overflow-wrap: anywhere;
  text-wrap: balance;
}

/* Hold any title for half a second to report a problem (§2.6, js/feedback.js
 * watchTitleHold). iOS starts a text selection or its callout on a long press
 * before any listener is asked, which cancels the hold: titles are not text a
 * person selects. */
#page-title, .screen-title {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

.screen-sub {
  font: 400 var(--t-body-size)/1.4 var(--font);
  color: var(--ink-2);
  text-wrap: pretty;
}

.screen-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 12px;
  margin-top: 10px;
}

/* A short screen's one visual (Water's gauge, an empty state) sits in the
 * middle of the space the screen has, not hanging under the title with the
 * rest of the phone blank: `ui.screenStage(node)` (§ the A pass). It takes the
 * viewport less the chrome, and centres what it holds; a tall screen simply
 * grows past it. */
.screen-stage {
  display: grid;
  align-content: center;
  justify-items: stretch;
  gap: var(--s-6);
  min-height: calc(100dvh - 300px - env(safe-area-inset-top) - env(safe-area-inset-bottom));
  padding: var(--s-4) 0 var(--s-8);
}
.screen-stage.is-compact { min-height: min(420px, calc(100dvh - 300px)); }

/* A card presented AS a screen loses its box — the screen is the box — and
 * its own title, which the screen title has already said. A card a screen
 * host strips (Body's, Trends' inside Progress) loses its ring with it: a
 * ring round a box with no ground reads as two stray rules. */
.screen-body > .card { background: none; padding: 0; border-radius: 0; box-shadow: none; }
[class*="screen-host"] > .card { box-shadow: none; }
.screen-body > .card > .card-title { display: none; }

/* -------------------------------------------------------------- nav rows --- */

/* The row that opens a screen: on the black ground, no card, a hairline
 * between rows. Sixty tall so the label and its line under it breathe. */
.nav-list { display: grid; }

.nav-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 60px;
  /* No inline inset: a row's title starts on the page gutter, the same edge
   * as the screen title, the section heads and the hairlines. Any wash is
   * drawn on ::before, reaching past the edges symmetrically. */
  padding: 10px 0;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.nav-row + .nav-row { border-top: 1px solid var(--line); }
.nav-row::before {
  content: "";
  position: absolute;
  inset: 0 -10px;
  z-index: -1;
  border-radius: var(--r-field);
  background: transparent;
  transition: background-color .12s var(--ease);
}
.nav-row { isolation: isolate; }
.nav-row:active::before { background: var(--surface-2); }
@media (hover: hover) {
  .nav-row:hover::before { background: color-mix(in srgb, var(--surface-2) 60%, transparent); }
}

/* The leading mark: a number in a circle, a check, an icon. */
.nav-row-lead {
  flex: none;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1.5px solid var(--line);
  color: var(--ink-2);
  font: 600 var(--fs-3)/1 var(--font);
}
.nav-row-lead.is-done { background: var(--ink); border-color: var(--ink); color: var(--bg); }
.nav-row-lead.is-live { border-color: var(--accent-mark); color: var(--ink); }

.nav-row-main { flex: 1 1 0; min-width: 0; display: grid; gap: 2px; }

.nav-row-label {
  font: 600 var(--t-row-size)/1.25 var(--font);
  letter-spacing: -.012em;
  overflow-wrap: anywhere;
}

/* The row's one line under its title: the §4.5 sub size in --ink-2, one or
 * two lines at most, wrapping "pretty" so no word is left on its own. */
.nav-row-sub {
  font: 400 var(--t-sub-size)/1.35 var(--font);
  color: var(--ink-2);
  text-wrap: pretty;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

/* The current value, centred on the row with the chevron (never on the title
 * line alone), in tabular figures. */
.nav-row-value {
  flex: none;
  align-self: center;
  max-width: 42%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 450 var(--t-body-size)/1.3 var(--font);
  color: var(--ink-2);
}

/* The chevron: the drawn mark (icons.js "chevron"), its right edge on the
 * gutter, the same edge a tick or a pill ends on. */
.nav-row-chev {
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 20px;
  height: 20px;
  margin-right: -4px;
  color: var(--ink-3);
  font-size: var(--fs-7);
  line-height: 1;
}
.nav-row-chev .icon { width: 20px; height: 20px; }

/* On wide the face stays beside the pane, and the row that opened it is
 * washed until the pane closes (§3.4; §5 "Nav row: selected on wide: wash,
 * 12 radius"). js/router.js sets the attribute. The wash reaches 12px past
 * the row's edges, as a selected row does; the hairlines either side of it
 * give way. Text on the wash is --ink or --ink-2 only (§4.2). */
.nav-row[data-pane-open] {
  border-top-color: transparent;
}
.nav-row[data-pane-open]::before,
.nav-row[data-pane-open]:hover::before {
  inset: 0 -12px;
  background: var(--accent-wash-bg);
}
.nav-row[data-pane-open] + .nav-row { border-top-color: transparent; }
.nav-row[data-pane-open] .nav-row-sub,
.nav-row[data-pane-open] .nav-row-value,
.nav-row[data-pane-open] .nav-row-chev { color: var(--ink-2); }

/* ------------------------------------------------------------ the pills --- */

/* Jordan's white pill — the ONE primary in a screen's corner. Ink on the
 * ground reversed, so it is the brightest thing on the screen by design. */
.pill-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap);
  padding: 0 18px;
  border: 0;
  border-radius: 999px;
  background: var(--ink);
  color: var(--bg);
  font: 600 var(--fs-4)/1 var(--font);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.pill-btn:active { filter: brightness(.9); }
.pill-btn:disabled, .pill-btn[aria-disabled="true"] { opacity: .45; cursor: default; }
.pill-btn-ghost { background: var(--quiet-bg); color: var(--ink); }

/* A round icon button for the corners (the ⓘ, the sliders). */
.icon-btn {
  width: var(--tap);
  height: var(--tap);
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 999px;
  background: var(--quiet-bg);
  color: var(--ink);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.icon-btn:hover { background: var(--quiet-press); }


/* ------------------------------------------------------------ the week --- */

/* Cal AI's week strip, under the day row: seven circles, Monday to Sunday.
 * One tap to any day of this week; the picker above still opens any other. */
.datenav-week {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 2px;
}

.datenav-day-btn {
  position: relative;
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 3px;
  min-height: var(--tap);
  padding: 5px 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--ink-3);
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color .15s var(--ease), background-color .15s var(--ease);
}

.datenav-dow {
  font: 600 var(--fs-1)/1 var(--font);
}

/* The number is the circle: 30px, centred, so the selected day is a filled
 * disc rather than a highlighted rectangle. */
.datenav-dnum {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  font: 600 var(--fs-3)/1 var(--font);
  font-variant-numeric: tabular-nums;
  color: var(--ink-2);
  transition: background-color .15s var(--ease), color .15s var(--ease);
}

.datenav-day-btn.is-today .datenav-dnum { box-shadow: inset 0 0 0 1.5px var(--edge); color: var(--ink); }
.datenav-day-btn.is-on { color: var(--ink); }
.datenav-day-btn.is-on .datenav-dnum {
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: none;
}
.datenav-day-btn:disabled { cursor: default; }
.datenav-day-btn:disabled .datenav-dnum { color: var(--ink-3); font-weight: 450; }
.datenav-day-btn:disabled .datenav-dow { color: var(--ink-3); }
/* Seven days in a 320 column are 38 wide: the hit reaches 44 anyway. */
.datenav-day-btn::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 0;
  width: max(100%, var(--tap));
  transform: translateX(-50%);
}
.datenav-day-btn:not(:disabled):not(.is-on):hover .datenav-dnum { background: var(--surface-2); }

/* ------------------------------------------------------- the ring tile --- */

/* ui.ringTile — "x of y" as a ring with the remainder inside it. Three side by
 * side are the macro row; one on its own is a stat that deserves a shape. */
.ringtile {
  display: grid;
  justify-items: center;
  gap: 2px;
  min-width: 0;
  padding: 12px 4px 10px;
  background: var(--surface-2);
  border-radius: var(--radius);
}

.ringtile-plot { position: relative; display: grid; place-items: center; width: 64px; height: 64px; }

.ringtile-svg { width: 64px; height: 64px; display: block; }

.ringtile-value {
  position: absolute;
  font: 700 var(--fs-5)/1 var(--font);
  color: var(--ink);
}

.ringtile-label {
  font: 600 var(--fs-2)/1.3 var(--font);
  color: var(--ink);
  text-align: center;
}

.ringtile-sub { font: 500 var(--fs-1)/1.3 var(--font); color: var(--ink-3); }

.ringtile.is-past .ringtile-value { color: var(--red); }


/* ========================================================== the kit ====
 *
 * REVAMP-DESIGN §5 (WP5): the components js/ui.js and js/charts.js build, in
 * the §4.5 scale and the §4.6 shapes. Three control shapes mean three things:
 * an OUTLINED chip (1px --edge) is a one-tap write or a choice; a SOFT pill
 * (--surface-2 in dark, white with lift in light) is an action; the FILLED
 * pill (the accent under --on-accent) is the next action, at most one per
 * screen. Text on --accent-wash is --ink or --ink-2 only; nothing quiet or
 * red sits on --raised (cli/test_theme_contrast.py reads this file).
 */

/* ------------------------------------------------------------- the pills */

.pill-btn.pill-filled,
.pill-btn.pill-quiet,
.pill-btn.pill-outline {
  position: relative;
  gap: 6px;
  min-height: 40px;
  height: 40px;
  padding: 0 16px;
  font: 560 var(--t-body-size)/1 var(--font);
  white-space: nowrap;
}
/* 40 tall, 44 to the finger: the hit area reaches past the pill (§4.6). */
.pill-btn.pill-filled::before,
.pill-btn.pill-quiet::before,
.pill-btn.pill-outline::before,
.chip::before {
  content: "";
  position: absolute;
  inset: -2px 0;
}
.pill-btn.pill-filled,
.pill-btn.pill-quiet,
.pill-btn.pill-outline {
  -webkit-tap-highlight-color: transparent;
  transition: background-color .16s var(--ease), color .16s var(--ease), box-shadow .16s var(--ease),
              transform .12s var(--ease);
}
.pill-btn.pill-filled { background: var(--accent); color: var(--on-accent); font-weight: 620; box-shadow: none; }
/* Quiet is FLAT in both modes (the A pass): a surface step, no lift, so a
 * column of quiet actions never reads as floating buttons and only the one
 * filled pill stands up off the ground. */
.pill-btn.pill-quiet { background: var(--quiet-bg); color: var(--ink); box-shadow: none; }
.pill-btn.pill-outline { background: transparent; color: var(--ink); box-shadow: inset 0 0 0 1px var(--edge); }
.pill-btn.pill-quiet:hover { background: var(--quiet-press); }
.pill-btn.pill-outline:hover { background: var(--surface-2); }
.pill-btn.pill-filled:active,
.pill-btn.pill-quiet:active,
.pill-btn.pill-outline:active { transform: scale(.97); filter: none; }
/* Disabled is a state, never a muddy version of the action: no fill, the
 * label in --ink-3 on a dashed edge; it keeps its words (and says why on a
 * tap when it has a reason). The filled pill turns to its accent the moment
 * it becomes enabled, with the colour transition above. */
.pill-btn.pill-filled:disabled, .pill-btn.pill-filled[aria-disabled="true"],
.pill-btn.pill-quiet:disabled, .pill-btn.pill-quiet[aria-disabled="true"],
.pill-btn.pill-outline:disabled, .pill-btn.pill-outline[aria-disabled="true"] {
  opacity: 1;
  background: transparent;
  color: var(--ink-3);
  font-weight: 560;
  box-shadow: inset 0 0 0 1px var(--line-strong);
  transform: none;
}
.pill-btn.pill-sm { height: 34px; min-height: 34px; padding: 0 13px; font-size: var(--t-sub-size); }
.pill-btn.pill-sm::before { inset: -5px 0; }
.pill-btn .icon { width: 18px; height: 18px; flex: none; }

/* ------------------------------------------------------------- the chips */

.chips { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 40px;
  padding: 0 15px;
  border: 1px solid var(--edge);
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  font: 500 var(--t-body-size)/1 var(--font);
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color .14s var(--ease), border-color .14s var(--ease);
}
.chip-more { color: var(--ink-2); }
.chip:hover:not(.is-done):not([aria-pressed="true"]):not([aria-disabled="true"]) { background: var(--surface-2); }
.chip:active:not([aria-disabled="true"]) { transform: scale(.97); }
.chip .chip-mark { width: 16px; height: 16px; flex: none; }
.chip .chip-pending { width: 14px; height: 14px; flex: none; color: var(--ink-2); }
/* Done, and a selected choice: ONE selected look across the kit — the
 * person's wash under an accent-ink edge, the words in ink. */
.chip.is-done,
.chip[aria-pressed="true"] { background: var(--accent-wash); border-color: var(--accent-ink); color: var(--ink); }
.chip.is-done .chip-mark { animation: check-in .22s var(--ease) both; }
@keyframes check-in { from { transform: scale(.4); opacity: 0; } to { transform: none; opacity: 1; } }
.chip[aria-disabled="true"] { color: var(--ink-3); border-style: dashed; border-color: var(--line-strong); cursor: default; }
/* A rail of chips (a lift picker, DEXA's regions): ONE line that scrolls
 * sideways under the finger, with the last chip fading into the edge so it
 * is plain there is more; it never wraps into a five-row cloud. */
.chips.is-rail {
  flex-wrap: nowrap;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  margin: 0 calc(-1 * var(--pad));
  padding: 2px var(--pad);
  scroll-padding-inline: var(--pad);
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--pad), #000 calc(100% - 40px), transparent);
  mask-image: linear-gradient(to right, transparent 0, #000 var(--pad), #000 calc(100% - 40px), transparent);
}
.chips.is-rail::-webkit-scrollbar { display: none; }
.chips.is-rail > .chip { flex: none; }

/* ------------------------------------------------------ tags and badges */

.tag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 24px;
  padding: 0 9px;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--ink-2);
  font: 500 var(--t-cap-size)/1 var(--font);
  white-space: nowrap;
  vertical-align: middle;
}
.tag-dot { width: 7px; height: 7px; flex: none; border-radius: 999px; background: var(--ink-3); }
.tag-dot-hollow { background: transparent; box-shadow: inset 0 0 0 1.5px var(--ink-3); }
.tag-dot-ring { background: transparent; box-shadow: inset 0 0 0 1.5px var(--accent-ink); }
.tag-dot-solid { background: var(--ink-2); }
.tag-dashed { background: transparent; border: 1px dashed var(--ink-3); }
/* "Past the cap": red text and dot on --surface-2 (4.83 / 4.56), never on
 * the red wash (§4.2). */
.tag-limit { color: var(--red); background: var(--surface-2); }
.tag-limit .tag-dot { background: var(--red); }

.prov {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--ink-2);
  font: 500 var(--t-cap-size)/1 var(--font);
  white-space: nowrap;
}

/* ----------------------------------------------------------------- hero */

.hero { padding: var(--s-3) 0 var(--s-4); }
.hero-top { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--s-3); }
.hero-n { display: flex; align-items: baseline; gap: var(--s-2); min-width: 0; }
.hero-value {
  font: var(--t-hero);
  font-stretch: 85%;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}
.u { font-weight: 450; font-stretch: 100%; color: var(--ink-2); letter-spacing: 0; }
.hero-unit { font: 500 var(--t-row-size)/1 var(--font); }
/* A figure a write changed settles in (§4.6: numbers never count up); the
 * caller adds .is-changed only when the figure moved. */
.hero-value.is-changed { animation: hero-settle 320ms var(--ease) 1 both; }
@keyframes hero-settle {
  from { opacity: .25; transform: translateY(5px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) { .hero-value.is-changed { animation: none; } }
.hero-2 { text-align: right; flex: none; }
.hero-2 b {
  display: block;
  font: 600 26px/1 var(--font);
  font-stretch: 88%;
  letter-spacing: -.01em;
}
.hero-2 span { font: var(--t-cap); color: var(--ink-2); }
.hero-cap {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font: var(--t-cap);
  color: var(--ink-2);
}
.hero-cap b { font-weight: 600; color: var(--ink); }
.hero-cap-btn {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  min-height: var(--tap);
  margin: -14px 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-2);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.hero-chev { width: 16px; height: 16px; color: var(--ink-3); flex: none; }
.hero-cap-right { text-align: right; }
.hero-foot { display: flex; align-items: center; gap: var(--s-2); margin-top: 14px; }

/* ---------------------------------------------------------------- meter */

.meter { position: relative; height: 12px; margin: var(--s-4) 0 6px; }
.meter-track { position: absolute; inset: 0; border-radius: 6px; background: var(--surface-2); }
.meter-seg { position: absolute; top: 0; bottom: 0; border-radius: 3px; }
.meter-eaten { background: var(--accent-mark); }
.meter-plan { background: var(--accent-band); box-shadow: inset 0 0 0 1.5px var(--accent-mark); }
/* Room held for a meal nobody planned (MEAL-PLANNING-SPEC §6.7): dotted,
 * never the accent, the same mark as the board's held segment. */
.meter-held { box-sizing: border-box; border: 1.25px dotted var(--ink-2); background: var(--surface-2); }
.meter-over { background: var(--red); }
.meter-limit {
  position: absolute;
  top: -5px;
  bottom: -5px;
  width: 2px;
  margin-left: -1px;
  border-radius: 1px;
  background: var(--ink);
}

/* ----------------------------------------------------------- the day rail
 * A 2px line down the day; block heads with a node (hollow later, the now
 * disc with its halo, grey done); items with a ring to do, a grey disc with
 * a check when done. */

.dayrail { position: relative; padding: 4px 0 0 34px; }
/* The line starts AT the first node (never a stub above it) and fades out
 * after the last block instead of stopping dead: the day runs on (the A
 * pass). The first node's centre: 4 (the rail's top) + 17 + 12 for a head,
 * 4 + 15 + 10 for a folded block's item node. */
.dayrail::before {
  content: "";
  position: absolute;
  left: 11px;
  top: 33px;
  bottom: 0;
  width: 2px;
  border-radius: 1px;
  background: linear-gradient(to bottom, var(--line-strong) calc(100% - 56px), transparent);
}
.dayrail:has(> .dayrail-block:first-child.is-folded)::before { top: 29px; }
/* The one orchestrated moment (§4.6): the node that holds now takes a single
 * soft halo when now is new to the eye (the caller adds .is-arriving), then
 * rests. Never under reduced motion. */
.dayrail.is-arriving .dayrail-block.is-now:not(.has-now-marker) .dayrail-bnode,
.dayrail.is-arriving .dayrail-now-dot {
  animation: dayrail-halo 1100ms var(--ease) 240ms 1 both;
}
@keyframes dayrail-halo {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-mark) 50%, transparent); }
  100% { box-shadow: 0 0 0 14px color-mix(in srgb, var(--accent-mark) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .dayrail.is-arriving .dayrail-block.is-now .dayrail-bnode,
  .dayrail.is-arriving .dayrail-now-dot { animation: none; }
}
.dayrail-block { position: relative; }
.dayrail-head { position: relative; display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px; padding: 14px 0 6px; }
.dayrail-bnode {
  position: absolute;
  left: -34px;
  top: 17px;
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: var(--bg);
}
.dayrail-bnode::after {
  content: "";
  width: 12px;
  height: 12px;
  border-radius: 999px;
  background: var(--bg);
  box-shadow: inset 0 0 0 2px var(--edge);
}
.dayrail-block.is-now .dayrail-bnode { background: color-mix(in srgb, var(--accent-mark) 24%, var(--bg)); }
.dayrail-block.is-now .dayrail-bnode::after { background: var(--accent-mark); box-shadow: none; }
.dayrail-block.is-done .dayrail-bnode::after { background: var(--ink-3); box-shadow: none; }
.dayrail-name { font: var(--t-head); letter-spacing: -.01em; }
.dayrail-block.is-later .dayrail-name { font-size: 18px; color: var(--ink-2); }
.dayrail-when { font: 500 var(--t-sub-size)/1 var(--font); color: var(--ink-2); }
.dayrail-block.is-now .dayrail-when { font-weight: 600; color: var(--accent-ink); }
/* "Now" in time order (fb_a1002cd5): when the block holding now has past
 * items, the now disc leaves the head and sits after them, with the clock. */
.dayrail-block.has-now-marker .dayrail-bnode { background: var(--bg); }
.dayrail-block.has-now-marker .dayrail-bnode::after { background: var(--bg); box-shadow: inset 0 0 0 2px var(--edge); }
.dayrail-now {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 32px;
  padding: 4px 0;
  border-top: 1px solid var(--line);
  font: 600 var(--t-sub-size)/1.25 var(--font);
  color: var(--accent-ink);
}
.dayrail-now-dot {
  position: absolute;
  left: -34px;
  top: 50%;
  width: 24px;
  height: 24px;
  margin-top: -12px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent-mark) 24%, var(--bg));
}
.dayrail-now-dot::after { content: ""; width: 12px; height: 12px; border-radius: 999px; background: var(--accent-mark); }
/* Now breathes (owner, 30 Sep): a soft ring grows from the now dot and fades,
 * forever, on its own layer so the one-time arrival halo above is untouched.
 * Off under reduced motion. */
.dayrail-now-dot::before,
.dayrail-block.is-now:not(.has-now-marker) .dayrail-bnode::before {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 12px;
  height: 12px;
  border-radius: 999px;
  background: var(--accent-mark);
  opacity: 0;
  pointer-events: none;
  animation: dayrail-pulse 2400ms var(--ease) infinite;
}
@keyframes dayrail-pulse {
  0% { transform: scale(1); opacity: .45; }
  70% { transform: scale(2.6); opacity: 0; }
  100% { transform: scale(2.6); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .dayrail-now-dot::before,
  .dayrail-block.is-now:not(.has-now-marker) .dayrail-bnode::before { animation: none; display: none; }
}
.dayrail-summary { flex: 1 0 100%; font: var(--t-sub); color: var(--ink-2); }
.dayrail-block.is-compact .dayrail-summary { flex: 1 1 auto; }

.dayrail-item {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  column-gap: var(--s-3);
  padding: var(--s-3) 0;
}
.dayrail-item + .dayrail-item { border-top: 1px solid var(--line); }
/* On wide the rail item whose screen the pane shows (cook mode from a meal,
 * MEAL-PLANNING-SPEC §6.5) is washed as a selected row is (§3.4), until the
 * screen closes; js/tab_today.js sets the attribute. */
.dayrail-item[data-pane-open] { isolation: isolate; }
.dayrail-item[data-pane-open]::before {
  content: "";
  position: absolute;
  inset: 0 -12px;
  z-index: -1;
  border-radius: 12px;
  background: var(--accent-wash-bg);
}
.dayrail-item[data-pane-open],
.dayrail-item[data-pane-open] + .dayrail-item { border-top-color: transparent; }
.dayrail-node {
  position: absolute;
  left: -32px;
  top: 15px;
  width: 20px;
  height: 20px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: var(--bg);
  box-shadow: inset 0 0 0 1.5px var(--edge);
}
.dayrail-node .icon { width: 13px; height: 13px; stroke-width: 3; color: var(--bg); }
.dayrail-item.is-done .dayrail-node { background: var(--ink-3); box-shadow: none; }
.dayrail-item.is-pending .dayrail-node { background: var(--bg); box-shadow: inset 0 0 0 1.5px var(--ink-3); }
.dayrail-item.is-pending .dayrail-node .icon { color: var(--ink-2); stroke-width: 2.4; }
.dayrail-main {
  flex: 1 1 0%;
  min-width: 0;
  display: grid;
  gap: 2px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  text-align: left;
}
button.dayrail-main { cursor: pointer; min-height: var(--tap); align-content: start; }
.dayrail-title { font: var(--t-row); }
.dayrail-sub, .dayrail-reason { font: var(--t-sub); color: var(--ink-2); text-wrap: pretty; }
.dayrail-act { flex: none; display: flex; align-items: center; gap: var(--s-2); }
.dayrail-chev { width: 20px; height: 20px; color: var(--ink-3); margin-top: 2px; }
.dayrail-item > .chips { flex: 0 0 100%; margin-top: 10px; }
/* One line and nothing under it: the title, the node and the action all sit
 * on the row's middle. */
.dayrail-item.is-single { align-items: center; }
.dayrail-item.is-single .dayrail-node { top: 50%; margin-top: -10px; }
.dayrail-item.is-single button.dayrail-main { align-content: center; }
.dayrail-fold .dayrail-title { font-size: 16px; }
.dayrail-item.is-disabled .dayrail-title { color: var(--ink-2); }

/* ----------------------------------------------------------- food rows */

.frow { display: flex; align-items: center; gap: var(--s-3); min-height: 60px; padding: var(--s-2) 0; }
.frow + .frow { border-top: 1px solid var(--line); }
.frow-main {
  flex: 1 1 auto;
  min-width: 0;
  display: grid;
  gap: 2px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  text-align: left;
}
button.frow-main { cursor: pointer; min-height: var(--tap); align-content: center; }
.frow-name {
  font: 560 16px/1.3 var(--font);
  overflow-wrap: anywhere;
  /* Two lines at most; the whole name is on the row's title. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}
.frow-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; font: var(--t-sub); color: var(--ink-2); }
/* A row's tag ("Up to 1,200 kcal") always takes its own line under the
 * words, so a list reads as one column of chips rather than one tucked
 * beside a short line and the rest hung below long ones. */
.frow-meta-text:not(:last-child) { flex-basis: 100%; }
.frow-num { flex: none; text-align: right; font: 560 16px/1.2 var(--font); white-space: nowrap; }
.frow-num b { font-weight: 600; }
.frow-num .u { font: 450 var(--t-cap-size)/1 var(--font); color: var(--ink-2); }
.frow-p { display: block; margin-top: 2px; font: var(--t-cap); color: var(--ink-2); }
.frow-add {
  position: relative;
  flex: none;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 999px;
  background: var(--quiet-bg);
  color: var(--ink);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color .14s var(--ease), transform .12s var(--ease);
}
.frow-add:hover { background: var(--quiet-press); }
.frow-add:active { transform: scale(.94); }
.frow-add::before { content: ""; position: absolute; inset: -2px; }
.frow-add .icon { width: 20px; height: 20px; }
.frow-add.is-logged { background: var(--accent-wash); color: var(--ink); box-shadow: inset 0 0 0 1px var(--accent-ink); }
.frow-add.is-busy { opacity: .6; }

/* -------------------------------------------------------- capture tiles */

.capture { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s-2); }
.capture-tile {
  min-height: 62px;
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 5px;
  padding: 6px 4px;
  border: 0;
  border-radius: var(--r-capture);
  background: var(--surface-2);
  color: var(--ink);
  font: 500 var(--t-cap-size)/1.1 var(--font);
  text-align: center;
  cursor: pointer;
}
.capture-tile .icon { width: 22px; height: 22px; color: var(--ink-2); }
.capture-tile.is-off { color: var(--ink-2); cursor: default; }
.capture-reason { font: var(--t-cap); color: var(--ink-2); }

/* ------------------------------------------------ ticks, exercises, sets */

.tick {
  position: relative;
  flex: none;
  width: 48px;
  height: 48px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  box-shadow: inset 0 0 0 1.5px var(--edge);
  color: var(--ink-2);
  cursor: pointer;
  transition: background-color .14s var(--ease);
}
.tick .icon { width: 20px; height: 20px; stroke-width: 2.4; }
.tick.is-done { background: var(--accent-mark); box-shadow: none; color: var(--on-accent); }
.tick.is-live { box-shadow: inset 0 0 0 2px var(--accent-ink); color: var(--accent-ink); }
.tick.tick-sm { width: 44px; height: 44px; }

.exrow { display: flex; align-items: center; gap: var(--s-3); min-height: 68px; padding: 10px 0; }
.exrow + .exrow { border-top: 1px solid var(--line); }
.exrow-main {
  flex: 1 1 auto;
  min-width: 0;
  display: grid;
  gap: 2px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  text-align: left;
}
button.exrow-main { cursor: pointer; min-height: var(--tap); align-content: center; }
.exrow-name { font: var(--t-row); }
.exrow-sub { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; font: var(--t-sub); color: var(--ink-2); }
.exrow.is-done .exrow-name { color: var(--ink-2); }
.exrow.is-held .exrow-name,
.exrow.is-held .exrow-sub { color: var(--ink-2); }
.exrow.is-held .exrow-name { font-weight: 450; }
.exrow.is-selected {
  margin: 0 -12px;
  padding: 10px 12px;
  border-radius: var(--r-field);
  background: var(--accent-wash);
}
.exrow.is-selected + .exrow { border-top-color: transparent; }
/* On the wash --edge falls under 3:1 for several accents; ink-2 holds. */
.exrow.is-selected .pips i:not(.is-done) { box-shadow: inset 0 0 0 1.5px var(--ink-2); }
.pips { display: inline-flex; gap: 4px; }
.pips i { width: 8px; height: 8px; border-radius: 999px; box-shadow: inset 0 0 0 1.5px var(--edge); }
.pips i.is-done { background: var(--accent-mark); box-shadow: none; }

.setrow {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr) minmax(0, 1fr) 48px;
  align-items: center;
  gap: var(--s-2);
  min-height: 64px;
}
.setrow + .setrow { border-top: 1px solid var(--line); }
.setrow-n { font: 600 var(--t-body-size)/1 var(--font); color: var(--ink-2); }
.setrow.is-live .setrow-n { color: var(--accent-ink); }
.setrow-cell {
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  border-radius: var(--r-field);
  background: var(--quiet-bg);
  box-shadow: inset 0 0 0 1px var(--line);
  font: 600 20px/1 var(--font);
}
.setrow-cell .u { font-size: var(--t-sub-size); }
.setrow-cell.is-ghost { background: transparent; box-shadow: inset 0 0 0 1px var(--edge); color: var(--ink-3); font-weight: 450; }
.setrow-cell.is-ghost .u { color: var(--ink-3); }
.setrow-warmup { grid-column: 2 / -1; }

.stepper {
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0;
  border-radius: var(--r-field);
  background: var(--bg);
  box-shadow: inset 0 0 0 2px var(--accent-ink);
}
.stepper-btn {
  flex: none;
  width: 48px;
  height: 48px;
  border-radius: var(--r-field);
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-2);
  cursor: pointer;
  touch-action: manipulation;
}
.stepper-btn .icon { width: 18px; height: 18px; }
.stepper-val { display: grid; justify-items: center; line-height: 1; }
.stepper-val b { font: 600 20px/1 var(--font); }
.stepper-val .u { font-size: var(--t-cap-size); margin-top: 2px; }

/* ---------------------------------------------------------------- dock
 * Sticky at the foot of the face, so it keeps its place in the flow (the
 * face reserves its height) and never covers a row. */

.dock {
  position: sticky;
  /* Above the phone's fixed tab bar, never under it (§3.1: the dock covers
   * and hides nothing). On wide there is no bottom bar: 0 (below). */
  bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin: var(--s-4) calc(-1 * var(--pad)) 0;
  padding: 10px var(--pad);
  border-top: 1px solid var(--line);
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
.dock-rest { display: flex; align-items: center; gap: 10px; }
.dock-ring { width: 30px; height: 30px; flex: none; }
.dock-ring-track { fill: none; stroke: var(--surface-2); stroke-width: 3; }
.dock-ring-arc { fill: none; stroke: var(--accent-mark); stroke-width: 3; stroke-linecap: round; }
.dock-clock { display: block; font: 600 var(--t-stat-size)/1 var(--font); font-stretch: 88%; }
.dock-of { display: block; font: var(--t-cap); color: var(--ink-2); }
.dock-actions { display: flex; align-items: center; gap: var(--s-2); }

/* ----------------------------------------------------------- week board */

.board-wrap { display: grid; gap: 10px; }
.board {
  --board-day: 40px;
  --board-kcal: 84px;
  display: grid;
  align-items: center;
}
.board-h {
  padding-bottom: 6px;
  color: var(--ink-2);
  font: 500 var(--t-cap-size)/1.15 var(--font);
  font-stretch: 90%;
  text-align: center;
  hyphens: manual;           /* ui.weekBoard puts a soft hyphen in a long word */
}
.board-h-k { text-align: right; }
.board-d, .board-c, .board-k { height: var(--board-row, 44px); display: flex; align-items: center; }
.board-d {
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  font: 560 var(--t-body-size)/1 var(--font);
  text-align: left;
}
/* A row is a role="row" for a screen reader and nothing for the grid. */
.board-row { display: contents; }
/* The button inside a tappable cell fills it, and its hit area reaches 44px
 * whatever the column's width (a five- or six-slot plan at 320). */
.board-hit {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: inherit;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  border-radius: var(--r-field);
  background: none;
  color: inherit;
  font: inherit;
  text-align: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.board-hit::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: max(100%, var(--tap));
  height: max(100%, var(--tap));
  transform: translate(-50%, -50%);
}
.board-c { justify-content: center; padding: 0; border: 0; background: none; color: var(--ink-2); }
.board-c i { position: relative; width: 16px; height: 16px; border-radius: 999px; }
/* The board speaks the hero meter's vocabulary (the A pass): what was eaten
 * is the person's mark, solid; a plan is a ring, and inside it a faint disc
 * of the person's band sized by the meal's share of the day. */
.board-c.is-eaten i { background: var(--accent-mark); }
.board-c.is-planned i { box-shadow: inset 0 0 0 1.75px var(--ink-2); }
.board-c.is-planned i[data-share]::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc(var(--share, .5) * 100%);
  height: calc(var(--share, .5) * 100%);
  transform: translate(-50%, -50%);
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent-mark) 45%, transparent);
}
.board-c.is-open i { width: 18px; height: 18px; border-radius: 0; }
.board-c.is-open i::before,
.board-c.is-open i::after {
  content: "";
  position: absolute;
  left: 8px;
  top: 2px;
  width: 2px;
  height: 14px;
  border-radius: 1px;
  background: var(--ink-3);
}
.board-c.is-open i::after { transform: rotate(90deg); }
/* Eaten, and a planned meal in the slot still to log: the filled mark inside
 * the plan's ring (a snack where dinner was planned is not dinner eaten). */
.board-c.is-partial i { box-shadow: inset 0 0 0 1.75px var(--ink-2); }
.board-c.is-partial i::after {
  content: "";
  position: absolute;
  inset: 4px;
  border-radius: 999px;
  background: var(--accent-mark);
}
/* Planned on a day gone by and never logged: not counted (§1's hollow ring
 * with a word — the legend's "Not logged"), smaller and lighter than a plan's
 * ring, so it never reads as a plan still to come. */
.board-c.is-missed i { width: 10px; height: 10px; box-shadow: inset 0 0 0 1.5px var(--ink-3); }
/* Held (MEAL-PLANNING-SPEC §6.1, §6.7): room kept for a meal nobody has
 * planned. A dotted ring in grey, never the person's colour, and never
 * without its word in the legend ("Held"). */
.board-c.is-held i { box-shadow: none; border: 1.75px dotted var(--ink-3); }
.board-k { flex-direction: column; align-items: flex-end; justify-content: center; gap: 4px; }
.board-n { font: 560 var(--t-sub-size)/1 var(--font); }
.board-k.is-over .board-n { color: var(--red); }
/* A day with nothing eaten or planned keeps its line's height but draws no
 * figure: a dash right-aligned over the ceiling tick read as a "T" glyph.
 * The cell's label still says it. */
.board-n.is-none { visibility: hidden; }
.board-bar { position: relative; width: 72px; max-width: 100%; height: 6px; border-radius: 3px; background: var(--surface-2); }
.board-bar b { position: absolute; top: 0; bottom: 0; }
/* The same tiers as the hero meter: eaten solid in the mark, the plan's
 * remainder a band of the mark with its edge — never a grey outline. */
.board-bar-eaten { left: 0; border-radius: 3px; background: var(--accent-mark); }
.board-bar-plan { border-radius: 0 3px 3px 0; background: var(--accent-band); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-mark) 70%, transparent); }
.board-bar-eaten + .board-bar-plan { margin-left: 1px; }
.board-bar-over { min-width: 3px; border-radius: 0 3px 3px 0; background: var(--red); }
/* Held room in the bar: a dotted segment after the plan's, part of the
 * day's figure (its lands) and never drawn as food. */
.board-bar-held { box-sizing: border-box; border: 1.25px dotted var(--ink-2); border-left: 0; border-radius: 0 3px 3px 0; background: var(--surface-2); }
.board-bar-plan + .board-bar-held { margin-left: 1px; }
.board-bar-line { position: absolute; top: -3px; bottom: -3px; width: 2px; margin-left: -1px; border-radius: 1px; background: var(--ink); }
/* Incomplete (§1: today, a half-logged day): the bar's track is a dashed
 * outline, never a solid total; the cell's label and the legend say the word. */
.board-k.is-incomplete .board-bar { background: none; outline: 1.25px dashed var(--ink-3); outline-offset: 1px; }
/* Today's row: the person's wash on the ground; its label stays in ink. The
 * day the wide pane shows is washed the same way until its screen closes. */
.board .is-today,
.board .is-selected { background: var(--accent-wash-bg); }
.board-d.is-today,
.board-d.is-selected { margin-left: -8px; padding-left: 8px; border-radius: var(--r-field) 0 0 var(--r-field); }
.board-d.is-today { font-weight: 650; }
.board-k.is-today,
.board-k.is-selected { margin-right: -8px; padding-right: 8px; border-radius: 0 var(--r-field) var(--r-field) 0; }
.board-legend { display: flex; flex-wrap: wrap; gap: 6px 14px; font: var(--t-cap); color: var(--ink-2); }
.board-legend span { display: inline-flex; align-items: center; gap: 6px; }
/* Five or six marks: three even columns, never a lone word wrapped onto a
 * line of its own (the A pass). */
/* More than four marks flow in rows of what fits (two at 390 for seven),
 * never a fixed three columns that cost the face a third row (§6.1). */
.board-legend:has(> :nth-child(5)) { column-gap: 12px; }
.board-legend i { position: relative; width: 10px; height: 10px; border-radius: 999px; }
.board-legend .is-eaten { background: var(--accent-mark); }
.board-legend .is-planned { box-shadow: inset 0 0 0 1.5px var(--ink-2); }
.board-legend .is-open { border-radius: 0; }
.board-legend .is-open::before,
.board-legend .is-open::after {
  content: "";
  position: absolute;
  left: 4px;
  top: 0;
  width: 2px;
  height: 10px;
  background: var(--ink-3);
}
.board-legend .is-open::after { transform: rotate(90deg); }
.board-legend .is-line { width: 2px; height: 12px; border-radius: 1px; background: var(--ink); }
.board-legend .is-partial { box-shadow: inset 0 0 0 1.5px var(--ink-2); }
.board-legend .is-partial::after {
  content: "";
  position: absolute;
  inset: 3px;
  border-radius: 999px;
  background: var(--accent-mark);
}
.board-legend .is-missed { width: 7px; height: 7px; margin: 0 1.5px; box-shadow: inset 0 0 0 1.5px var(--ink-3); }
.board-legend .is-incomplete { width: 18px; height: 6px; border-radius: 3px; outline: 1.25px dashed var(--ink-3); }
.board-legend .is-held { border: 1.5px dotted var(--ink-3); }
/* "Share of the day": a planned ring holding its disc (§6.1). */
.board-legend .is-share { box-shadow: inset 0 0 0 1.5px var(--ink-2); }
.board-legend .is-share::after {
  content: "";
  position: absolute;
  inset: 2.5px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ink-2) 45%, transparent);
}
/* A face under 328 wide is a phone under 360 (its 16px gutters): the kcal
 * column stays, narrower, and the slot heads take the condensed width. The
 * hero's caption stacks rather than wrapping both ends into each other. */
@container (max-width: 327.98px) {
  .hero-cap { flex-direction: column; gap: 2px; }
  .hero-cap-right { text-align: left; }
  .board { --board-day: 32px; --board-kcal: 60px; }
  .board-h { font-stretch: 85%; }
  .board-d { font-size: var(--t-sub-size); }
  .board-bar { width: 54px; }
  /* Seven marks and their words (Held, Share of the day) in two columns:
   * three would run past a 288 column. */
  .board-legend:has(> :nth-child(5)) { grid-template-columns: repeat(2, max-content); column-gap: var(--s-5); }
}

/* ------------------------------------------------------ small multiples */

.tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-2); }
.tiles-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.tile {
  min-width: 0;
  display: grid;
  align-content: start;
  padding: 11px 11px 10px;
  border: 0;
  border-radius: var(--r-tile);
  background: var(--surface);
  box-shadow: var(--lift);
  color: var(--ink);
  font: inherit;
  text-align: left;
}
button.tile { cursor: pointer; }
.tile-k { font: 500 var(--t-cap-size)/1.2 var(--font); color: var(--ink-2); }
.tile-v {
  margin-top: 6px;
  font: 600 22px/1 var(--font);
  font-stretch: 88%;
  letter-spacing: -.01em;
  white-space: nowrap;
}
.tile-v .u { font-size: var(--t-cap-size); }
.tile-pm {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  flex-wrap: wrap;
  gap: 2px 5px;
  margin-top: 4px;
  font: var(--t-cap);
  color: var(--ink-2);
}
.tile-pm > span:empty { display: none; }
.tile-spark { display: block; width: 100%; height: 30px; margin-top: 6px; overflow: visible; }
.spark-line { fill: none; stroke: var(--accent-ink); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.spark-end { fill: var(--accent-ink); }
.spark-band { fill: var(--accent-band); }
.spark-dot { fill: var(--ink-2); }
/* A measured reading on an estimate's line (a DEXA scan on the model's fat
 * line): a solid dot cut from the line by the tile's ground. */
.spark-mark { fill: var(--ink-2); stroke: var(--surface); stroke-width: 1.5; }

/* ---------------------------------------------------------------- gauge */

.hgauge-wrap { margin-top: var(--s-3); }
.hgauge { position: relative; height: 34px; }
.hgauge i { position: absolute; }
.hgauge-track { left: 0; right: 0; top: 13px; height: 8px; border-radius: 4px; background: var(--surface-2); }
.hgauge-past { top: 13px; right: 0; height: 8px; border-radius: 0 4px 4px 0; background: var(--red-wash); }
.hgauge-val { left: 0; top: 13px; height: 8px; border-radius: 4px 0 0 4px; background: var(--accent-mark); }
.hgauge-over { top: 13px; height: 8px; border-radius: 0 4px 4px 0; background: var(--red); }
.hgauge-pm { top: 10px; height: 14px; border-radius: 3px; box-shadow: inset 0 0 0 1px var(--ink-3); }
.hgauge-tick { top: 6px; width: 2px; height: 22px; margin-left: -1px; border-radius: 1px; background: var(--ink); }
.hgauge-tick.is-soft { background: var(--ink-3); }
.hgauge-labels { position: relative; height: 18px; font: var(--t-cap); color: var(--ink-2); }
.hgauge-labels span { position: absolute; top: 0; white-space: nowrap; transform: translateX(-50%); }
.hgauge-labels .hgauge-zero { transform: none; }
.hgauge-labels span:last-child:not(.hgauge-zero) { transform: translateX(-100%); }
.hgauge-label.is-binding { color: var(--ink); font-weight: 600; }
.limit-line { margin-top: 4px; color: var(--red); font: 560 var(--t-sub-size)/1.35 var(--font); }

/* ------------------------------------------------------ process strip */

.proc { position: relative; padding-left: 30px; }
.proc::before {
  content: "";
  position: absolute;
  left: 7px;
  top: 10px;
  bottom: 26px;
  width: 2px;
  background: var(--line);
}
.stage { position: relative; padding: var(--s-3) 0 14px; }
.stage + .stage { border-top: 1px solid var(--line); }
.stage-node {
  position: absolute;
  left: -30px;
  top: 16px;
  width: 16px;
  height: 16px;
  border-radius: 999px;
  background: var(--bg);
  box-shadow: inset 0 0 0 2px var(--accent-ink);
}
.stage.is-measured .stage-node { background: var(--ink-2); box-shadow: none; }
.stage.is-hot .stage-node {
  background: var(--accent-mark);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent-mark) 24%, var(--bg));
}
.stage-top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.stage-name { font: 600 var(--t-row-size)/1.3 var(--font); }
.stage-v {
  font: 600 22px/1 var(--font);
  font-stretch: 88%;
  text-align: right;
  white-space: nowrap;
}
.stage-pm { font: 450 var(--t-sub-size)/1 var(--font); font-stretch: 100%; color: var(--ink-2); }
.stage-u { display: block; margin-top: 3px; font: var(--t-cap); font-stretch: 100%; color: var(--ink-2); }
.stage-why { margin-top: 3px; font: var(--t-sub); color: var(--ink-2); text-wrap: pretty; }

/* -------------------------------------------------------- stacked bar
 * Categories are greys, never the accent (§4.1). */

.stackbar { display: flex; gap: 2px; height: 10px; margin-top: 10px; }
.stackbar b { min-width: 2px; border-radius: 2px; background: var(--ink-3); }
.stackbar b.is-a { background: var(--ink-2); }
.stackbar b.is-b { background: var(--ink-3); }
.stackbar b.is-c { background: var(--edge); }
.stackbar-key { display: flex; flex-wrap: wrap; gap: 4px 12px; margin-top: 7px; font: var(--t-cap); color: var(--ink-2); }
.stackbar-key span { display: inline-flex; align-items: center; gap: 5px; }
.stackbar-key i { width: 9px; height: 9px; border-radius: 2px; }
.stackbar-key i.is-a { background: var(--ink-2); }
.stackbar-key i.is-b { background: var(--ink-3); }
.stackbar-key i.is-c { background: var(--edge); }
/* What the parts leave of a whole they do not add up to: an empty step. */
.stackbar b.is-rest,
.stackbar-key i.is-rest { background: transparent; box-shadow: inset 0 0 0 1px var(--edge); }

/* -------------------------------------------------------------- callout */

.callout {
  width: 100%;
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-top: 14px;
  padding: 14px;
  border: 0;
  border-radius: var(--r-tile);
  background: var(--surface);
  box-shadow: var(--lift);
  color: var(--ink);
  font: inherit;
  text-align: left;
}
button.callout { cursor: pointer; }
.callout-main { flex: 1 1 auto; min-width: 0; display: grid; gap: 2px; }
.callout-title { font: 600 16px/1.3 var(--font); }
.callout-sub { font: var(--t-sub); color: var(--ink-2); text-wrap: pretty; }
.callout-chev { width: 20px; height: 20px; flex: none; color: var(--ink-3); }

/* ------------------------------------------------------- section heads */

.sec-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 22px 0 8px; }
.sec-title { font: var(--t-head); letter-spacing: -.01em; }
.sec-meta { font: var(--t-cap); color: var(--ink-2); }

/* ------------------------------------------------- sheet corner actions */

.sheet-head.has-corners .sheet-action { margin-right: 2px; }
.undo .confirm-msg { color: var(--ink); }

/* ------------------------------------------------ the charts, round 12 */

.chart-numbers {
  justify-self: start;
  min-height: 34px;
  padding: 0 13px;
  border: 0;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--ink);
  font: 560 var(--t-sub-size)/1 var(--font);
  cursor: pointer;
}
.numbers-scroll { overflow-x: auto; }
.numbers { width: 100%; border-collapse: collapse; font: var(--t-sub); }
.numbers th, .numbers td { padding: 8px 6px; border-bottom: 1px solid var(--line); text-align: right; }
.numbers th:first-child { text-align: left; font-weight: 450; }
.numbers thead th { font: 500 var(--t-cap-size)/1.2 var(--font); color: var(--ink-2); }
.chart-label { font-family: var(--font); font-variant-numeric: tabular-nums; }
.chart-mini { display: block; width: 100%; height: 44px; margin-top: 6px; overflow: visible; }
.chart-timeline { overflow: visible; }


/* ============================================================== wide ====
 *
 * REVAMP-DESIGN §3.3, §3.4 (WP4). At 1024px and up the same DOM is laid out
 * as three columns: the side rail (the tab bar's own <nav>, restyled), the
 * face column (the header over #view, 420 wide), and the pane (#pane), where
 * js/router.js opens a pushed screen beside the face instead of over it. At
 * 1440 the pane caps at 820 and the three centre together. The face keeps
 * its title and subtitle and drops the switch, which the rail carries; a
 * sheet becomes a 480 dialog with the same corner actions and focus trap.
 */
@media (min-width: 1024px) {
  body {
    display: grid;
    grid-template-columns: 232px 420px minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
    align-items: start;
    min-height: 100dvh;
    padding-bottom: 0;
  }

  /* ---- the side rail: wordmark, switch, the five, and the foot ---------- */
  .tabbar {
    position: sticky;
    /* `inset` first: after `top` it would reset top to auto, and a sticky
     * rail with no offset scrolls away with a long face. */
    inset: auto;
    top: 0;
    grid-column: 1;
    grid-row: 1 / span 2;
    height: 100dvh;
    overflow-y: auto;
    flex-direction: column;
    justify-content: flex-start;
    align-items: stretch;
    gap: 18px;
    padding: 22px 16px 18px;
    background: var(--bg);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-top: 0;
    border-right: 1px solid var(--line);
  }
  .rail-head { display: grid; gap: 18px; }
  /* The K-Fit wordmark: the brand mark's one use outside the lock screen. */
  .rail-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    padding-left: 6px;
    font: 650 18px/1 var(--font);
    color: var(--ink);
  }
  .rail-brand-mark {
    flex: none;
    width: 22px;
    height: 22px;
    border-radius: 7px;
    background: var(--brand);
  }
  /* The switch, full width: the two segments share it. */
  .rail-switch { display: flex; }
  .rail-switch .person-btn { flex: 1 1 0; min-width: 0; }

  .tabbar-tabs {
    grid-auto-flow: row;
    grid-auto-columns: auto;
    gap: 2px;
    max-width: none;
  }
  /* A destination: mark and label on one line. The active one is washed, with
   * a 3px accent edge and an accent mark; its label stays in ink. */
  .tabbar .tab {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 12px;
    min-height: var(--tap);
    padding: 0 12px;
    border-radius: var(--r-field);
    font: 500 16px/1 var(--font);
    color: var(--ink-2);
  }
  .tabbar .tab .tab-icon { width: 22px; height: 22px; }
  .tabbar .tab::before { content: none; }
  .tabbar .tab[aria-current="page"] {
    background: var(--accent-wash-bg);
    color: var(--ink);
    font-weight: 620;
  }
  .tabbar .tab[aria-current="page"]:hover { color: var(--ink); }
  .tabbar .tab[aria-current="page"] .tab-icon { color: var(--accent-ink); }
  /* The edge in the accent's INK tier: the mark tier measures under 3:1 on
   * the wash in light for five of the eight accents; the ink clears it. */
  .tabbar .tab[aria-current="page"]::before {
    content: "";
    position: absolute;
    left: 0;
    top: 10px;
    bottom: 10px;
    width: 3px;
    height: auto;
    transform: none;
    border-radius: 2px;
    background: var(--accent-ink);
  }

  /* The foot: the queue's state, Report a problem, Lock (asks first). */
  .rail-foot {
    display: grid;
    margin-top: auto;
  }
  .rail-foot-row {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    min-height: var(--tap);
    padding: 0 12px;
    border: 0;
    border-radius: var(--r-field);
    background: none;
    color: var(--ink-2);
    font: 450 var(--t-sub-size)/1.3 var(--font);
    text-align: left;
    cursor: pointer;
  }
  .rail-foot-row[hidden] { display: none; }
  .rail-foot-row:not(:disabled):hover { color: var(--ink); background: var(--surface-2); }
  /* All saved is a status, not a control: no pointer, no dimming. */
  .rail-foot-row:disabled { cursor: default; }
  /* A face control this foot already carries (Me's Report a problem and
   * Lock) is not said twice beside it; with the foot's Lock hidden, the
   * face's stays. */
  body:has(.rail-report) .rail-dup-report,
  body:has(.rail-lock:not([hidden])) .rail-dup-lock { display: none; }
  /* Something to do is said in ink; a failed save is a word, never red. */
  .rail-sync[data-state="pending"],
  .rail-sync[data-state="failed"] { color: var(--ink); }

  /* ---- the face column: the phone face, unchanged, 420 wide ------------ */
  .top {
    grid-column: 2;
    grid-row: 1;
    width: 100%;
    max-width: none;
    margin: 0;
    padding: 22px 22px 6px;
  }
  /* No switch in the row on wide (the rail has it), so the row need not be
   * the switch's 48: the title's top sits on the wordmark's, 22 down. */
  .top-row { min-height: 0; }
  .top .person-switch { display: none; }
  :root[data-screen] .top { display: grid; }

  main#view {
    grid-column: 2;
    grid-row: 2;
    width: 100%;
    max-width: none;
    margin: 0;
    padding: 6px 22px 32px;
  }

  /* ---- the pane: where a row's screen opens ---------------------------- */
  #pane {
    display: block;
    grid-column: 3;
    grid-row: 1 / span 2;
    position: sticky;
    top: 0;
    height: 100dvh;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 18px 32px 40px;
    background: var(--pane);
    border-left: 1px solid var(--line);
    container: pane / inline-size;
  }
  #pane:focus { outline: none; }
  .pane-root { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; }
  .pane-root > .screen { padding-top: 0; }

  /* The pane's first level: ONE header, the same as a sheet's (the A pass).
   * The title sits on the face title's line; the corner action and a round
   * × close it from the right. Deeper levels keep "‹ Parent" above the
   * title, as on the phone. */
  .pane-root > .screen.is-pane-top {
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: 16px;
  }
  .screen.is-pane-top > .screen-head {
    grid-column: 2;
    grid-row: 1;
    flex-direction: row-reverse;
    justify-content: flex-start;
    align-self: center;
  }
  .screen.is-pane-top > .screen-title {
    grid-column: 1;
    grid-row: 1;
    align-self: center;
    margin-top: 0;
  }
  .screen.is-pane-top > .screen-sub,
  .screen.is-pane-top > .screen-body { grid-column: 1 / -1; }
  .screen.is-pane-top .screen-back {
    justify-content: center;
    width: 36px;
    min-width: 36px;
    height: 36px;
    min-height: 36px;
    margin: 0;
    padding: 0;
    border-radius: 999px;
    background: var(--surface-2);
    color: var(--ink-2);
  }
  .screen.is-pane-top .screen-back::after { content: ""; position: absolute; inset: -4px; }
  .screen.is-pane-top .screen-back { position: relative; }
  .screen.is-pane-top .screen-back:hover { color: var(--ink); background: var(--quiet-press); }
  .screen.is-pane-top .screen-back-mark .icon { width: 18px; height: 18px; stroke-width: 2.25; }
  .screen.is-pane-top .screen-back-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }

  /* ---- sheets become dialogs (§3.4) ------------------------------------ */
  .sheet-backdrop {
    align-items: flex-start;
    padding: max(56px, 8dvh) 24px 24px;
  }
  .sheet {
    width: 480px;
    max-width: 100%;
    max-height: calc(100dvh - 2 * max(56px, 8dvh));
    border: 1px solid var(--dialog-edge);
    border-radius: var(--r-dialog);
    box-shadow: var(--lift-dialog);
    padding: 16px 0 0;
    animation: dialog-in .24s var(--ease);
  }
  .sheet-head { padding: 0 16px 12px 24px; }
  .sheet-body { padding: 2px 24px 24px; }
  /* A dialog is not dragged: no grabber. */
  .sheet::before { display: none; }

  /* No tab bar under them on wide: the toast sits low, and the queue's badge
   * gives way to the rail's foot, which says the same and opens the same. */
  #toast { bottom: 24px; }
  .queue-badge { display: none !important; }
  .dock { bottom: 0; }
}

@media (min-width: 1440px) {
  body {
    grid-template-columns: 232px 420px minmax(0, 820px);
    justify-content: center;
    /* The trio is centred, so the page shows a margin each side: the left one
     * is the rail's ground, the right one the pane's, and the pane no longer
     * ends in a band of a darker ground. The page's middle always falls
     * inside the pane (232 + 420 < 1472 / 2 + 1), never in the face. */
    background: linear-gradient(90deg, var(--bg) 50%, var(--pane) 50%);
  }
}
