/* Reclaim: one stylesheet for every surface.
 * ===========================================================================
 *
 * The brief, in one line: PHOTOGRAPHS ARE OBJECTS. The page is a pale wall
 * they hang on. Green is what is safe, clay is the single moment the client
 * permits removal, and nothing else gets a colour.
 *
 * Specification: docs/design/site-redesign/design-system.md. Reference
 * rendering: docs/design/site-redesign/b-shoebox.html. Where the two disagree
 * the document wins; the prototype still carries marketing devices (the
 * pile-to-stack animation, photo-corner mounts, Fraunces above 40px) that
 * deliberately do not travel into the app.
 *
 * This file replaces the three inline <style> blocks that used to live in
 * client.html, admin.html and admin-login.html, and it absorbs forms.css. The
 * `.dt-*` table set was copied into two pages and had already drifted between
 * them; there is one copy now, here.
 *
 * ---------------------------------------------------------------------------
 * CONTRAST, MEASURED. Every pair below was computed with the WCAG relative
 * luminance formula on 2026-09-02. "AA" is 4.5:1 for text under 24px regular
 * or 18.66px bold; non-text (outlines, bar fills, icons) needs 3:1.
 *
 *   token         value      role                              measured
 *   --wall        #e4e9e3    page ground on every surface       --
 *   --print       #fffdf8    cards, inputs, table bodies        --
 *   --ink         #1c1a17    text            14.1:1 on wall, 17.1:1 on print
 *   --muted       #5e665f    secondary text  4.82:1 on wall, 5.83:1 on print
 *   --line        #c8d0c9    decorative rules                   1.28:1 on wall,
 *                                                               1.63:1 on print
 *   --line-print  #eee9e0    decorative rules inside a print    1.19:1 on print
 *   --line-control #77887a   boundaries of enabled controls     3.05:1 on wall,
 *                                                               3.70:1 on print
 *   --green       #1f6f5c    green as INK: links, verified,     4.89:1 on wall
 *                            "stays in iCloud", the tick       as text
 *   --control     #1f6f5c    green as a SURFACE: the primary    on-control on
 *                            button, the bars, a pressed chip,  it 5.92:1;
 *                            a checked box. The same value as   4.89:1 on wall
 *                            --green in light and not in dark
 *   --control-dk  #14503f    the hover on a control fill        on-control on
 *                                                               it 9.19:1
 *   --on-control  #fffdf8    the label on a control fill; one   see the two
 *                            value in both appearances          pairs above
 *   --green-dk    #14503f    text on green-soft, and links      on green-soft
 *                                                               8.17:1
 *   --green-soft  #e8f2ee    the ok note, the done pill         --
 *   --green-line  #c3ddd4    the ok note's edge on a print      non-text
 *   --clay        #a8562a    the removal decision, only         4.23:1 on wall,
 *                            so never small text; white on it 5.20:1
 *   --clay-ink    #8f461f    small text in a clay element       5.77:1 on
 *                            clay-soft, 5.57:1 on wall
 *   --clay-soft   #f7e9de    ground of the removal decision     --
 *   --slate       #5d6a8c    in motion: copied out, running     5.29:1 on
 *                            print as text, 4.37:1 on wall so FILL ONLY there
 *   --slate-ink   #3f4a5c    text on slate-soft                 7.37:1
 *   --slate-soft  #e6e9f0    the running pill, the legend dot   --
 *   --stop        #8b2f2f    danger fill                        white 8.25:1
 *   --stop-ink    #7a2626    text in the bad note               8.43:1 on
 *                                                               stop-soft
 *   --stop-soft   #f9eaea    the bad note, the failed pill      --
 *   --stop-line   #e0b9b9    the bad note's edge on a print     non-text
 *   --wall-half   #f2f3ee    a hovered or opened table row      non-text
 *   --off         #dfe3de    a disabled control's ground        exempt
 *   --on-clay     #ffffff    text on a clay or a stop FILL.     see the two
 *   --on-stop     #ffffff    Never write the literal at a call  pairs above
 *                            site: in dark it is the wall
 *   --stop-dk     #6f2424    the danger button's hover          white 10.75:1
 *
 * ---------------------------------------------------------------------------
 * THE DARK APPEARANCE. The three application surfaces -- the client page, the
 * operator console and its sign-in -- follow the system. THE PUBLIC SITE DOES
 * NOT: `site/index.html`, `privacy.html` and `terms.html` carry
 * `data-appearance="light"` on their <html> element, and the dark block below
 * is scoped `:root:not([data-appearance="light"])`, so the marketing pages keep
 * the sage wall in every context on one attribute each and `site/static/
 * site.css` needs no change at all. The attribute, rather than a second
 * stylesheet or a duplicated token set, is what keeps ONE reclaim.css: the site
 * copy under `site/static/` is this file byte for byte and `tests_site.py`
 * asserts so.
 *
 * THE ATTRIBUTE NOW CARRIES THREE ANSWERS, NOT ONE, because the application
 * pages gained a per-device switch (TBX-5203) and the phone had one first.
 *   absent   System. The media query decides, which is also what a page with
 *            no JavaScript gets, so following the device costs no script.
 *   "light"  Light, whatever the device says. The same value the three
 *            marketing pages have always carried, so the site is simply a page
 *            that answered this question in its markup.
 *   "dark"   Dark, whatever the device says. This is the one the media query
 *            cannot express, and the reason the token set below appears twice.
 * The choice lives in `localStorage` under `reclaim.appearance`, never a
 * cookie: the privacy notice states these pages set no cookie, and a
 * preference is not worth reopening that sentence for. It is written by an
 * inline script in the <head> of each application page, ABOVE the link to this
 * file, so the attribute is on <html> before the first paint and no client
 * ever sees the light wall flash on the way to the dark one.
 *
 * The dark values come from the phone. `--wall`, `--print`, `--ink`, `--muted`,
 * the three greens, the two slates and the three stops are the iOS
 * `ReclaimTheme` set published in section 3a of the document, unchanged, so one
 * colour still means one thing across the web and the app. The rest were chosen
 * by hand here and MEASURED; `scripts/contrast_check.py` prints the table below
 * from this file and fails on a pair that misses its bar.
 *
 *   token         value      measured in dark
 *   --wall        #0e2620    the screen ground
 *   --print       #133129    a card on it, 1.14:1 -- a shadow does the rest
 *   --wall-half   #1d3f36    a hovered row: LIGHTER than a print, not darker,
 *                            because on a dark ground a hover is a lift
 *   --ink         #f3ede2    13.69:1 on wall, 12.02:1 on print
 *   --muted       #b7c6be    8.99:1 on wall, 7.89:1 on print
 *   --line        #31544a    1.90:1 on wall, 1.67:1 on print
 *   --line-print  #254a40    1.42:1 on print
 *   --line-control #698378   3.89:1 on wall, 3.41:1 on print -- WCAG 1.4.11
 *   --green       #8fd9be    green as ink: 9.75:1 on wall, 8.56:1 on print
 *   --control     #2c7c62    green as a surface, and the pair that forced the
 *                            split: on-control on it is 4.96:1, where on the
 *                            mint it would be 1.64:1. 3.16:1 on the wall, and
 *                            2.78:1 on a print, which is the one residual and
 *                            is recorded in section 3a of the document
 *   --control-dk  #1f6f5c    the hover, which is the LIGHT value of --control,
 *                            so the gesture deepens the fill in both
 *                            appearances; on-control on it 5.92:1
 *   --on-control  #fffdf8    unchanged from light: the fill is the darker
 *                            element in both appearances
 *   --green-dk    #bfeedc    12.51:1 on wall, 9.37:1 on green-soft
 *   --green-soft  #1a3d33    the ok note, the done pill
 *   --green-line  #2a5f4e    1.90:1 on print, non-text
 *   --clay        #e0a878    7.63:1 on wall; the wall on it 7.63:1
 *   --clay-ink    #f0c8a4    9.18:1 on clay-soft, 10.25:1 on wall
 *   --clay-soft   #3d2a1b    ground of the removal decision
 *   --slate       #a9b7d6    7.92:1 on wall, 6.96:1 on print
 *   --slate-ink   #c9d3e8    8.79:1 on slate-soft
 *   --slate-soft  #22304a    the running pill
 *   --stop        #e99a9a    the wall on it 7.26:1
 *   --stop-dk     #f0c4c4    the wall on it 10.17:1
 *   --stop-ink    #f3bdbd    8.84:1 on stop-soft, 8.56:1 on print
 *   --stop-soft   #3d2222    the bad note, the failed pill
 *   --stop-line   #6b3a3a    1.53:1 on print, non-text
 *   --off         #204237    a disabled control's ground, exempt; 1.26:1 on
 *                            print, the same separation the light one has, so
 *                            a disabled button is still a shape on a card
 *   --on-clay     the wall   7.63:1 on clay
 *   --on-stop     the wall   7.26:1 on stop, 10.17:1 on stop-dk
 *
 * THE PRIMARY BUTTON IS THE PAIR THAT BREAKS FIRST, AND IT BREAKS IN BOTH
 * DIRECTIONS. In dark the green a mark is drawn in has to be mint, and white
 * on mint is 1.64:1; so the fill is not that green, it is `--control`, and
 * the label on it is `--on-control` at 4.96:1. Clay and stop have the opposite
 * problem -- their dark values ARE the light half of the pair -- so those two
 * fills take the wall as their text, through `--on-clay` and `--on-stop`.
 * Either way nothing writes `#fff` at a call site any more. The two chevrons
 * are the only colours left that a token cannot reach, because a `data:` URI
 * is a string; they are redrawn in the dark block at the foot of this file.
 *
 * `scripts/contrast_audit.js` re-measures every text node inside every button
 * variant WITH EVERY :hover RULE APPLIED. Run it after touching any button:
 * the exclusion-list hover rule this file deletes existed because three
 * variants in a row silently inherited a 1.86:1 hover.
 *
 * TWO THINGS THAT ARE NOT A RE-SKIN, both argued in section 9 of the document:
 *
 *   CLAY MEANS ONE THING. Ten `.note.gate` uses in the client page were doing
 *   duty for instructions, limitations and statuses. Clay is the client's
 *   authority over removal and nothing else, so only the removal decision
 *   keeps it, and the confirm button beside it is the one clay-filled button
 *   that exists anywhere.
 *
 *   SLATE REPLACES THE ORANGE. "Copied out" and "running" used `#bf5a12`,
 *   which sat beside clay and diluted the one meaning clay has. A photograph
 *   in motion is not a decision, so it takes slate.
 *
 * WHY @font-face IS IN THE PAGES AND NOT IN HERE. `app/pages.py` stamps every
 * /static reference in an HTML response with a content version, which is what
 * makes the one-year max-age on assets correct rather than dangerous. It does
 * not rewrite CSS, so a `url()` written here would be the one asset URL on the
 * site with no version on it -- the exact shape of the defect that made a
 * deploy invisible for four hours on 2026-09-01. The face declarations
 * therefore sit in each page's small inline block, where they get stamped.
 * ======================================================================== */

