
:root {
  color-scheme: light dark;
  --font-family: -apple-system, "PingFang SC", "Noto Sans SC", sans-serif;
  --font-voice: ui-serif, Georgia, "Songti SC", "Noto Serif SC", "Noto Naskh Arabic", serif;
  --font-size-caption: 13px;
  --font-size-small: 15px;
  --font-size-base: 17px;
  --font-size-title: 20px;
  --font-size-display: 26px;
  --font-size-hero: 34px;
  --line-height: 1.5;
  --color-ok: #0F7B3E;
  --color-waiting: #B45309;
  --color-warn: #B42318;
  --color-highlight: #8A6D00;
  --color-ink: #1A1A1A;
  --color-ink-secondary: #5C5C5C;
  --color-surface: #FFFFFF;
  --color-surface-alt: #F6F5F2;
  --color-border: #E4E2DD;
  --color-jade: #0F7B3E;
  --color-jade-deep: #0A5A2C;
  --color-jade-wash: #EDF4EF;
  --color-jade-line: #C6DCCE;
  --color-paper: #FBFAF7;
  --color-paper-sunk: #F0EDE7;
  --color-ok-wash: #E2EFE8;
  --color-ok-line: #B7D7C5;
  --color-warn-wash: #F6E5E3;
  --color-warn-line: #E9BDBA;
  --color-waiting-wash: #F5E7DD;
  --color-waiting-line: #E5C3A9;
  --color-highlight-wash: #EFEBDB;
  --color-highlight-line: #EAE5D1;
  --space-4: 4px;
  --space-8: 8px;
  --space-12: 12px;
  --space-16: 16px;
  --space-24: 24px;
  --space-32: 32px;
  --space-48: 48px;
  --radius-card: 12px;
  --radius-chip: 999px;
  --measure-column: 1040px;
  --measure-prose: 62ch;
  --measure-form: 40ch;
  --shadow-card: 0 1px 3px rgba(0,0,0,0.08);
  --shadow-raised: 0 4px 12px rgba(0,0,0,0.10);
  --shadow-lift1: 0 1px 1px rgba(26,26,26,0.04), 0 2px 6px rgba(26,26,26,0.05), 0 0 0 1px rgba(26,26,26,0.04);
  --shadow-lift2: 0 2px 4px rgba(26,26,26,0.05), 0 10px 24px rgba(26,26,26,0.09), 0 0 0 1px rgba(26,26,26,0.05);
  --motion-fast: 120ms;
  --motion-normal: 200ms;
  --motion-max: 300ms;
}
@media (prefers-color-scheme: dark) {
  :root {
  --color-ok: #4ADE80;
  --color-waiting: #FBBF24;
  --color-warn: #F87171;
  --color-highlight: #FACC15;
  --color-ink: #F2F1EE;
  --color-ink-secondary: #A3A29E;
  --color-surface: #161514;
  --color-surface-alt: #211F1D;
  --color-border: #33312E;
  --color-jade: #4ADE80;
  --color-jade-deep: #22C55E;
  --color-jade-wash: #13251B;
  --color-jade-line: #1F3E2C;
  --color-paper: #1C1B19;
  --color-paper-sunk: #100F0E;
  --color-ok-wash: #12241A;
  --color-ok-line: #1F3E2C;
  --color-warn-wash: #2A1614;
  --color-warn-line: #4A211C;
  --color-waiting-wash: #2A1F12;
  --color-waiting-line: #4A3418;
  --color-highlight-wash: #262112;
  --color-highlight-line: #43391C;
  }
}
html[lang="zh"] { --line-height: 1.7; }
html[lang="ar"] { --line-height: 1.75; }
  * { box-sizing: border-box; }
  body { margin: 0; background: var(--color-surface-alt); color: var(--color-ink);
    font: var(--font-size-base)/var(--line-height) var(--font-family); }
  a { color: inherit; text-decoration: none; }
  /* Anything a PERSON says — her draft, a buyer's quoted words. Never a label. */
  .voice { font-family: var(--font-voice); }
  .layout { display: grid; grid-template-columns: 232px 1fr; min-height: 100vh; }
  /* A grid child does not shrink below its own content unless told to: its
     default min-width is auto, so one unbreakable string — a long sku, a URL a
     buyer pasted — widens the content track and scrolls the whole page
     sideways. Nothing in the product does that today; this is the guard, not a
     repair. */
  .layout > * { min-width: 0; }
  nav.side { background: var(--color-paper); border-inline-end: 1px solid var(--color-border);
    padding: var(--space-16) var(--space-12); }
  .brand { display:flex; align-items:center; gap:var(--space-8); font-weight: 700;
    font-size: var(--font-size-base); padding: 6px 12px 18px; letter-spacing: .3px; }
  .brand .mark { flex:none; }
  /* Two cuts of one mark (brand.ts): the detail cut beside the word on a desktop,
     the small reversed cut alone in the phone nav — below 40px the pale disc
     would not read. One is drawn at a time. */
  .brand .mark-detail { display:flex; }
  .brand .mark-small { display:none; }
  .brand small { display:block; color:var(--color-ink-secondary); font-weight:500;
    font-size:var(--font-size-caption); letter-spacing:0; margin-top:var(--space-4); }
  nav.side a.navlink { display: flex; align-items: center; gap: var(--space-8); padding: var(--space-12);
    min-height: 44px; border-radius: var(--radius-card); color: var(--color-ink-secondary);
    font-size: var(--font-size-small); margin-bottom: var(--space-4); }
  nav.side a.navlink:hover { background: var(--color-paper-sunk); color: var(--color-ink); }
  /* M49 — the active destination reads by WEIGHT and a recess, not by colour.
     Green was being spent five times on one screen: this slab, a panel, every
     link, the language pill and the button. Colour that appears everywhere
     marks nothing; jade now means only "this sends" and "this is a state". */
  nav.side a.navlink.active { background: var(--color-paper-sunk); color: var(--color-ink); font-weight:600; }
  /* D — the setup count on the Setup entry: a figure at the far end of the
     row, in the secondary ink. Not a state, so no state colour. */
  /* V1 step three — the count sits BESIDE its word, not at the far end of the row. */
  nav.side .navcount { margin-inline-start:var(--space-8); font-size:var(--font-size-caption);
    font-weight:500; color:var(--color-ink-secondary); font-variant-numeric:tabular-nums; }
  /* V1 · option A (2026-09-24) — there is no header band. The nav row is the
     chrome; the language switch and log out are the first rows of Setup, and
     the login page keeps its own switcher. */
  .langsw { display:inline-flex; gap:var(--space-4); background:var(--color-paper-sunk);
    border:1px solid var(--color-border); border-radius:var(--radius-chip); padding:3px; }
  .langsw a { display:inline-flex; align-items:center; min-height:44px; padding:0 14px;
    border-radius:var(--radius-chip); font-size:var(--font-size-caption);
    color:var(--color-ink-secondary); white-space:nowrap; }
  /* The switcher is chrome. A solid jade fill made it the loudest object
     on a page whose subject was somebody's business. */
  .langsw a.on { background:var(--color-surface); color:var(--color-ink); font-weight:600; }

  /* M49 — THE COLUMN. One measure for the whole product, centred in the space
     beside the nav rather than stuck against it. At 2000px the old page put a
     1040px block hard left and left 700px of nothing to its right, which reads
     as a window that failed to fill rather than a sheet placed on a desk.
     Centred is a decision; the accidental middle was not. */
  main { padding: var(--space-24); max-width: var(--measure-column); margin-inline: auto; width: 100%; }
  /* Prose is allowed to be narrower INSIDE the column. It may not be a
     different number: these two classes are the only prose measures there are. */
  .measure-prose { max-width: var(--measure-prose); }
  .measure-form { max-width: var(--measure-form); }
  h1.page { font-size: var(--font-size-title); margin: 0 0 var(--space-16); }
  /* The hairline in --shadow-lift1 does what a 1px border used to; two would
     read as a double rule at the same edge. */
  .card { background:var(--color-surface); border:0; border-radius:var(--radius-card);
    box-shadow:var(--shadow-lift1); padding:var(--space-16); margin:var(--space-16) 0; }
  /* Phase F: section headings speak to the owner in her own sentence case.
     The 13px tracked-uppercase eyebrow was the one SaaS tell the product had. */
  .card h2, .block h2, main h2 { font-size:var(--font-size-base); font-weight:600;
    color:var(--color-ink); margin:0 0 var(--space-12); text-transform:none; letter-spacing:0; }

  /* Counts. Never a KPI tile — a plain line, the way Today has always drawn it. */
  .stats { display:flex; flex-direction:column; }
  .stat { display:flex; align-items:baseline; gap:var(--space-8); padding:8px 0;
    border-bottom:1px solid var(--color-border); }
  .stat:last-child { border-bottom:0; }
  .stat .v { font-size:var(--font-size-base); font-weight:600; color:var(--color-ink);
    font-variant-numeric:tabular-nums; min-width:2.5em; }
  .stat .l { font-size:var(--font-size-small); color:var(--color-ink-secondary); }

  /* One pill. It marks STATE — never decoration, never a label wearing a costume. */
  .pill { display:inline-block; padding:4px 10px; border-radius:var(--radius-chip);
    font-size:var(--font-size-caption); font-weight:600;
    margin-inline-end:var(--space-8); margin-block-end:var(--space-8); white-space:nowrap; }
  .pill.ok { background:var(--color-ok-wash); color:var(--color-ok); }
  .pill.bad { background:var(--color-warn-wash); color:var(--color-warn); }
  .pill.warn { background:var(--color-waiting-wash); color:var(--color-waiting); }
  .pill.owner { background:var(--color-highlight-wash); color:var(--color-highlight); }

  /* One button. Quiet does nothing on its own; jade sends; red takes something away. */
  .btn { display:inline-flex; align-items:center; justify-content:center; min-height:44px;
    padding:10px 18px; border-radius:var(--radius-card); border:0;
    background:var(--color-paper-sunk); color:var(--color-ink);
    font:inherit; font-size:var(--font-size-small); cursor:pointer; }
  .btn.send { background:var(--color-jade); color:var(--color-surface); box-shadow:var(--shadow-lift1); }
  .btn.send:hover { background:var(--color-jade-deep); }
  .btn.danger { background:var(--color-warn-wash); color:var(--color-warn); }
  .btn.ghost { background:transparent; border:1px solid var(--color-border); color:var(--color-ink-secondary); }
  .inline { display:inline; }
  .doors { display:flex; flex-direction:column; gap:var(--space-8); margin-top:var(--space-12); }
  /* M49 — a button in a column form stretched to the width of the input above
     it, which made "Save" a 455px slab. A button is as wide as its word. */
  form .btn, form button:not(.full) { align-self:start; }

  /* ── The form. ONE definition, in the shell, because five pages use it.
     Three of them (the rate, the closures, the samples) referenced these
     classes while emitting no rule for them, so their fields rendered as
     inline labels strung across the page — the same failure as a renderer
     reaching for a variable nobody emits, and invisible to every test that reads
     strings rather than boxes. Caught by a screenshot. */
  .pform { display:flex; flex-direction:column; gap:var(--space-16);
           max-width:var(--measure-form); margin-top:var(--space-12); }
  .fld { display:flex; flex-direction:column; gap:var(--space-4);
         font-size:var(--font-size-small); color:var(--color-ink); }
  .pform input, .pform textarea, .pform select {
    background:var(--color-paper-sunk); border:1px solid var(--color-border);
    border-radius:10px; color:var(--color-ink); padding:11px 14px; font:inherit;
    min-height:44px; resize:vertical; }
  .chkbox { display:inline-flex; align-items:center; gap:var(--space-8);
            font-size:var(--font-size-small); color:var(--color-ink); min-height:44px; }
  .chkbox input { min-height:0; }
  /* One figure, stated large: the rate she set, the sample price, the state an
     order is in. It is a READING, not a KPI tile. */
  .stated-now { font-size:var(--font-size-display); margin:var(--space-12) 0; }

  /* One notice — in two tones, because one of them is a refusal.
     D5: every notice was painted in the jade of a success, so "Only the owner
     may do that" and "Sent" looked alike at a glance. The bad tone carries
     a line as well as a wash, since the two washes are close enough in
     value that colour alone would be the whole signal. */
  .flash { background:var(--color-jade-wash); color:var(--color-jade-deep);
    border:1px solid transparent;
    border-radius:var(--radius-card); padding:var(--space-12) var(--space-16);
    margin-bottom:var(--space-16); font-size:var(--font-size-small); }
  .flash.bad { background:var(--color-warn-wash); color:var(--color-warn);
    border-color:var(--color-warn-line); }
  /* CC-26 · the line a page shows when something new arrives while it is
     open: the notice's own shape and tone, and the whole line one door to
     the newest. It sits at the foot of the column, at the reading measure,
     and stays in view while she scrolls, over nothing she is reading until
     she reaches it; empty, it takes no room. Nothing above it moves when it
     appears. */
  .live { position:sticky; bottom:var(--space-16); z-index:1; max-width:var(--measure-prose); }
  .live-line { padding:0; margin:var(--space-16) 0 0; box-shadow:var(--shadow-lift2); }
  .live-line .deeper { width:100%; justify-content:space-between; gap:var(--space-12);
    padding:var(--space-8) var(--space-16); color:inherit; font-weight:600; }

  /* One tab row. */
  .tabs { display:flex; gap:var(--space-8); margin-bottom:var(--space-16); }
  .tab { display:inline-flex; align-items:center; min-height:44px; padding:8px 16px;
    border-radius:var(--radius-chip); background:var(--color-surface);
    border:1px solid var(--color-border); color:var(--color-ink-secondary);
    font-size:var(--font-size-small); }
  .tab.on { background:var(--color-paper-sunk); border-color:var(--color-border); color:var(--color-ink); font-weight:600; }

  .list { display:flex; flex-direction:column; gap:var(--space-12); }
  .back { display:inline-flex; align-items:center; gap:var(--space-4); min-height:44px;
    color:var(--color-ink); font-size:var(--font-size-small); }
  pre { background:var(--color-paper-sunk); border:1px solid var(--color-border);
    border-radius:var(--radius-card); padding:18px; overflow-x:auto;
    font:var(--font-size-small)/1.55 "SF Mono", ui-monospace, Menlo, monospace;
    color:var(--color-ink); white-space:pre; margin:0; }
  /* One "go deeper" link for the whole product; the chevron mirrors in RTL. */
  /* Each "go deeper" is its own ROW. Inline-flex put three of them on one
     line on the settings page, where they read as one run-on sentence with
     chevrons in it rather than three separate doors. fit-content keeps the
     target the width of its words, not the width of the column. */
  .deeper { display:flex; width:fit-content; align-items:center; gap:var(--space-4); min-height:44px;
    padding:var(--space-8) 0; font-size:var(--font-size-small); color:var(--color-ink); }
  .deeper:hover, .deeper:focus-visible { color:var(--color-jade-deep); }
  /* The chevron carries the affordance now that the label does not shout. */
  .go { font-size:var(--font-size-base); color:var(--color-ink-secondary); }
  [dir="rtl"] .go { transform:scaleX(-1); display:inline-block; }
  /* The chevron above mirrors because it POINTS — "onward" is to the left in
     Arabic. The mark does NOT, and its absence here is deliberate rather than an
     oversight: a brand mark is a constant, the same object in every language,
     and flipping it would make Nomi a different mark for Arabic readers. Only
     directional glyphs mirror. Do not add .mark to this rule. */
  a:focus-visible, button:focus-visible, input:focus-visible,
  textarea:focus-visible, select:focus-visible { outline:2px solid var(--color-jade); outline-offset:2px; }
  .muted { color:var(--color-ink-secondary); font-size:var(--font-size-caption); }
  /* M49 — one empty state, aligned like everything else. It was centred while
     the page around it was left-aligned, which is the single clearest way to
     make a considered page look like an accident. It mirrors in RTL on its own. */
  .empty { text-align:start; color:var(--color-ink-secondary);
    font-size:var(--font-size-small); padding:var(--space-24) 0;
    max-width:var(--measure-prose); }

  /* ── Speech: the two voices. ─────────────────────────────────────────────
     Anything a PERSON says — the buyer's words, her drafts, her sent replies —
     is set in the voice serif. Everything around the speech (labels,
     timestamps, buttons, counts) is the product speaking, and stays sans.
     One family per speaker, everywhere: these components are declared HERE and
     owned by the shell, because inbox and sandbox each carrying a copy is how
     the two drifted apart last time. */
  .timeline { display:flex; flex-direction:column; gap:var(--space-12); }
  .msg { max-width:82%; }
  .msg.inbound { align-self:flex-start; } .msg.outbound { align-self:flex-end; }
  .bubble { font-family:var(--font-voice); padding:10px 14px; border-radius:14px;
    white-space:pre-wrap; word-break:break-word; }
  /* The buyer's words are FULL SIZE; every reply is one step down. The page
     belongs to the buyer's business — she works inside it. */
  .msg.inbound .bubble { font-size:var(--font-size-base);
    background:var(--color-paper-sunk); border-start-start-radius:4px; }
  /* Her sent replies: neutral. These carried an amber fill — colour on every
     message she ever sent, marking no state at all. */
  .msg.outbound .bubble { font-size:var(--font-size-small);
    background:var(--color-surface); border:1px solid var(--color-border);
    border-start-end-radius:4px; }
  .ts { font-size:var(--font-size-caption); margin-top:var(--space-4); }
  /* CC-25 — a link into a transcript lands on its newest message: clear of the
     sticky phone nav, with the message before it still in view. After an
     action it lands on the notice the action left, drawn under that message.
     The practice box is a landing of the same kind. */
  #latest, #compose { scroll-margin-top:25vh; }
  /* Her PROPOSAL — visually subordinate to the buyer's words above it. Not a
     boxed rival: a quiet serif paragraph behind a jade hairline that means
     "hers, awaiting your decision". border-inline-start keeps the hairline on
     the reading edge in RTL with no override. */
  .proposed { font-family:var(--font-voice); font-size:var(--font-size-small);
    border-inline-start:2px solid var(--color-jade); padding:2px 14px;
    margin-bottom:var(--space-12); white-space:pre-wrap; word-break:break-word; }

  /* ── A plain section: air and a hairline. The DEFAULT grouping. ──────────
     A card is reserved for a boundary that MEANS something — one buyer's
     business, one verdict. A page of prose and counts is sections, not boxes. */
  /* M49 — one gap between sections, from the scale. It was 22px here and
     anything from 40 to 180 once each page had added its own margins. */
  .block { padding:var(--space-24) 0; border-top:1px solid var(--color-border); }
  .block:first-of-type { border-top:0; padding-top:var(--space-4); }
  /* RTL needs NO override here: a grid's first track already sits on the
     inline-start edge, so the sidebar mirrors to the right on its own. The
     three rules that used to live here re-flipped it — putting the sidebar
     back on the LEFT in Arabic, and, because two explicitly-placed columns ran
     against DOM order, pushing the whole content column into grid row 2 behind
     a screen-height gap. Every page was affected at desktop width. */

  /* ── V1 step two — the shared families, defined ONCE (Symow, decision 4:
     "define what already exists, once, in one stylesheet"). Each rule below
     was declared in two or more pages with small differences; this is the one
     body, and tests/parity/v1-one-stylesheet.test.ts refuses a second. Names
     that meant two things (.acts, .certs, .tag, .prow…) were renamed on the
     page that used them differently, so a name has one meaning again. */
  .editform { display:flex; flex-direction:column; gap:var(--space-8); }
  .replyform { display:flex; flex-direction:column; gap:var(--space-8); }
  .takeover { display:flex; align-items:center; gap:var(--space-8); flex-wrap:wrap; }
  .acts { display:flex; gap:var(--space-8); flex-wrap:wrap; margin-bottom:var(--space-16); }
  .perr { color:var(--color-highlight); font-size:var(--font-size-caption); margin:0; }
  .pq { display:flex; flex-direction:column; gap:var(--space-4); font-size:var(--font-size-small); color:var(--color-ink); }
  .subline { font-size:var(--font-size-caption); margin-bottom:var(--space-12); }
  .dhead { display:flex; align-items:center; gap:var(--space-12); flex-wrap:wrap; margin-bottom:var(--space-8); }
  .chips { display:flex; flex-wrap:wrap; gap:var(--space-8); }
  .chip { background:var(--color-paper-sunk); border:1px solid var(--color-border); border-radius:999px; padding:5px 12px; font-size:var(--font-size-caption); }
  .as-box { display:inline-flex; align-items:center; gap:var(--space-4); font-size:var(--font-size-small); }
  .facts { margin-top:var(--space-16); display:flex; flex-direction:column; gap:var(--space-8); }
  .sub { margin:var(--space-16) 0 var(--space-12); font-size:var(--font-size-caption); color:var(--color-ink-secondary); }
  .note { font-size:var(--font-size-small); margin-top:var(--space-8); margin-bottom:var(--space-12); }
  .ok-line { color:var(--color-ok); font-weight:600; margin-bottom:var(--space-12); }
  .frow { display:flex; gap:var(--space-12); font-size:var(--font-size-small); }
  .flabel { color:var(--color-ink-secondary); min-width:8.5em; }
  .dhead.spread { justify-content:space-between; }
  /* ── V1 · what ten pages each drew for themselves, named once (step four).
     A list of hairline rows — dense, as decision 2 says of lists — and the
     column that names someone; two text sizes; a fieldset of choices; the
     one-time access code; the stopped pill. */
  .rows { list-style:none; margin:var(--space-12) 0 0; padding:0; }
  /* A list of counts or rows is read, not scanned: it keeps the prose measure,
     so a figure, its word and its chevron stay together at 1280 px. */
  .stats, .rows { max-width:var(--measure-prose); }
  .row { display:flex; align-items:center; justify-content:space-between; gap:var(--space-12); flex-wrap:wrap;
    padding:var(--space-8) 0; border-bottom:1px solid var(--color-border); }
  .row:last-child { border-bottom:0; }
  /* One column the width of the row, each line at its own width, so a line
     that spreads (a name and its state) reaches the row's far edge. Left to
     the row's space-between, the column shrank to its content, and on the
     channels page each state sat a different distance from its name. */
  .row.lines { display:grid; grid-template-columns:minmax(0, 1fr); justify-items:start; gap:var(--space-4); }
  .row.lines > .spread { justify-self:stretch; }
  .row.top { align-items:flex-start; }
  .row p { margin:0; }
  .row .btn { flex:none; }
  .grow { flex:1; min-width:0; }
  /* .who is the inbox's inline line (flag · name · country) and must stay
     inline; the column that names someone on People is .person. */
  .person { display:flex; flex-direction:column; gap:var(--space-4); min-width:0; }
  .caption { font-size:var(--font-size-caption); }
  .small { font-size:var(--font-size-small); }
  ul.chips { list-style:none; margin:var(--space-12) 0 0; padding:0; }
  .pill.stop { background:var(--color-paper-sunk); color:var(--color-ink-secondary); }
  /* The one-time access code on the People page; tests read it by this class. */
  .issued .code { font-size:var(--font-size-display); font-weight:600; letter-spacing:.08em; margin:var(--space-8) 0; }
  .choices { border:0; margin:0; padding:0; display:flex; flex-wrap:wrap; gap:var(--space-8) var(--space-16); }
  .choices legend { padding:0; margin-bottom:var(--space-4); font-size:var(--font-size-caption); }
  /* V1 · decision 5 (2026-09-28): the row's one tag. Caption size, never
     wrapped; state colour only when it names a state — now (waiting for the
     owner), you (a person here holds it). Any other label, the calendar's
     kinds among them, is the neutral tag. */
  .tag { display:inline-flex; align-items:center; padding:5px 11px; border-radius:var(--radius-chip);
    font-size:var(--font-size-caption); font-weight:600; white-space:nowrap;
    background:var(--color-paper-sunk); color:var(--color-ink-secondary); }
  .tag.now { background:var(--color-waiting-wash); color:var(--color-waiting); }
  .tag.you { background:var(--color-highlight-wash); color:var(--color-highlight); }
  /* The doors either side of one page of a list, and where it sits in the whole. */
  .pager { display:flex; flex-wrap:wrap; align-items:center; gap:var(--space-4) var(--space-24); margin-top:var(--space-24); }
  /* Today (step four). The calm state IS the page: a short rule and one
     sentence in the product's voice — quiet, not jade, while messaging is off.
     And a count you can tap: the stat row as a link, its figure one step up. */
  .calm-page { padding:var(--space-32) 0 var(--space-48); }
  .calm-rule { height:2px; width:3.5rem; background:var(--color-jade); border-radius:2px; margin-bottom:var(--space-24); }
  .calm-page.off .calm-rule { background:var(--color-border); }
  .calm-say { font-size:var(--font-size-title); line-height:1.45; color:var(--color-ink); margin:0; max-width:var(--measure-prose); }
  a.stat { color:inherit; }
  a.stat:hover .go, a.stat:focus-visible .go { color:var(--color-jade-deep); }
  .stat.need .v { font-size:var(--font-size-title); }
  /* The controls the browser used to draw — the hand-to select, a details
     disclosure, a textarea outside a form — get the same recess as an input. */
  main select, main textarea, main input:not([type=checkbox]):not([type=radio]) {
    background:var(--color-paper-sunk); border:1px solid var(--color-border);
    border-radius:10px; color:var(--color-ink); padding:11px 14px; font:inherit; min-height:44px; }
  main textarea { resize:vertical; }
  details > summary { display:flex; align-items:center; gap:var(--space-8); min-height:44px; cursor:pointer;
    color:var(--color-ink); font-size:var(--font-size-small); list-style:none; }
  details > summary::-webkit-details-marker { display:none; }
  details > summary::before { content:'›'; color:var(--color-ink-secondary); display:inline-block; }
  details[open] > summary::before { content:'⌄'; }
  [dir="rtl"] details:not([open]) > summary::before { transform:scaleX(-1); }
  /* The twins of :hover and :focus-visible, so a page can SHOW a state without a
     pointer. Only the components page under Setup wears them. */
  .btn:hover:not(.send), .btn.is-hover:not(.send) { background:var(--color-border); }
  .btn.send.is-hover { background:var(--color-jade-deep); }
  .is-focus { outline:2px solid var(--color-jade); outline-offset:2px; }
  .btn:disabled, .btn.is-disabled { background:var(--color-paper-sunk); color:var(--color-ink-secondary);
    box-shadow:none; cursor:default; }

  @media (max-width: 720px) {
    /* Rows matter here. .layout carries min-height:100vh, and with one column
       and no declared rows the nav and the content shared that height evenly:
       the nav row grew to half the viewport and, because a flex row stretches
       its children by default, the ACTIVE tab's background filled all of it —
       a jade slab down the page, on every surface, at phone width. */
    .layout { grid-template-columns: 1fr; grid-template-rows: auto 1fr; }
    /* Four destinations fit one row on a phone: an equal-width bottom-style bar
       at the top, each a full-height target, no wrapping to three ragged rows. */
    /* V1 step three — the nav STAYS (sticky) and compacts as the page scrolls.
       No script: the state is
       a pure function of scroll position, so nothing can be stuck collapsed and
       nothing changes when scripting is off. Where scroll-driven animation is
       unsupported the nav is simply sticky at full size. */
    nav.side { position:sticky; top:0; z-index:2; display:flex; align-items:center; gap:var(--space-4);
      padding:10px 12px; border-inline-end:none; border-bottom:1px solid var(--color-border); }
    /* The mark belongs to the product, so it sits with the product's nav — small,
       and without the word beside it. */
    nav.side .brand { display:flex; padding:0; margin-inline-end:var(--space-4); }
    nav.side .brand .mark-detail { display:none; }
    nav.side .brand .mark-small { display:flex; }
    nav.side .brand .brandname { display:none; }
    nav.side a.navlink { flex:1; flex-direction:row; flex-wrap:wrap; gap:var(--space-4); margin:0;
      padding:var(--space-8) var(--space-4); min-height:56px; align-items:center; justify-content:center;
      font-size:var(--font-size-caption); text-align:center; }
    @supports (animation-timeline: scroll()) {
      @media (prefers-reduced-motion: no-preference) {
        nav.side { animation: nav-compact linear both; animation-timeline: scroll(root); animation-range: 0 160px; }
        nav.side a.navlink { animation: navlink-compact linear both; animation-timeline: scroll(root); animation-range: 0 160px; }
      }
    }
    main { padding:var(--space-16); }
    .msg { max-width:92%; }
    .stats { grid-template-columns: repeat(2,1fr); }
    .frow { flex-direction:column; align-items:flex-start; gap:var(--space-4); }
    .flabel { min-width:0; font-size:var(--font-size-caption); }
  }
  /* V1 step three — the compaction the sticky phone nav plays over the first
     160px of scroll (see the phone block). Spacing from the scale only. */
  @keyframes nav-compact { to { padding-top:var(--space-4); padding-bottom:var(--space-4); } }
  @keyframes navlink-compact { to { min-height:44px; padding-top:var(--space-4); padding-bottom:var(--space-4); } }

  .login { max-width: var(--measure-form); margin: 10vh auto; padding: 0 var(--space-16); }
  .login .top-sw { display:flex; justify-content:center; margin-bottom:var(--space-16); }
  .login .card { padding: var(--space-24); }
  .login .brand { font-weight:700; font-size:var(--font-size-title); margin-bottom:var(--space-8); padding:0; }
  .login h1 { font-size:var(--font-size-base); margin:0 0 var(--space-8); }
  .login .lead { color:var(--color-ink-secondary); font-size:var(--font-size-caption); margin:0 0 var(--space-16); }
  input { width:100%; padding:12px 14px; border-radius:var(--radius-card);
    border:1px solid var(--color-border); background:var(--color-paper-sunk);
    color:var(--color-ink); font-size:var(--font-size-base); margin:var(--space-8) 0 var(--space-16); }
  /* M49 — as wide as its word, like every other button in the product. */
  button { min-height:44px; padding:12px var(--space-24); border:0; border-radius:var(--radius-card);
    background:var(--color-jade); color:var(--color-surface); font-weight:600;
    font-size:var(--font-size-small); cursor:pointer; }
  button:hover { background:var(--color-jade-deep); }
  select { width:100%; min-height:44px; padding:10px 14px; border-radius:var(--radius-card);
    border:1px solid var(--color-border); background:var(--color-paper-sunk);
    color:var(--color-ink); font-size:var(--font-size-base); margin:var(--space-8) 0 var(--space-16); }
  .login h2 { font-size:var(--font-size-small); color:var(--color-ink-secondary); margin:var(--space-24) 0 var(--space-8); font-weight:600; }
  .login form > h2:first-child { margin-top:0; }
  fieldset.checks { border:0; padding:0; margin:0 0 var(--space-16); }
  fieldset.checks legend { color:var(--color-ink-secondary); font-size:var(--font-size-caption); padding:0; margin-bottom:var(--space-8); }
  label.check { display:inline-flex; align-items:center; gap:var(--space-8); min-height:44px; margin-inline-end:var(--space-16); color:var(--color-ink); }
  label.check input { width:auto; margin:0; }
  .err { color:var(--color-warn); font-size:var(--font-size-caption); margin-bottom:var(--space-8); }
  .fld-err { color:var(--color-warn); font-size:var(--font-size-caption); margin:calc(-1 * var(--space-8)) 0 var(--space-16); }
  .hint { color:var(--color-ink-secondary); font-size:var(--font-size-caption); margin:calc(-1 * var(--space-8)) 0 var(--space-16); }
  label { color:var(--color-ink-secondary); font-size:var(--font-size-caption); display:block; }
  details { margin-top:var(--space-24); border-top:1px solid var(--color-border); padding-top:var(--space-16); }
  summary { cursor:pointer; color:var(--color-ink-secondary); font-size:var(--font-size-caption); min-height:44px; display:flex; align-items:center; }
  details form { margin-top:var(--space-8); }
  .login .other { text-align:center; margin:var(--space-16) 0 0; font-size:var(--font-size-caption); }
  .login .foot { text-align:center; font-size:var(--font-size-caption); }