:root {
  /* --- colour ------------------------------------------------------------ */
  --wall: #e4e9e3;
  --print: #fffdf8;
  /* The wall at 50% over a print. Table row hover and an opened row; stated
     rather than mixed at run time so it reads in a colour picker. */
  --wall-half: #f2f3ee;
  --ink: #1c1a17;
  --muted: #5e665f;
  --line: #c8d0c9;
  --line-print: #eee9e0;
  /* A control edge carries information; a divider does not. Keeping those
     jobs separate lets fields and buttons clear WCAG 1.4.11 without turning
     every rule on the page into a heavy frame. */
  --line-control: #77887a;

  --green: #1f6f5c;
  --green-dk: #14503f;
  --green-soft: #e8f2ee;
  /* The border of a soft-ground note: the soft ground darkened just enough to
     draw an edge on a print. Non-text, so 3:1 is the bar and both clear it. */
  --green-line: #c3ddd4;

  --clay: #a8562a;
  --clay-ink: #8f461f;
  --clay-soft: #f7e9de;

  --slate: #5d6a8c;
  --slate-ink: #3f4a5c;
  --slate-soft: #e6e9f0;

  --stop: #8b2f2f;
  /* The danger button's hover. It was a literal at the call site, which in a
     dark appearance would have gone on darkening a fill that is already
     light. */
  --stop-dk: #6f2424;
  --stop-ink: #7a2626;
  --stop-soft: #f9eaea;
  --stop-line: #e0b9b9;

  /* GREEN AS A SURFACE, NOT GREEN AS INK. `--green` is what says something:
     text, links, the verified tick, and the borders and underlines that mark
     a state on the page ground. `--control` is what a label sits on: the
     primary button, the progress bar, the split bar and its legend swatch,
     the stepper, the console's disk meter, a pressed chip, a checked box. In
     light the two are the same value, which is how one token did both jobs
     until a dark appearance existed -- see section 3a of the document, and
     the phone, which split the same token on the same day for the same
     measurement.
     `--control-dk` is the hover. The phone has none; on the web the hover
     deepens the fill in both appearances, and in dark it deepens to the LIGHT
     value of --control rather than to a colour invented for it. */
  --control: #1f6f5c;
  --control-dk: #14503f;
  /* Not `#fff` and not `--print`: --print is this value in light and #133129
     in dark, where it reads 2.78:1 on the fill. The label is the same
     near-white in both appearances because the fill is the darker element in
     both. */
  --on-control: #fffdf8;

  /* THE TEXT THAT SITS ON A CLAY OR A DANGER FILL. Those two families go the
     other way: their dark values are the LIGHT half of the pair, so white on
     them is unreadable and the ground is what reads. Written as tokens so no
     call site has to know which appearance it is in. */
  --on-clay: #ffffff;
  --on-stop: #ffffff;

  /* A disabled control SHOULD look inactive; WCAG 1.4.3 excludes it from the
     contrast minimum and `contrast_audit.js` reports it separately rather than
     failing on it. */
  --off: #dfe3de;

  /* --- type -------------------------------------------------------------- */
  /* Three families, each with exactly one job.
     Fraunces  headings, and numbers that are the point of a component.
     Atkinson  prose: paragraphs, notes, labels, captions, button text.
     --figures ANY digit read as a quantity. Atkinson's zero is slashed, so
               "10 GB" set in it reads as "1O GB". Prices, sizes, counts,
               dates and every table go in --figures. */
  /* The "stand-in" entries are metric-matched fallbacks declared in each
     page's head; they exist so `font-display: swap` does not move the layout
     when the real face lands. Measured in chromium on 2026-09-02 at 100px:
     Fraunces at opsz 96 is 0.88 of Georgia's width, and Atkinson is 0.98 of
     Helvetica Neue's. See the @font-face block in any of the three pages. */
  --display: "Fraunces", "Fraunces stand-in", "Iowan Old Style", Georgia, serif;
  --body: "Atkinson Hyperlegible", "Atkinson stand-in", -apple-system,
          BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  --figures: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial,
             sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, monospace;

  /* The scale, from the client page's existing steps plus three headings.
     Sizes used to be hardcoded at every call site and had drifted to nineteen
     values across three surfaces, five of them half-pixel. */
  --fs-nano: 11px;    /* badges, step captions */
  --fs-tiny: 12px;    /* metadata, table footers */
  --fs-body: 13px;    /* dense UI body text */
  --fs-label: 14px;   /* form labels, secondary prose */
  --fs-read: 15px;    /* reading text, and the base size */
  --fs-lead: 16px;    /* button text, lede */
  --fs-h3: 20px;
  --fs-h2: 26px;
  --fs-h1: 34px;

  /* --- space, on an 8px rhythm ------------------------------------------- */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 24px; --sp-6: 32px; --sp-7: 48px;

  /* --- radius: three values and no others -------------------------------- */
  --r-print: 3px;      /* anything that is a print: cards, notes, tables */
  --r-control: 10px;   /* buttons, inputs, selects */
  --r-pill: 999px;     /* chips, pills, bars */

  /* --- shadow: one recipe for a print at rest ---------------------------- */
  --sh-print: 0 1px 2px rgba(28, 26, 23, .10),
              0 10px 26px -18px rgba(28, 26, 23, .35);
  --sh-raised: 0 1px 2px rgba(28, 26, 23, .10),
               0 14px 30px -18px rgba(28, 26, 23, .45);
  --sh-btn: 0 1px 0 rgba(0, 0, 0, .08);
  --sh-btn-green: 0 1px 0 rgba(0, 0, 0, .08),
                  0 8px 20px -12px rgba(31, 111, 92, .6);

  /* THE COLOUR A LAYER CASTS, without its geometry: a drawer's shadow is
     offset sideways and a modal's is not, so the offsets stay at the call site
     and only the ink is a token. On a dark wall an ink shadow at 14 percent is
     nothing at all, which is why these are stated rather than left literal in
     admin.html. `--scrim` dims the page under a drawer or a dialog;
     `--scrim-deep` is the copy-cancellation dialog, which sits over a moving
     progress ledger and needs the page behind it to stop competing. */
  --shade: rgb(28 26 23 / .24);
  --shade-soft: rgb(28 26 23 / .14);
  --scrim: rgb(28 26 23 / .42);
  --scrim-deep: rgb(20 42 35 / .66);

  /* --- motion ------------------------------------------------------------ */
  --t-hover: .15s ease;
  --t-bar: .25s ease;

  /* Ink, never green and never clay, so a focus ring can never read as a
     state. Never removed on anything interactive. */
  --focus: 3px solid var(--ink);
  --focus-field: 2px solid var(--ink);

  /* The application follows the system. `var(--ink)` above is not resolved
     here but where it is used, so both focus rings travel into dark with the
     palette and nothing below has to state them twice. */
  color-scheme: light dark;
}

/* ===========================================================================
   THE DARK APPEARANCE, AND THE ONE GATE THAT KEEPS THE SITE OUT OF IT
   The measured table is in the header. The gate is a single attribute on the
   three marketing pages: `data-appearance="light"` on <html>. It is written as
   an opt-OUT rather than an opt-in because the application is three pages and
   the site is three pages, and an opt-in would have to be remembered on every
   new application surface -- a page that silently keeps the light palette in a
   dark system is exactly the defect nobody notices until a client reports it.
   ======================================================================== */
:root[data-appearance="light"] { color-scheme: light; }

@media (prefers-color-scheme: dark) {
  :root:not([data-appearance="light"]) {
    --wall: #0e2620;
    --print: #133129;
    /* A HOVER IS A LIFT HERE, NOT A DIP. In light this token is the wall laid
       over a print, i.e. darker than the row it replaces. On a dark ground the
       same gesture has to go the other way or a hovered row reads as a hole. */
    --wall-half: #1d3f36;
    --ink: #f3ede2;
    --muted: #b7c6be;
    --line: #31544a;
    --line-print: #254a40;
    --line-control: #698378;

    --green: #8fd9be;
    --green-dk: #bfeedc;
    --green-soft: #1a3d33;
    --green-line: #2a5f4e;

    /* Clay is absent from the phone -- the companion apps never remove
       anything -- so these three are the one family with no ReclaimTheme value
       to copy. Chosen here and measured: the removal decision has to stay the
       one warm thing on a green screen without becoming the loudest. */
    --clay: #e0a878;
    --clay-ink: #f0c8a4;
    --clay-soft: #3d2a1b;

    --slate: #a9b7d6;
    --slate-ink: #c9d3e8;
    --slate-soft: #22304a;

    --stop: #e99a9a;
    --stop-dk: #f0c4c4;
    --stop-ink: #f3bdbd;
    --stop-soft: #3d2222;
    --stop-line: #6b3a3a;

    /* A DISABLED BUTTON STILL HAS TO BE A SHAPE. At the first value tried
       (#1b322b) this was 1.03:1 against a print, so the "Remove from iCloud"
       button on the consent screen and both danger buttons on the result
       screen were invisible until they enabled: a client would have read the
       screen as having no button at all rather than one they cannot press
       yet. In light it stands 1.28:1 off a print, and this now matches. */
    --off: #204237;

    /* GREEN SPLITS HERE AND NOWHERE ELSE. Mint is right for a mark on a dark
       ground and far too light to sit under a light label: `--on-control` on
       mint is 1.64:1. So the fill keeps its own value, close enough to the
       light one that the primary button is recognisably the same button, and
       the label stays the same near-white in both appearances. These two are
       the phone's `ReclaimTheme.control` and `.controlInk` exactly.
       The hover deepens to the LIGHT value of --control. */
    --control: #2c7c62;
    --control-dk: #1f6f5c;

    /* Clay and stop go the other way: their dark values are the light half of
       the pair, so the text on them is the ground. */
    --on-clay: var(--wall);
    --on-stop: var(--wall);

    /* An ink shadow at ten percent does nothing over a dark wall. A print is
       separated here by being LIGHTER than the wall by 1.14:1 plus a shadow
       that is genuinely darker than the ground under it. */
    --sh-print: 0 1px 2px rgb(0 0 0 / .45),
                0 10px 26px -18px rgb(0 0 0 / .85);
    --sh-raised: 0 1px 2px rgb(0 0 0 / .45),
                 0 14px 30px -18px rgb(0 0 0 / .9);
    --sh-btn: 0 1px 0 rgb(0 0 0 / .35);
    --sh-btn-green: 0 1px 0 rgb(0 0 0 / .35),
                    0 8px 20px -12px rgb(44 124 98 / .6);
    --shade: rgb(0 0 0 / .70);
    --shade-soft: rgb(0 0 0 / .55);
    --scrim: rgb(0 0 0 / .66);
    --scrim-deep: rgb(0 0 0 / .80);
  }
}

/* THE SAME SET AGAIN, THIS TIME BECAUSE SOMEBODY ASKED FOR IT. The switch in
   the top bar writes `data-appearance="dark"` when a client chooses Dark on a
   device whose system is light, and no declaration inside the media query
   above can reach that case: CSS has no way to share one block across a media
   boundary. So the token set is repeated here, deliberately, and
   `tests_style.py` compares the two blocks declaration for declaration and
   fails on the first one that drifts. That guard is the whole reason a
   duplicate is acceptable at all; the .dt-* rule set was copied by hand into
   two pages with nothing watching, and it had diverged by the time anyone
   looked.

   `color-scheme` is the one declaration the media block does not need and this
   one does. Up there the root's `light dark` plus a dark system already tells
   the browser which way its form controls go. Down here the system says light
   and the page says otherwise, so it has to be stated. */
:root[data-appearance="dark"] {
  color-scheme: dark;

  --wall: #0e2620;
  --print: #133129;
  /* A HOVER IS A LIFT HERE, NOT A DIP. In light this token is the wall laid
     over a print, i.e. darker than the row it replaces. On a dark ground the
     same gesture has to go the other way or a hovered row reads as a hole. */
  --wall-half: #1d3f36;
  --ink: #f3ede2;
  --muted: #b7c6be;
  --line: #31544a;
  --line-print: #254a40;
  --line-control: #698378;

  --green: #8fd9be;
  --green-dk: #bfeedc;
  --green-soft: #1a3d33;
  --green-line: #2a5f4e;

  /* Clay is absent from the phone -- the companion apps never remove
     anything -- so these three are the one family with no ReclaimTheme value
     to copy. Chosen here and measured: the removal decision has to stay the
     one warm thing on a green screen without becoming the loudest. */
  --clay: #e0a878;
  --clay-ink: #f0c8a4;
  --clay-soft: #3d2a1b;

  --slate: #a9b7d6;
  --slate-ink: #c9d3e8;
  --slate-soft: #22304a;

  --stop: #e99a9a;
  --stop-dk: #f0c4c4;
  --stop-ink: #f3bdbd;
  --stop-soft: #3d2222;
  --stop-line: #6b3a3a;

  /* A DISABLED BUTTON STILL HAS TO BE A SHAPE. At the first value tried
     (#1b322b) this was 1.03:1 against a print, so the "Remove from iCloud"
     button on the consent screen and both danger buttons on the result
     screen were invisible until they enabled: a client would have read the
     screen as having no button at all rather than one they cannot press
     yet. In light it stands 1.28:1 off a print, and this now matches. */
  --off: #204237;

  /* GREEN SPLITS HERE AND NOWHERE ELSE. Mint is right for a mark on a dark
     ground and far too light to sit under a light label: `--on-control` on
     mint is 1.64:1. So the fill keeps its own value, close enough to the
     light one that the primary button is recognisably the same button, and
     the label stays the same near-white in both appearances. These two are
     the phone's `ReclaimTheme.control` and `.controlInk` exactly.
     The hover deepens to the LIGHT value of --control. */
  --control: #2c7c62;
  --control-dk: #1f6f5c;

  /* Clay and stop go the other way: their dark values are the light half of
     the pair, so the text on them is the ground. */
  --on-clay: var(--wall);
  --on-stop: var(--wall);

  /* An ink shadow at ten percent does nothing over a dark wall. A print is
     separated here by being LIGHTER than the wall by 1.14:1 plus a shadow
     that is genuinely darker than the ground under it. */
  --sh-print: 0 1px 2px rgb(0 0 0 / .45),
              0 10px 26px -18px rgb(0 0 0 / .85);
  --sh-raised: 0 1px 2px rgb(0 0 0 / .45),
               0 14px 30px -18px rgb(0 0 0 / .9);
  --sh-btn: 0 1px 0 rgb(0 0 0 / .35);
  --sh-btn-green: 0 1px 0 rgb(0 0 0 / .35),
                  0 8px 20px -12px rgb(44 124 98 / .6);
  --shade: rgb(0 0 0 / .70);
  --shade-soft: rgb(0 0 0 / .55);
  --scrim: rgb(0 0 0 / .66);
  --scrim-deep: rgb(0 0 0 / .80);
}


/* ===========================================================================
   BASE
   ======================================================================== */
* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--wall);
  color: var(--ink);
  font: 400 var(--fs-read)/1.6 var(--body);
  -webkit-font-smoothing: antialiased;
}

/* Every interactive thing has an ink ring. Bounded fields take the finer
   field variant below; unbounded controls keep this stronger default. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible,
[data-sort]:focus-visible { outline: var(--focus); outline-offset: 3px; }

h1, h2, h3 {
  font-family: var(--display);
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: -.015em;
  margin: 0;
}
/* opsz 96 for a section heading, 30 for anything under 24px. 144 belongs to
   the marketing hero and is not used in the app. */
h1 { font-size: var(--fs-h1); font-variation-settings: "opsz" 96, "SOFT" 50; }
h2 { font-size: var(--fs-h2); font-variation-settings: "opsz" 96, "SOFT" 50; }
h3 { font-size: var(--fs-h3); font-weight: 500;
     font-variation-settings: "opsz" 30, "SOFT" 50; }

a { color: var(--green-dk); }

/* THE FIGURES RULE, as a class, so a number can opt in wherever it appears. */
.fig, .num { font-family: var(--figures); font-variant-numeric: tabular-nums; }

.hidden { display: none !important; }
.nw { white-space: nowrap; }

/* ===========================================================================
   LAYOUT
   ======================================================================== */
.wrap {
  max-width: var(--wrap-max, 720px);
  margin: 0 auto;
  padding: 44px var(--sp-5) 80px;
}

/* The wordmark: "Reclaim" in Fraunces 600, opsz 30, ink, and nothing beside
   it. The mark was withdrawn from every web header by the owner on
   2026-09-08 after three rendered forms and the flat form were tried at
   30px; it still serves the tab, the manifest, the link preview and the PDF
   record. THE FULL STOP IS GONE: the prototype dropped it and nothing missed
   it. The img rule stays for any page that still sets one. */
.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font: 600 var(--fs-h3)/1 var(--display);
  font-variation-settings: "opsz" 30, "SOFT" 50;
  letter-spacing: -.01em;
  color: var(--ink);
  margin-bottom: var(--sp-6);
}
.brand img { width: 30px; height: 30px; }

.sub { color: var(--muted); margin: var(--sp-2) 0 var(--sp-5);
       font-size: var(--fs-lead); }
.cap { font-size: var(--fs-label); color: var(--muted); margin-top: 9px; }
.hint { font-size: var(--fs-body); color: var(--muted); line-height: 1.6;
        margin-top: 6px; }
.validation-help { max-width: 62ch; margin: var(--sp-2) 0 0;
  color: var(--muted); font-size: var(--fs-label); line-height: 1.5; }
.validation-help:empty { display: none; }
.row { display: flex; gap: var(--sp-3); align-items: center; flex-wrap: wrap; }

/* ===========================================================================
   PRINT (card)
   No border. The border was doing the shadow's job; on the sage wall a print
   reads as an object without one. A print INSIDE a print (a note in a card)
   takes a --line-print border and no shadow instead -- see .note.
   ======================================================================== */
.card {
  background: var(--print);
  border-radius: var(--r-print);
  box-shadow: var(--sh-print);
  padding: var(--sp-5);
  margin-bottom: var(--sp-3);
}
@media (max-width: 560px) { .card { padding: 18px; } }
/* A PRINT ON A PRINT TAKES AN EDGE, NOT A SHADOW. A tile or a table inside a
   card is the same white on the same white, so the shadow that separates a
   card from the sage wall has nothing to separate here and only reads as
   grime. The rule is stated once, on the container, rather than remembered
   at each component. */
.card .tile, .card .dt-wrap, .card .lane {
  box-shadow: none;
  border: 1px solid var(--line-print);
}

/* ===========================================================================
   BUTTONS
   One family: padding 13px 22px, --r-control, Atkinson 700 at --fs-lead, and a
   44px minimum height.
   THE EXCLUSION-LIST HOVER RULE IS GONE. It read
     button:not(.ghost):not(.chip):not(.danger):not(.dt-pg):not(.dt-caret-btn):hover
   and it had to name every variant that paints itself. It could not: the list
   is only ever extended after somebody notices, and .dt-pg and .dt-caret-btn
   inherited a 1.86:1 and a 1.56:1 hover from it for weeks.
   EVERY VARIANT NOW STATES ITS OWN HOVER, and the primary's hover is written
   as an INCLUSION -- `button:not([class])`, plus `.primary` for the day a
   primary needs a utility class -- rather than as a list of exclusions. The
   difference matters and it is the whole point: a new variant necessarily
   carries a class, so it cannot inherit this, and nobody has to remember to
   add it to a list. It also matters to `scripts/contrast_audit.js`, which
   forces each :hover rule onto everything its selector matches; a hover
   written broadly enough to reach a variant it does not apply to in practice
   would be reported, correctly, as a pair somebody has to think about.
   ======================================================================== */
button, a.btn, .btn, a.cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 13px 22px;
  border: 0;
  border-radius: var(--r-control);
  font: 700 var(--fs-lead)/1.2 var(--body);
  text-decoration: none;
  cursor: pointer;
  background: var(--control);
  color: var(--on-control);
  box-shadow: var(--sh-btn-green);
  transition: background var(--t-hover), color var(--t-hover),
              border-color var(--t-hover);
}
button:not([class]):hover, button.primary:hover,
a.btn:hover, .btn:hover, a.cta:hover { background: var(--control-dk);
  color: var(--on-control); }

button.ghost, a.btn-ghost, a.ghost, .btn-ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 13px 22px;
  border: 1px solid var(--line-control);
  border-radius: var(--r-control);
  font: 700 var(--fs-lead)/1.2 var(--body);
  text-decoration: none;
  cursor: pointer;
  background: var(--print);
  color: var(--green);
  box-shadow: var(--sh-btn);
  transition: background var(--t-hover), color var(--t-hover),
              border-color var(--t-hover);
}
button.ghost:hover, a.btn-ghost:hover, a.ghost:hover, .btn-ghost:hover {
  background: var(--green-soft);
  color: var(--green-dk);
  border-color: var(--green-dk);
}

button.danger, .btn-danger {
  background: var(--stop);
  color: var(--on-stop);
  box-shadow: var(--sh-btn);
}
button.danger:hover, .btn-danger:hover { background: var(--stop-dk);
  color: var(--on-stop); }

/* THE ONE CLAY-FILLED BUTTON THAT EXISTS. It confirms removal from iCloud,
   which is the single moment clay is for. White on clay is 5.20:1. */
button.gate {
  background: var(--clay);
  color: var(--on-clay);
  box-shadow: var(--sh-btn);
}
button.gate:hover { background: var(--clay-ink); color: var(--on-clay); }

button[disabled], button[disabled]:hover,
button.ghost[disabled], button.ghost[disabled]:hover,
button.danger[disabled], button.danger[disabled]:hover,
button.gate[disabled], button.gate[disabled]:hover {
  background: var(--off);
  color: var(--muted);
  border-color: var(--off);
  box-shadow: none;
  cursor: not-allowed;
}

/* A link that reads as a button in running text: the record's "open" pill and
   the console's small actions. Pill shaped, 44px hit area, visible box small. */
a.open, .btn-mini {
  position: relative;
  display: inline-flex;
  align-items: center;
  margin-left: 9px;
  padding: 2px 10px;
  border: 1px solid var(--line-control);
  border-radius: var(--r-pill);
  background: var(--print);
  color: var(--green-dk);
  font: 700 var(--fs-tiny)/1.6 var(--body);
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--t-hover), color var(--t-hover),
              border-color var(--t-hover);
}
a.open:hover, .btn-mini:hover {
  background: var(--green-soft);
  color: var(--green-dk);
  border-color: var(--green-dk);
}
/* 44px of tappable, without a 44px visible pill. Vertical only, so it cannot
   overlap the element beside it. */
a.open::after, .btn-mini::after, .chip::after, .dt-pg::after,
.dt-caret-btn::after, .pw-reveal::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  top: 50%;
  transform: translateY(-50%);
  height: 44px;
}

/* ===========================================================================
   NOTES: four kinds, one meaning each.
   plain  an instruction, or a status
   ok     a reassurance that is true right now
   bad    something went wrong
   gate   THE REMOVAL DECISION, and nothing else, anywhere
   ======================================================================== */
.note {
  background: var(--print);
  border: 1px solid var(--line-print);
  border-radius: var(--r-print);
  padding: var(--sp-4) 18px;
  font-size: var(--fs-read);
  color: var(--ink);
  margin-top: var(--sp-4);
}
.note strong { display: block; margin-bottom: var(--sp-1); }
.note.ok   { background: var(--green-soft); border-color: var(--green-line);
             color: var(--green-dk); }
.note.bad  { background: var(--stop-soft); border-color: var(--stop-line);
             color: var(--stop-ink); }
.note.gate { background: var(--clay-soft); border: 0;
             outline: 2px solid var(--clay); outline-offset: -2px;
             color: var(--clay-ink); }

.err { color: var(--stop-ink); font-weight: 700; font-size: var(--fs-read);
       margin-top: var(--sp-3); }

/* ===========================================================================
   FIELDS
   ======================================================================== */
.field { margin-bottom: var(--sp-4); }

label {
  display: block;
  font: 700 var(--fs-label)/1.5 var(--body);
  color: var(--muted);
  margin: 0 0 6px;
}

input, textarea {
  width: 100%;
  padding: 12px 13px;
  border: 1px solid var(--line-control);
  border-radius: var(--r-control);
  /* 16px so iOS does not zoom the page when the field takes focus. */
  font: 400 var(--fs-lead)/1.4 var(--body);
  background: var(--print);
  color: var(--ink);
}
input[list]::-webkit-calendar-picker-indicator {
  width: 12px;
  height: 8px;
  padding: 4px;
  opacity: 1;
  color: transparent;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235e665f' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 12px 8px no-repeat;
}
input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  width: 20px;
  height: 20px;
  min-height: 20px;
  padding: 0;
  border: 1px solid var(--line-control);
  border-radius: 5px;
  background: var(--print);
  cursor: pointer;
}
/* THE CHECK IS DRAWN IN `--on-control` AND THE BOX FILLS WITH `--control`,
   which is why this needs no dark restatement: the stroke is the same
   near-white in both appearances because the fill is the darker element in
   both. It was `white` on `--green`, and green's dark value is mint. */
input[type="checkbox"]:checked {
  border-color: var(--control);
  background: var(--control) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='10' viewBox='0 0 12 10'%3E%3Cpath d='m1 5 3 3 7-7' fill='none' stroke='%23fffdf8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 12px 10px no-repeat;
}
input[type="checkbox"]:disabled { opacity: .55; cursor: not-allowed; }
input:focus, input:focus-visible,
textarea:focus, textarea:focus-visible,
select:focus, select:focus-visible {
  /* A bounded field already has its own edge. One step finer than the focus
     ring on buttons and links keeps the combined border legible, not heavy. */
  outline: var(--focus-field); outline-offset: 1px;
  border-color: var(--line-control);
}
input.bad, input[aria-invalid="true"] { border-color: var(--stop); }
.field .err { font-size: var(--fs-body); font-weight: 400; margin-top: 6px; }

/* The select, absorbed from forms.css. The native chevron cannot be
   positioned, only replaced: appearance:none removes it and the arrow is drawn
   as a background image, which CAN be inset from the border. That is the whole
   reason this block exists rather than a padding tweak.
   IT NO LONGER PINS `color-scheme: light`. The dropdown a native select opens
   is painted by the browser, not by this file, so pinning it light would have
   opened a white list of options out of a dark control -- the one part of the
   page a stylesheet cannot then fix. `color-scheme` inherits, so removing the
   line leaves the light appearance exactly as it was and lets the dark one
   through. */
:root { --sel-arrow-w: 34px; --sel-arrow-inset: 13px; }
.select {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  width: 100%;
  padding: 12px var(--sel-arrow-w) 12px 13px;
  border: 1px solid var(--line-control);
  border-radius: var(--r-control);
  background-color: var(--print);
  color: var(--ink);
  font: inherit;
  line-height: 1.3;
  cursor: pointer;
  /* Encoded rather than linked, so the control needs no second request and
     cannot render arrowless if one fails. The stroke is --muted. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235e665f' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--sel-arrow-inset) center;
  background-size: 12px 8px;
}
.select:hover { border-color: var(--muted); }
.select:focus, .select:focus-visible {
  outline: var(--focus-field); outline-offset: 1px;
  border-color: var(--line-control);
}
.select:disabled { opacity: .55; cursor: default; background-color: var(--wall); }
.select.sm {
  --sel-arrow-w: 26px;
  --sel-arrow-inset: 9px;
  padding: 6px var(--sel-arrow-w) 6px 8px;
  border-radius: 8px;
  font-size: var(--fs-body);
  background-size: 10px 7px;
}

/* ---------------------------------------------------------------------------
   THE APPEARANCE SWITCH: System, Light, Dark (TBX-5203)
   In the top bar of the three application pages, and on none of the three
   marketing pages, which state their appearance in their own markup.

   A NATIVE SELECT, on purpose. The choice is one of three, so a select brings
   its own keyboard handling and its own picker on a phone, and `.select`
   already redraws its chevron for the dark appearance -- which matters more
   here than anywhere, because the control that changes the appearance would
   be a poor one to leave looking wrong in it. The only thing `.select` does
   that this does not want is fill its container.
   ------------------------------------------------------------------------ */
.appearance { width: auto; vertical-align: middle; }

/* The wordmark and the switch on one line, for the two pages with no other
   top bar to hold it. The row carries the wordmark's own bottom margin, so
   the page below it does not move. */
.appearance-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-4); margin-bottom: var(--sp-6);
}
.appearance-row .brand { margin-bottom: 0; }

/* A password you cannot see is a password you cannot check. Especially here:
   the operator pastes a generated string, and the client types an Apple
   password they will not get many attempts at. */
.pw-wrap { position: relative; }
.pw-wrap input { padding-right: 74px; }
.pw-reveal {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  min-height: 0;
  width: auto;
  padding: 6px 10px;
  background: none;
  border: 0;
  border-radius: 8px;
  box-shadow: none;
  color: var(--green-dk);
  font: 700 var(--fs-body)/1.2 var(--body);
}
.pw-reveal:hover { background: var(--green-soft); color: var(--green-dk); }

/* ===========================================================================
   CHIPS
   32px visible box, 44px hit area (the ::after above). Pressed is the green
   fill; the count goes white at 85%.
   ======================================================================== */
.chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: 6px 12px;
  border: 1px solid var(--line-control);
  border-radius: var(--r-pill);
  background: var(--print);
  color: var(--ink);
  font: 400 var(--fs-body)/1.4 var(--body);
  box-shadow: none;
  cursor: pointer;
  transition: background var(--t-hover), border-color var(--t-hover);
}
/* `:not([aria-pressed="true"])` is not decoration. `contrast_audit.js` forces
   every :hover rule onto everything its selector matches, so a bare
   `.chip:hover` would paint the pale wall under a PRESSED chip whose count is
   white -- 1.3:1 in the report, and a pair a reader then has to disprove by
   hand. Saying which chips this hover is for removes the question. */
.chip:not([aria-pressed="true"]):hover {
  background: var(--wall); border-color: var(--muted); color: var(--ink);
}
/* Colour is deliberately NOT transitioned: mid-transition the label sits
   between dark and white, which over the green fill reads as unreadable grey
   for the duration. The fill fades; the text switches. */
.chip[aria-pressed="true"],
.chip[aria-pressed="true"]:hover {
  background: var(--control); border-color: var(--control);
  color: var(--on-control);
}
.chip[aria-pressed="true"] .c,
.chip[aria-pressed="true"] .sz { color: var(--on-control); }
/* THE COUNT'S `opacity: .85` IS GONE, for the reason stated on `.sz` two rules
   below and measured again here: dimmed to 85 percent it reads 4.79:1 on the
   light fill and 4.08:1 on the dark one, which is under AA on the chips a
   client reads while deciding. There is no headroom to dim it -- the label
   itself is 4.96:1 in dark -- so the count is simply the label's colour, and
   the spacing and the figures face are what set it apart. */
.chip .c { color: var(--muted); margin-left: 5px;
           font-family: var(--figures); font-variant-numeric: tabular-nums; }
/* A REAL COLOUR, NOT AN OPACITY. `.chip .sz` and `.chip .c` have the same
   specificity, so an opacity here once beat the one on .c and the size read at
   3.35:1 on exactly the chips a client reads while deciding. */
.chip .sz {
  color: var(--muted); opacity: 1; margin-left: 7px; padding-left: 7px;
  border-left: 1px solid var(--line);
  font-family: var(--figures); font-variant-numeric: tabular-nums;
}
/* Reset is not a selectable option, so it must never take the selected fill. */
.chip.clear, .chip.clear[aria-pressed="true"] {
  border-style: dashed; border-color: var(--line-control);
  color: var(--muted); background: transparent;
}
.chip.clear:hover, .chip.clear[aria-pressed="true"]:hover {
  color: var(--ink); border-color: var(--muted); background: var(--wall);
}

/* The tally: the same shape as a chip and deliberately NOT a button. A number
   that looks clickable invites a click that can do nothing. */
.tally {
  display: inline-block;
  padding: 6px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--print);
  color: var(--ink);
  font: 400 var(--fs-body)/1.4 var(--body);
}
.tally .c { color: var(--muted); margin-left: 5px;
            font-family: var(--figures); font-variant-numeric: tabular-nums; }

/* ===========================================================================
   TILES
   The number in Fraunces, opsz 72, tabular. keep is green, go is slate,
   neutral is ink.
   ======================================================================== */
.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--sp-3);
}
.tile {
  background: var(--print);
  border-radius: var(--r-print);
  box-shadow: var(--sh-print);
  padding: var(--sp-4);
}
.tile .n {
  font: 400 28px/1.1 var(--display);
  font-variation-settings: "opsz" 72, "SOFT" 50;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
  color: var(--ink);
}
.tile .l { font-size: var(--fs-body); color: var(--muted); margin-top: 5px; }
.tile.keep .n { color: var(--green); }
.tile.go   .n { color: var(--slate); }

/* The console's header strip is the same tile with a different inside. */
.tile > span { display: block; font-size: var(--fs-nano); color: var(--muted); }
.tile b {
  display: block; margin-top: 3px;
  font: 400 var(--fs-h3)/1.1 var(--display);
  font-variation-settings: "opsz" 30, "SOFT" 50;
  font-variant-numeric: tabular-nums;
}
.tile small { display: block; color: var(--muted); font-size: var(--fs-tiny);
              margin-top: 2px; font-family: var(--figures); }
.tile.warn b { color: var(--clay-ink); }
.tile.bad  b { color: var(--stop-ink); }

/* ===========================================================================
   SPLIT BAR AND PINNED SUMMARY
   Green is what stays. SLATE is what moves -- see the header note; this used
   to be an orange that sat beside clay and blunted it.
   ======================================================================== */
.split {
  display: flex;
  height: 10px;
  border-radius: var(--r-pill);
  overflow: hidden;
  background: var(--wall);
  margin-top: var(--sp-2);
}
.split i { display: block; height: 100%; }
.split i + i { margin-left: 2px; }
.dot {
  display: inline-block; width: 10px; height: 10px; border-radius: 50%;
  margin-right: 5px; vertical-align: -1px;
}
.dot.stay { background: var(--control); }
.dot.move { background: var(--slate); }

/* Shown only while the real summary is off screen, so it is never a second
   copy competing with the first. Fixed rather than sticky because the summary
   and the controls are in different containers. */
.pinned {
  position: fixed; top: 0; left: 0; right: 0; z-index: 30;
  background: var(--print);
  border-bottom: 1px solid var(--line-print);
  box-shadow: var(--sh-raised);
}
.pinwrap {
  max-width: 760px; margin: 0 auto; padding: 9px 22px;
  display: flex; align-items: center; gap: var(--sp-3);
  font-size: var(--fs-body);
}
.pin-go { font-weight: 700; white-space: nowrap; font-family: var(--figures); }
.pin-stay { color: var(--muted); white-space: nowrap;
            font-family: var(--figures); }
.pin-bar {
  flex: 1; display: flex; height: 10px; border-radius: var(--r-pill);
  overflow: hidden; background: var(--wall); min-width: 60px;
}
.pin-bar i { display: block; height: 100%; }
@media (prefers-reduced-motion: no-preference) {
  .pinned { transition: opacity var(--t-hover); }
}

/* ===========================================================================
   STEPPER
   Six bars, equal, 4px tall. Done and current are green; not yet are --line.
   THE SIXTH BAR IS GREEN LIKE THE OTHERS: the clay belongs on the sixth
   step's screen, not on the map of the journey.
   ======================================================================== */
.steps {
  display: flex; gap: var(--sp-2); margin: 0 0 var(--sp-6); padding: 0;
  list-style: none; flex-wrap: wrap;
}
.steps li { flex: 1 1 0; min-width: 56px; display: flex; flex-direction: column;
            gap: 6px; }
.steps i { height: 4px; border-radius: var(--r-pill); background: var(--line);
           transition: background .3s; }
.steps span { font-size: var(--fs-nano); line-height: 1.25; color: var(--muted); }
.steps li.on i, .steps li.now i { background: var(--control); }
.steps li.now span { color: var(--ink); font-weight: 700; }
.steps-now { display: none; }
@media (max-width: 560px) {
  .steps { gap: 6px; }
  /* SIX BARS, ONE ROW. `min-width:56px` needs 376px for six bars and their
     gaps, which a 390px phone does not have once the page's padding is off --
     so the sixth wrapped and the thing read as five steps plus something else.
     `flex:1` already shares the row evenly; the minimum was only holding the
     labels apart, and below 400px there are no labels. */
  .steps li { min-width: 0; }
}
@media (max-width: 400px) {
  .steps { gap: var(--sp-1); margin-bottom: 10px; }
  .steps span { display: none; }
  .steps-now { display: block; font-size: var(--fs-body); color: var(--muted);
               margin: 0 0 var(--sp-5); }
  .steps-now b { color: var(--ink); }
}

/* ===========================================================================
   PROGRESS BAR
   ======================================================================== */
.bar {
  height: 10px; background: var(--wall); border: 1px solid var(--line);
  border-radius: var(--r-pill); overflow: hidden; margin: var(--sp-4) 0 var(--sp-2);
  position: relative;
}
.bar i { display: block; height: 100%; background: var(--control); width: 0;
         transition: width var(--t-bar); }
/* Indeterminate: used while the library is being counted, when the total is
   genuinely unknown. A sweeping band says "moving" without inventing a number.
   Animating transform keeps it off the layout path, which matters because this
   runs for minutes on a client's laptop. */
.bar.waiting i {
  width: 38%; border-radius: var(--r-pill);
  animation: sweep 1.35s cubic-bezier(.45, 0, .55, 1) infinite;
}
.bar.waiting::after {
  content: ""; position: absolute; inset: 0; width: 22%;
  background: var(--control); opacity: .35; border-radius: var(--r-pill);
  animation: sweep 1.35s cubic-bezier(.45, 0, .55, 1) .45s infinite;
}
@keyframes sweep {
  0%   { transform: translateX(-110%); }
  100% { transform: translateX(300%); }
}
@media (prefers-reduced-motion: reduce) {
  .bar.waiting i { animation: none; width: 100%; opacity: .35; }
  .bar.waiting::after { display: none; }
  .bar i { transition: none; }
}
.barlabel { font-size: var(--fs-label); color: var(--muted);
            font-family: var(--figures); font-variant-numeric: tabular-nums; }

/* ===========================================================================
   PILLS (the console's job states)
   No clay pill exists. A job in progress is SLATE: it is in motion, not a
   decision anyone is being asked to take.
   ======================================================================== */
.pill {
  display: inline-block; padding: 2px 10px; border-radius: var(--r-pill);
  font: 600 var(--fs-tiny)/1.7 var(--body);
}
.pill.done { background: var(--green-soft); color: var(--green-dk); }
.pill.run  { background: var(--slate-soft); color: var(--slate-ink); }
.pill.wait { background: var(--wall);       color: var(--muted); }
.pill.bad  { background: var(--stop-soft);  color: var(--stop-ink); }

/* The small "who did this" badge on a log line. Sentence case, like every
   other label on these surfaces. */
.who {
  display: inline-block; font-size: var(--fs-nano);
  padding: 1px 8px; border-radius: var(--r-pill);
  background: var(--wall); color: var(--muted); vertical-align: 1px;
}

/* ===========================================================================
   LOG AND RECORD
   What a client reads and what an operator reads are the same component, and
   they stay so. Timestamps and file names in --figures. A held photograph's
   line is the ONLY clay in a log, because a held photograph is the removal
   rule doing its job.
   ======================================================================== */
.log { margin: 0; padding: 0; list-style: none; }
.log li {
  display: grid; grid-template-columns: 150px 1fr; gap: var(--sp-1) var(--sp-4);
  padding: 9px 2px; border-bottom: 1px solid var(--line-print);
  font-size: var(--fs-body); align-items: baseline;
}
.log li:last-child { border-bottom: none; }
.log .when {
  color: var(--muted); white-space: nowrap;
  font-family: var(--figures); font-variant-numeric: tabular-nums;
}
.log .what strong { display: block; font-weight: 700; margin-bottom: 2px; }
.log .kind { font-family: var(--mono); font-weight: 600; }
.log .detail { color: var(--muted); word-break: break-word; }
.log li.bad .what strong, .log li.bad .kind { color: var(--stop-ink); }
.log li.held .what strong, .log li.held .kind { color: var(--clay-ink); }
@media (max-width: 620px) { .log li { grid-template-columns: 1fr; } }

/* An operator reads a job by meaning, not by decoding one uninterrupted event
   stream. These groups change presentation only: every entry remains verbatim
   and newest-first inside its one section. */
.job-history{margin-top:var(--sp-4);padding-top:var(--sp-3);
  border-top:1px solid var(--line-print)}
.job-history > h3{margin:0;font-size:var(--fs-label)}
.job-history > .nofiles{margin:5px 0 var(--sp-3)}
.history-groups{display:grid;gap:10px}
.history-group{overflow:hidden;border:1px solid var(--line-print);
  border-radius:var(--r-print);background:var(--print)}
.history-group > summary{display:flex;align-items:center;gap:12px;min-height:44px;
  margin:0;padding:8px 13px;list-style:none;background:var(--wall-half);
  color:var(--ink);font-size:var(--fs-body)}
.history-group > summary::-webkit-details-marker{display:none}
.history-group > summary::before{content:"\25B8";flex:0 0 auto;color:var(--muted);
  font-family:var(--figures);font-weight:400}
.history-group[open] > summary{margin:0;border-bottom:1px solid var(--line-print)}
.history-group[open] > summary::before{content:"\25BE"}
.history-count{margin-left:auto;color:var(--muted);font:400 var(--fs-tiny)/1.4 var(--figures);
  white-space:nowrap}
.history-group > .log{padding:0 13px}
.history-empty{margin:0;color:var(--muted);font-size:var(--fs-body)}

/* The raw entry, beside the readable label rather than instead of it. A
   history we paraphrased before showing is not evidence. */
.raw {
  display: block; color: var(--muted); font-family: var(--mono);
  font-size: var(--fs-tiny); word-break: break-word; margin-top: 2px;
}
strong.bad { color: var(--stop-ink); }

/* A 64-CHARACTER FINGERPRINT HAS NO SPACES IN IT. The archive's sha256 sits in
   a `code` on the download screen, and with nothing to let it break, one
   unbreakable word made the whole page 604px wide inside a 390px phone. Both
   properties on purpose: `anywhere` is what breaks a run of hex, `break-word`
   is the older spelling older Safari understands. */
code {
  font-family: var(--mono); font-size: var(--fs-label);
  background: var(--green-soft); color: var(--green-dk);
  padding: 1px 6px; border-radius: var(--r-print);
  overflow-wrap: anywhere; word-break: break-word; max-width: 100%;
}

details summary {
  cursor: pointer; color: var(--green-dk); font-weight: 700;
  margin-top: 6px; font-size: var(--fs-body);
}
details[open] summary { margin-bottom: 2px; }
details.hist {
  margin-top: 18px; border-top: 1px solid var(--line); padding-top: var(--sp-3);
}
details.hist summary {
  font-size: var(--fs-label); font-weight: 700; list-style: none;
}
details.hist summary::-webkit-details-marker { display: none; }
details.hist summary::before {
  content: "\25B8 "; display: inline-block; color: var(--muted);
  font-family: var(--figures);
}
details.hist[open] summary::before { content: "\25BE "; }
details.hist .cap { color: var(--muted); font-size: var(--fs-body);
                    margin: 10px 0 var(--sp-1); }

/* ===========================================================================
   TABLES (.dt-*)
   ONE COPY, here, and set in --figures: a table is mostly quantities and
   Atkinson's slashed zero belongs nowhere near them. Head row on the wall with
   sentence-case --muted labels. The datatable JavaScript is unchanged.
   ======================================================================== */
/* SCROLLS SIDEWAYS RATHER THAN HIDING A COLUMN. `overflow:hidden` was here to
   keep the corners inside the rounded border, and on a phone it quietly cut
   the last column off: 423px of table in a 340px box with the "By" column
   simply not there, and nothing on screen to say so. This is the client's
   evidence record, so a column it cannot show is the one thing it must not do
   silently. Horizontal scroll only; vertical stays hidden so corners clip. */
.dt-wrap {
  background: var(--print);
  border-radius: var(--r-print);
  box-shadow: var(--sh-print);
  overflow-x: auto;
  overflow-y: hidden;
  margin-top: 10px;
}
.dt {
  width: 100%; border-collapse: collapse; background: var(--print);
  table-layout: fixed; font: 400 var(--fs-body)/1.5 var(--figures);
}
.dt th, .dt td {
  padding: 10px 13px; border-bottom: 1px solid var(--line-print);
  vertical-align: top; text-align: left; overflow-wrap: anywhere;
}
.dt .dt-r { text-align: right; }
.dt-h {
  background: var(--wall); font: 700 var(--fs-tiny)/1.5 var(--body);
  color: var(--muted); border-bottom: 1px solid var(--line);
  user-select: none; white-space: nowrap;
}
.dt-h[data-sort] { cursor: pointer; }
.dt-h[data-sort]:hover { color: var(--ink); background: var(--wall-half); }
.dt-h[data-sort]:focus-visible { outline: var(--focus); outline-offset: -3px; }
.dt-hl { display: inline-flex; align-items: center; gap: 6px; }
.dt-caret { font-style: normal; font-size: 9px; color: transparent;
            line-height: 1; font-family: var(--figures); }
.dt-caret.on { color: var(--green); }
.dt-frow th { background: var(--print); padding: 7px 9px;
              border-bottom: 1px solid var(--line); }
/* `background-color`, NOT `background`. The filter row's select carries
   `class="select sm"` and draws its chevron as a background IMAGE, and the
   shorthand here reset that image to none along with the repeat, the size and
   the position -- so every filter select in both consoles has rendered as a
   text box with no chevron since the row was written, and there is no way to
   tell one from the field beside it. The dark appearance is what surfaced it:
   restating the image alone put a chevron back on a control whose
   `background-repeat` had been reset to `repeat`, and it tiled across the
   whole box. Setting only the colour leaves `.select`'s four longhands alone
   and fixes both appearances at once. */
.dt-frow input, .dt-frow select {
  width: 100%; padding: 6px 8px; border: 1px solid var(--line-control);
  border-radius: 8px; background-color: var(--print); color: var(--ink);
  font: 400 var(--fs-body)/1.4 var(--figures);
}
.dt-frow input:focus, .dt-frow select:focus {
  /* Table filters use the same bounded-field cue as every other form. */
  outline: var(--focus-field); outline-offset: 1px;
  border-color: var(--line-control);
}
.dt tbody tr:last-child td { border-bottom: none; }
.dt tbody tr:hover { background: var(--wall-half); }
.dt-empty { color: var(--muted); padding: 18px 13px; font-family: var(--body); }
.dt-foot {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 10px; padding: var(--sp-2) 13px;
  background: var(--wall); border-top: 1px solid var(--line);
}
@media (max-width: 720px) {
  .admin-console #records .dt-foot {
    background: var(--print); border: 0; border-radius: var(--r-print);
    margin-top: 10px;
  }
}
.dt-count { font-size: var(--fs-tiny); color: var(--muted); }
/* Every control in the footer is the SAME height and on one centre line. The
   rows box had no height at all, so it took whatever its font produced beside
   the buttons, and the whitespace between the label and the nav became an
   anonymous flex item whose line-height strut pushed the group off centre.
   Heights are stated rather than inherited and line-height is neutralised. */
.dt-pgs { display: flex; align-items: center; gap: var(--sp-3); line-height: 1; }
/* margin:0 is the actual fix and it was not obvious. `.dt-size` is a <label>,
   and the global label rule gives every label a bottom margin. Flex centres
   the MARGIN box, so the control sat 3px above the buttons beside it while
   every height in the footer measured identical. */
.dt-size {
  display: flex; align-items: center; gap: 6px; margin: 0; line-height: 1;
  font: 400 var(--fs-tiny)/1 var(--body); color: var(--muted);
}
.dt-size input {
  width: 52px; height: 32px; padding: 0 6px; line-height: 1; text-align: center;
  border: 1px solid var(--line-control); border-radius: 8px; background: var(--print);
  color: var(--ink); font: 400 var(--fs-body)/1 var(--figures);
}
/* gap 12px, not 3px: the 44px hit areas below are inset -6px either side, so
   at anything tighter two page buttons would overlap each other's target. */
.dt-nav { display: flex; align-items: center; gap: var(--sp-3); }
.dt-at {
  display: inline-flex; align-items: center; height: 32px; padding: 0 7px;
  font-size: var(--fs-tiny); color: var(--muted);
  font-family: var(--figures); font-variant-numeric: tabular-nums;
}
.dt-pg {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 32px; min-height: 32px; height: 32px; padding: 0 6px;
  font: 400 var(--fs-label)/1 var(--figures);
  color: var(--ink); background: var(--print);
  border: 1px solid var(--line-control); border-radius: 8px;
  box-shadow: none; cursor: pointer;
}
.dt-pg:hover { background: var(--wall); border-color: var(--green);
               color: var(--ink); }
.dt-pg:disabled, .dt-pg:disabled:hover {
  color: var(--muted); opacity: .45; border-color: var(--line-print);
  background: var(--print); cursor: default;
}
.dt-pg:focus-visible, .dt-caret-btn:focus-visible {
  outline: var(--focus); outline-offset: 1px;
}
/* A 44px square centred on the control, rather than the control's own box
   grown by a margin: a page button is 32px wide and a caret button is not,
   and a rule stated as an inset gives each of them a different target. The
   nav's 12px gap is what keeps two of these from overlapping -- 32 + 12 is
   44, so adjacent targets abut exactly and neither steals the other's edge. */
.dt-pg::after, .dt-caret-btn::after {
  left: 50%; right: auto; width: 44px; height: 44px;
  transform: translate(-50%, -50%);
}

/* On a phone the timestamp's `nowrap` costs more than it is worth. Measured on
   an iPhone 2026-09-03 and reproduced at 390px: the trail table came to 386px
   inside a 342px card, so `.dt-wrap` scrolled 44px and cut the "By" pill
   through the middle of a word. A table 13% too wide does not read as
   scrollable, it reads as broken, and nobody discovers a scroll they cannot
   see the far edge of.

   `.nw` holds "Sep 2, 2026 at 09:11 PM" on one line and is worth 54px here,
   which is the whole overflow and more. Wrapped it reads "Sep 2, 2026 at"
   over "09:11 PM" -- still one date, still sorted by the same column, and the
   row is already two lines tall because the detail beneath it wraps anyway.
   Only the width is relaxed: the cell keeps its own column, so the table is
   still a table to a screen reader.

   Deliberately NOT a collapse-to-cards breakpoint. That is the right answer
   for the five-column jobs table, which is 140px over at this width and needs
   its own card; this one is 44px over and fits once the date may wrap. */
@media (max-width: 430px) {
  .dt .nw { white-space: normal; }
  /* Keep the actor filter legible. The earlier 18% share left only 62px in
     the client history sheet, which clipped even its default "All" option. */
  #hist-list col[data-col="at"] { width: 27% !important; }
  #hist-list col[data-col="what"] { width: 49% !important; }
  #hist-list col[data-col="actor"] { width: 24% !important; }
}

/* Four job attributes plus disclosure no longer compare cleanly in a phone's
   342px content area. Present the same cells as compact records, keeping the
   disclosure and expanded history in place and preserving DOM/table semantics
   for assistive technology. */
@media (max-width: 560px) {
  #jobs-list { background: transparent; box-shadow: none; overflow: visible; }
  #jobs-list .dt { display: block; min-width: 0; background: transparent; }
  #jobs-list thead { display: none; }
  #jobs-list tbody { display: grid; gap: var(--sp-3); }
  #jobs-list tr.dt-row {
    display: grid; grid-template-columns: 34px minmax(0, 1fr) auto;
    gap: var(--sp-2) var(--sp-3); padding: var(--sp-3);
    background: var(--print); border-radius: var(--r-print);
    box-shadow: var(--sh-print);
  }
  #jobs-list tr.dt-row td { display: block; min-width: 0; padding: 0; border: 0; }
  #jobs-list tr.dt-row td.dt-x {
    grid-column: 1; grid-row: 1 / span 3; width: auto;
  }
  #jobs-list tr.dt-row td[data-col="created_at"],
  #jobs-list tr.dt-row td[data-col="outcome"] { grid-column: 2 / -1; }
  #jobs-list tr.dt-row td[data-col="copy"] { grid-column: 2; }
  #jobs-list tr.dt-row td[data-col="entries"] { grid-column: 3; }
  #jobs-list tr.dt-exp { display: block; margin-top: calc(-1 * var(--sp-3)); }
  #jobs-list tr.dt-exp > td {
    display: block; padding: var(--sp-3); background: var(--print);
    border-radius: 0 0 var(--r-print) var(--r-print);
  }
  #jobs-list .dt-foot {
    background: var(--print); border: 0; border-radius: var(--r-print);
    margin-top: var(--sp-2);
  }
}

/* --- expandable rows. A job's history lives INSIDE its row rather than in a
   list of its own: a job and what happened to it are looked at together or not
   at all. */
.dt-col-expand { width: 34px; }
.dt-h.dt-x, .dt td.dt-x { width: 34px; padding-right: 0; text-align: center; }
.dt-caret-btn {
  position: relative;
  min-height: 32px; min-width: 32px; padding: 2px 6px;
  background: none; border: 0; border-radius: 6px; box-shadow: none;
  color: var(--muted); font: 400 var(--fs-body)/1 var(--figures); cursor: pointer;
}
.dt-caret-btn:hover { color: var(--green-dk); background: var(--wall); }
.dt tbody tr.dt-row { cursor: pointer; }
.dt tbody tr.dt-row.on { background: var(--wall-half); }
.dt tbody tr.dt-row.on td { border-bottom-color: transparent; }
.dt tbody tr.dt-exp > td { background: var(--wall-half); padding: 2px 14px 14px;
                           font-family: var(--body); }
/* Client message history and the preview-first operator dialog. */
.mailbox{margin:14px 0 0;padding-top:12px;border-top:1px solid var(--line-print)}
.mailbox-head{display:flex;align-items:center;justify-content:space-between;gap:14px}
.mailbox h3{font-size:var(--fs-label);margin:0 0 4px}
.mailbox .none{font-size:var(--fs-body);color:var(--muted);margin:0}
.mailbox-head button{padding:7px 12px;font-size:var(--fs-label)}
.email-modal{width:min(760px,100%)}

/* Copy cancellation is a quiet way out, with one deliberate confirmation.
   The solid print surface matters: this dialog sits over a visually active
   progress ledger, so translucency would merge two unrelated reading layers. */
.copy-cancel{margin-top:18px;padding-top:16px;border-top:1px solid var(--line-print)}
.copy-cancel .ghost{background:transparent;box-shadow:none}
.cancel-dialog{width:min(560px,calc(100% - 32px));max-height:calc(100dvh - 32px);
  padding:0;border:1px solid var(--line-control);border-top:6px solid var(--stop);
  border-radius:var(--r-print);background:var(--print);color:var(--ink);
  box-shadow:0 24px 80px var(--scrim);overflow:auto;
  overscroll-behavior:contain}
.cancel-dialog::backdrop{background:var(--scrim-deep)}
.cancel-dialog-inner{padding:30px}
.cancel-dialog h2{margin:0 0 12px;font:500 var(--fs-h3)/1.12 var(--display)}
.cancel-dialog p{margin:0;color:var(--ink);line-height:1.55}
.cancel-safety{display:grid;gap:4px;margin-top:20px;padding:16px 18px;
  border:1px solid var(--green-line);border-radius:var(--r-print);
  background:var(--green-soft);color:var(--green-dk);line-height:1.45}
.cancel-safety strong{font-size:var(--fs-read)}
.cancel-safety span{color:var(--green-dk)}
.cancel-dialog .row{margin-top:26px;justify-content:flex-end;gap:12px}
.cancel-dialog .row button{min-width:154px}
@media (max-width:560px){
  .cancel-dialog{width:calc(100% - 24px);max-height:calc(100dvh - 24px)}
  .cancel-dialog-inner{padding:22px 20px 20px}
  .cancel-dialog .row{display:grid;grid-template-columns:1fr}
  .cancel-dialog .row button{width:100%}
}
.email-controls{display:grid;gap:16px}
.email-primary-fields{display:grid;grid-template-columns:minmax(0,1fr) minmax(220px,.65fr);
  gap:16px;align-items:end}
.email-controls textarea{min-height:104px;resize:vertical}
.email-fixed{padding:10px 12px;background:var(--wall-half);
  border:1px solid var(--line-print);border-radius:var(--r-print);
  font-family:var(--figures);overflow-wrap:anywhere}
.email-check{display:flex;align-items:flex-start;gap:9px;min-height:44px;
  font-size:var(--fs-body);cursor:pointer}
.email-check input{margin-top:2px}
.email-check span{display:block;color:var(--muted);font-size:var(--fs-tiny);margin-top:3px}
.email-preview{margin-top:22px;padding-top:18px;border-top:1px solid var(--line-print)}
.email-preview h3{margin:0 0 10px;font:700 var(--fs-label)/1.4 var(--body);
  color:var(--muted)}
.email-paper{min-width:0;background:var(--print);border:1px solid var(--line);
  border-radius:var(--r-print);box-shadow:var(--sh-print);overflow:hidden}
.email-paper-head{padding:16px 18px;border-bottom:1px solid var(--line-print);
  background:var(--wall-half)}
.email-paper-head span{display:block;color:var(--muted);font-size:var(--fs-tiny)}
.email-paper-head strong{display:block;margin-top:3px;font-size:var(--fs-body)}
.email-paper pre{max-height:min(34vh,320px);overflow:auto;margin:0;padding:20px 22px;
  white-space:pre-wrap;overflow-wrap:anywhere;
  font:400 var(--fs-body)/1.55 var(--body);color:var(--ink)}
.email-evidence{margin:0;padding:12px 14px;border-top:1px solid var(--line-print);
  color:var(--muted);font-size:var(--fs-tiny)}
.email-evidence b{color:var(--ink)}
.email-status{min-height:1.5em;margin:0;color:var(--muted);font-size:var(--fs-body)}
@media(max-width:720px){
  .email-primary-fields{grid-template-columns:1fr}
  .email-paper pre{max-height:32vh;padding:16px}
  .history-group > summary{padding-inline:11px}
  .history-group > .log{padding-inline:11px}
}

/* ===========================================================================
   TOPBAR
   The wordmark and, on the public site, the section links. The client page
   uses it with the wordmark alone, linked to the public site, so a client
   who arrived on a private link can find the page that explains the service.
   Sticky only where `.sticky` is added: on the site, whose page is long. The
   ground is the wall at 85 percent over a blur, mixed from the token rather
   than written as a literal, so a palette change carries here too.
   ======================================================================== */
.topbar { margin-bottom: var(--sp-6); }
.topbar-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-4); min-height: 68px;
}
.topbar .brand { margin-bottom: 0; }
a.brand { text-decoration: none; color: var(--ink); }
.topbar nav { display: flex; gap: var(--sp-1); overflow-x: auto; }
.topbar nav a {
  display: inline-flex; align-items: center; min-height: 44px;
  padding: 0 var(--sp-3); border-radius: var(--r-control);
  font: 400 var(--fs-label)/1 var(--body); color: var(--muted);
  text-decoration: none; white-space: nowrap;
}
.topbar nav a:hover { color: var(--ink); background: var(--print); }
.topbar nav a.strong { color: var(--green-dk); font-weight: 700; }
.topbar.sticky {
  position: sticky; top: 0; z-index: 30;
  background: var(--wall);
  background: color-mix(in srgb, var(--wall) 85%, transparent);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid transparent; transition: border-color .15s;
}
.topbar.sticky.scrolled { border-bottom-color: var(--line); }
/* The site's wordmark sits in a sticky bar with no room for the app's 32px
   bottom margin; the bar carries the spacing instead. */
.topbar.sticky { margin-bottom: 0; }

/* A short label above a heading, on the public site. Sentence case in the
   field-label style; the all-caps tracked label is the thing section 3 of the
   document retired. */
.kicker { font: 700 var(--fs-label)/1.3 var(--body); color: var(--muted);
          margin: 0 0 var(--sp-3); }

/* A print that IS the client's decision: the sixth step on the public site.
   Same ground and outline as .note.gate, because it is the same meaning, and
   clay means nothing else. */
.card.gate { background: var(--clay-soft); outline: 2px solid var(--clay);
             outline-offset: -2px; }

/* THE LEGAL FOOTER, shared. The 44px-both-ways rule on the links is the whole
   reason they are flex items and not three words in a sentence: "Terms" at
   13px is 35px wide, so the min-width does real work on the shorter link. */
footer.legal {
  margin-top: var(--sp-7); padding-top: var(--sp-5);
  border-top: 1px solid var(--line);
  font-size: var(--fs-body); color: var(--muted); line-height: 1.6;
}
footer.legal p { margin: 0; max-width: 60ch; }
.legal-links { display: flex; flex-wrap: wrap; align-items: center;
               column-gap: var(--sp-5); margin-top: var(--sp-1); }
.legal-links a { display: inline-flex; align-items: center; justify-content: center;
                 min-height: 44px; min-width: 44px; }

/* ===========================================================================
   THE TWO THINGS THE DARK PALETTE CANNOT REACH ON ITS OWN
   A `data:` URI is a string, not a style, so the two chevrons carry their
   stroke as literal hex and no token substitution happens inside them. They
   are redrawn here, once, in --muted's dark value.
   THE CHECK INSIDE A CHECKED BOX USED TO BE HERE TOO and is not any more: the
   box fills with --control rather than --green, and --control is the darker
   element in both appearances, so one near-white stroke serves both. That is
   the whole argument for splitting the token, in the one place where it saves
   a rule rather than costing one.
   Gated the same way as the token block: the marketing pages opt out with
   `data-appearance="light"` and never reach any of this.
   ======================================================================== */
@media (prefers-color-scheme: dark) {
  :root:not([data-appearance="light"]) .select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%23b7c6be' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  }
  :root:not([data-appearance="light"]) input[list]::-webkit-calendar-picker-indicator {
    background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%23b7c6be' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 12px 8px no-repeat;
  }
}

/* And the same two again for a client who chose Dark on a device the system
   says is light, for the reason set out at the forced token block: nothing can
   be shared across a media boundary, so it is written twice and watched. */
:root[data-appearance="dark"] .select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%23b7c6be' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
:root[data-appearance="dark"] input[list]::-webkit-calendar-picker-indicator {
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%23b7c6be' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 12px 8px no-repeat;
}
