/*
  Editorial Ink (Card Kit) tokens — design-mine skill.

  CANONICAL: this file leads. The design system's source of truth is the
  pb-stack design-mine skill (07-pocketbase-stack); apps copy from here.
  Historical provenance, do not re-vendor from either:
    - 21-prompt-manager/pocketbase/pb_public/assets/css/wired.css
      (Wired square-flat generation, md5 9D1E37A9EBE46DA5236D0DB1A303ED38)
    - 33-barbados/Pocketbase/pb_public/assets/css/{wired,barbados}.css
      (where the card kit was incubated, reconciled 2026-08-29)

  Short wired names (--ink, --canvas, --surface) are canonical. The --*
  aliases below exist for migration only and will be removed once no file
  references them — do not write new code against them.

  Dual-mode. Light is the design mode; the dark block below re-inks every token
  for prefers-color-scheme: dark (adopted 2026-08-29 per the frontend-taste
  doctrine's dark-mode protocol). There is NO theme toggle: the OS decides.
  Every dark pair below is WCAG-AA-verified against its ground. Components must
  never hardcode a color — they read tokens, so they get both modes for free.

  Full drop-in layer: these tokens + wired-base.css (@font-face, resets,
  primitives) + card-kit.css (cards, pills, tiers) — concatenated in that
  order into a project's assets/css/wired.css.
*/

:root {
    /* ---- Ink on warm paper -------------------------------------------- */
    --ink: #1c1a17;             /* primary text AND the one filled CTA */
    --ink-soft: #3f3a33;        /* lead copy, hover on the filled button */
    --body: #3f3a33;
    --muted: #6b6459;
    --muted-soft: #7d7669;      /* placeholders, timestamps, fine print, chevrons.
                                   AA (4.5) on --surface white; on the paper or an
                                   inset card use --muted instead (darkened
                                   2026-08-30, taste-doctrine contrast audit) */
    --hairline: #ddd7ca;        /* 1px dividers on the paper */
    --border: #cfcabf;          /* input + quiet-button borders */
    --canvas: #f5f2ec;          /* warm paper — the page ground */
    --canvas-soft: #ede9e0;     /* hover wash, recessed bands */
    --surface: #ffffff;         /* inputs + the card holding the next action */
    --link: #057dbc;            /* the only accent — never two per screen */
    --on-ink: #ffffff;          /* text on a filled --ink surface */
    --canvas-rgb: 245, 242, 236; /* --canvas as raw channels, for fade gradients */
    --scrim: rgba(28, 26, 23, 0.4);   /* sheet/dialog backdrop */
    --btn-waiting: #e5e0d4;     /* the quiet-until-ready CTA fill */

    /* ---- The card kit --------------------------------------------------
       Cards are --surface on --canvas: the same palette, a different way of
       grouping — not a second theme. Three line weights, strictly ordered:
       --hairline (a rule on the paper) is darker than --card-edge (the line
       AROUND a pressable tile on white), which is darker than --card-rule
       (the line BETWEEN two rows inside one card). A rule only has to
       separate; an edge has to say "this is an object" against the white it
       sits on; a rule drawn on white next to a rule drawn on paper reads as
       two weights unless the white one gives some back. */
    --card-radius: 14px;        /* one row of a list, as its own card */
    --card-radius-lg: 16px;     /* a section card — 14px on a 400px-tall panel reads as square */
    --card-shadow: 0 1px 2px rgba(28, 26, 23, 0.07);
    --card-rule: #efeade;
    --card-edge: #e7e1d6;

    /* ---- Identity tints ------------------------------------------------
       One desaturated ink per taxonomy key (a trade, a category, a section),
       assigned once in project config and NEVER re-mapped after users learn
       them. Tints are classification, not accents and not status: a tint
       colors an 11px kicker line or the block behind a glyph, never a button.
       Rename per project; keep them desaturated - and keep them AA: every tint
       must read at 4.5:1 as 11px text on --surface AND --canvas (rust/ochre/
       olive/stone were darkened 2026-08-30 after the taste-doctrine contrast
       audit failed the values inherited from the incubator app). */
    --tint-rust: #9d5f3c;
    --tint-slate: #4f6b7a;
    --tint-ochre: #84692a;
    --tint-olive: #647246;
    --tint-plum: #6f5b86;
    --tint-stone: #756e62;      /* also the fallback for an unassigned key */

    /* ---- Measure tiers -------------------------------------------------
       Phone defaults. Every page declares ONE archetype hook class on the
       first child of <main> (.home, .list-page, .form-page, .settings-page,
       .detail-page); card-kit.css sets these three per archetype via
       body:has() and widens them at 720px and 1080px. The header inner and
       main BOTH read --page-measure, so a bar can never disagree with its
       column. Floating UI anchors to the column through the same tokens. */
    --page-measure: 430px;
    --page-gutter: 12px;
    --fab-gutter: 16px;

    /* ---- Kind swatches -------------------------------------------------
       Not accents and not status: matte pigments off the same warm ground,
       used ONLY as the 8-10px square that says what kind of thing a row is
       before the word does. Saturating any of them puts a second accent on
       the page. Rename per project; keep them desaturated. */
    --kind-prompt: #b5714f;     /* clay */
    --kind-cluster: #7d8c6f;    /* sage */
    --kind-session: #7b89a3;    /* dusty slate */

    /* ---- Status --------------------------------------------------------
       Feedback only. Not accents, not decoration, never the sole carrier of
       meaning — pair every status color with a word (the "mark + word" rule).
       Lifted from the literals the shipped app had scattered outside :root. */
    --status-ok: #1f7a3d;
    --status-warn: #a8730a;
    --status-danger: #a3231f;

    /* ---- Type ----------------------------------------------------------
       Serif carries narrative, sans carries structure. Lora is SELF-HOSTED
       (see wired-base.css) — a CDN font link violates script-src 'self'. */
    --serif-display: "Lora", Georgia, "Times New Roman", serif;
    --serif-body: "Lora", Georgia, "Times New Roman", serif;
    --sans: "Helvetica Neue", Helvetica, Arial, sans-serif;
    --mono: "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;

    /* ---- Touch & motion ------------------------------------------------
       44px is a FINGER floor, not a screen floor: it holds wherever a coarse
       pointer exists and relaxes only under
       (min-width:900px) and (pointer:fine) and (not (any-pointer:coarse)).
       scripts/native-check.js stops that exception being widened later. */
    --touch-target: 44px;
    --input-min-font: 16px;     /* below this, iOS zooms on focus */
    --icon-size: 24px;
    --header-height: 3.75rem;
    --measure: 680px;           /* fallback measure for a page with NO archetype
                                   hook (prose, admin, style-ref). App screens
                                   declare a hook and get --page-measure tiers. */
    --duration-instant: 50ms;
    --duration-fast: 100ms;
    --duration-normal: 150ms;
    --duration-slow: 200ms;
    --duration-modal: 300ms;

    /* ---- Deprecated aliases (migration only) ---------------------------
       Nothing in this skill references these any more. They exist so an app
       still built against the old REF-046 names keeps rendering while it
       migrates. Delete the block once no app needs it. */
    --editorial-ink: var(--ink);
    --editorial-ink-soft: var(--ink-soft);
    --editorial-body: var(--body);
    --editorial-hairline: var(--hairline);
    --editorial-canvas: var(--canvas);
    --editorial-canvas-soft: var(--canvas-soft);
    --editorial-link: var(--link);
    --editorial-on-ink: var(--on-ink);
    --font-display: var(--serif-display);
    --font-body-serif: var(--serif-body);
    --font-ui: var(--sans);
    --font-sans: var(--sans);
    --color-danger: var(--status-danger);
    --color-success: var(--status-ok);
    --color-warning: var(--status-warn);
}

/* ---- Dark mode --------------------------------------------------------
   The same warm paper, after dark: every hue keeps its temperature, only the
   values invert. Filled elements invert cleanly because components write
   `background: var(--ink); color: var(--surface)` — in dark that renders as a
   light pill with dark text, which keeps the one filled element the loudest
   thing on the page. The OS decides the mode; there is no toggle. One theme
   per page — sections never invert locally.
   Every pair here passes WCAG AA against its ground (verified 2026-08-29). */
@media (prefers-color-scheme: dark) {
    :root {
        --ink: #ece7dd;
        --ink-soft: #cdc6b9;
        --body: #cdc6b9;
        --muted: #a29a8c;
        --muted-soft: #98907f;
        --hairline: #3a352d;
        --border: #4a443a;
        --canvas: #161411;          /* dark warm paper */
        --canvas-soft: #2c2822;     /* inset cards, chip fills — a step up from the card */
        --surface: #211e19;         /* cards; also the text on a filled element */
        --link: #4aa3d8;
        --on-ink: #161411;
        --canvas-rgb: 22, 20, 17;
        --scrim: rgba(0, 0, 0, 0.55);
        --btn-waiting: #3a352c;

        --card-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
        --card-rule: #2b2720;
        --card-edge: #3a352c;

        --tint-rust: #c98a64;
        --tint-slate: #8fb0c4;
        --tint-ochre: #c0a04e;
        --tint-olive: #9db07a;
        --tint-plum: #ab97c6;
        --tint-stone: #a89f92;

        --kind-prompt: #c98a64;
        --kind-cluster: #9db07a;
        --kind-session: #8fa8c4;

        --status-ok: #5cb87a;
        --status-warn: #d3a44a;
        --status-danger: #e08d88;
    }
}

/* Skeleton — zero CLS */
.skeleton {
    background: linear-gradient(
        90deg,
        var(--canvas-soft) 0%,
        var(--hairline) 50%,
        var(--canvas-soft) 100%
    );
    background-size: 200% 100%;
    animation: shimmer 1.5s infinite linear;
}

@keyframes shimmer {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

[data-loading="true"] {
    opacity: 0.6;
    pointer-events: none;
}

:focus-visible {
    outline: 2px solid var(--ink);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}
/*
  wired-base.css — the portable Editorial Ink base layer.

  CANONICAL: this file leads. Source of truth is the pb-stack design-mine
  skill (07-pocketbase-stack). Historical provenance, do not re-vendor:
  21-prompt-manager's wired.css (md5 9D1E37A9EBE46DA5236D0DB1A303ED38).

  What this is: @font-face + resets + the primitives every Tier 1 app needs
  before it writes a single app-specific rule — type ladder, buttons, forms,
  tabs, list rows, disclosure, action bar. The CARD KIT (card surfaces, pill
  geometry, measure tiers, sheets, panels) lives in card-kit.css, which loads
  AFTER this file and re-rounds the square primitives below — the base stays
  square so it still parses and renders standalone, and so the override order
  mirrors how the system actually shipped.

  Requires tokens.css first, or paste its :root block above this file.

  Touch floors are written as a literal `44px`, never `var(--touch-target)`:
  scripts/native-check.js parses the CSS as text and cannot resolve a custom
  property, so a tokenised floor reads to it as no floor at all. The token stays
  defined for layout maths; the enforced property stays literal.


  Fonts: copy lora-latin.woff2 and lora-latin-ext.woff2 to /assets/fonts/.
  Never link a CDN font — it violates script-src 'self'.
*/

@font-face {
  font-family: "Lora";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("/assets/fonts/lora-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Lora";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("/assets/fonts/lora-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
[hidden] { display: none !important; }

/* No scrollbar is ever drawn — not on the window, not on a sheet, not on a
   panel. Scrolling itself is untouched. A visible bar is chrome that says
   nothing the content does not already say.

   The counterpart rule lives in your layout, not here: no scroll region may
   end flush at its own boundary. Content bleeds past the fold or fades, so a
   reader can see there is more. Hiding the bar without keeping that cue is
   how a long page starts looking finished halfway down. */
* { scrollbar-width: none; -ms-overflow-style: none; }
*::-webkit-scrollbar { width: 0; height: 0; display: none; }

/* Nothing scrolls sideways. clip wins where supported because it never creates
   a scroll container, so position:sticky keeps working. */
html { -webkit-text-size-adjust: 100%; overflow-x: hidden; overflow-x: clip; }

/* contain, never none: none kills pull-to-refresh on an installed PWA. */
html, body { overscroll-behavior-y: contain; }

body {
  background: var(--canvas);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.5;
  min-height: 100vh;
  max-width: 100%;
  overflow-x: hidden;
  overflow-x: clip;
}

/* ---------------------------------------------------------------- measure */
/* The FALLBACK measure, for a page that declares no archetype hook (prose,
   admin, the style reference). App screens declare a hook on the first child
   of main and card-kit.css swaps in --page-measure with its 720/1080 tiers.
   Either way, content that outgrows the measure scrolls inside its own
   container, never page-wide. */
main {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 32px calc(24px + env(safe-area-inset-right, 0px)) 96px calc(24px + env(safe-area-inset-left, 0px));
  min-height: calc(100dvh - var(--header-height));
  display: flex;
  flex-direction: column;
}

.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--canvas);
  border-bottom: 1px solid var(--hairline);
}
.site-header__inner {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 0 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  min-height: var(--header-height);
}

/* ------------------------------------------------------------ typography */
h1 {
  font-family: var(--serif-display);
  font-weight: 400;
  font-size: 34px;
  line-height: 1.12;
  letter-spacing: -0.5px;
  margin: 14px 0 0;
}
h2 { font-family: var(--serif-display); font-weight: 400; font-size: 26px; line-height: 1.15; margin: 12px 0 8px; }
h3 { font-family: var(--serif-body); font-weight: 400; font-size: 20px; line-height: 1.25; margin: 8px 0; }
.eyebrow {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.9px;
  color: var(--muted);
}
.lead { font-family: var(--serif-body); font-size: 19px; line-height: 1.6; color: var(--ink-soft); margin: 16px 0 0; }
.muted { color: var(--muted); font-size: 15px; }
.small { font-size: 14px; }
a { color: var(--link); }
a:hover { color: var(--ink); }
code, .mono { font-family: var(--mono); font-size: 0.92em; }

@media (min-width: 768px) {
  h1 { font-size: 42px; }
}

/* -------------------------------------------------------------- buttons */
/* EXACTLY ONE filled .btn per screen. .btn-outline is secondary, .btn-quiet
   tertiary. Link color never lands on a button — that is the accent's job. */
.btn, .btn-outline, .btn-quiet {
  display: inline-block;
  font-family: var(--sans);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.3px;
  text-transform: none;
  margin: 0;
  padding: 12px 20px;
  border-radius: 0;               /* base square; card-kit.css re-rounds every .btn to a pill */
  border: 1px solid var(--ink);
  cursor: pointer;
  text-decoration: none;
  text-align: center;
  min-height: 44px;
  background: var(--ink);
  color: var(--surface);
}
.btn-outline { background: var(--surface); color: var(--ink); }
.btn-quiet { background: transparent; color: var(--ink); border-color: var(--border); font-weight: 400; }
.btn:hover, .btn:focus-visible { color: var(--surface); }
.btn-outline:hover, .btn-outline:focus-visible { color: var(--ink); }
.btn-quiet:focus-visible { color: var(--ink); border-color: var(--ink); }
.btn:disabled, .btn-outline:disabled, .btn-quiet:disabled { opacity: 0.35; cursor: default; }
.btn-block { display: block; width: 100%; }
.btn-lg { font-size: 18px; padding: 18px 20px; min-height: 58px; }
.btn-sm { font-size: 14px; padding: 10px 14px; min-height: 44px; font-weight: 400; }
.btn-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }

.btn-text {
  font-family: var(--sans);
  font-size: 15px;
  color: var(--muted);
  background: none;
  border: 0;
  padding: 12px 0;
  min-height: 44px;
  cursor: pointer;
}
.btn-text:hover, .btn-text:focus-visible { color: var(--ink); }
.btn-text.is-link { color: var(--link); }
.btn-text.is-link:hover, .btn-text.is-link:focus-visible { color: var(--ink); }

/* The dock. Same pigment as the page — no white frame around the primary
   button. Lift comes from the top rule plus one soft upward shadow. This is
   one of the kit's FOUR sanctioned shadows (card, dropdown, FAB, this dock);
   nothing else lifts. Sticky, not fixed: form screens must not park over the
   keyboard. */
.action-bar {
  position: sticky;
  bottom: 0;
  background: var(--canvas);
  margin: 40px calc(-24px - env(safe-area-inset-right, 0px)) 0 calc(-24px - env(safe-area-inset-left, 0px));
  padding: 16px calc(24px + env(safe-area-inset-right, 0px)) calc(18px + env(safe-area-inset-bottom, 0px)) calc(24px + env(safe-area-inset-left, 0px));
  border-top: 1px solid var(--border);
  box-shadow: 0 -8px 20px -12px rgba(28, 26, 23, 0.18);
  z-index: 20;
}
/* Terminal dock: foot of the viewport on short pages, flush at full scroll on
   long ones (main's 96px bottom pad is given back). */
main > .action-bar:last-child {
  margin-top: auto;
  margin-bottom: -96px;
  padding-bottom: calc(26px + env(safe-area-inset-bottom, 0px));
}
.action-hint { text-align: center; margin: 12px 0 0; font-size: 15px; color: var(--muted); }

/* ---------------------------------------------------------------- forms */
/* iOS Safari zooms the whole page when a focused control's computed font-size
   sits under 16px — and never zooms back out. 16px is the floor for anything
   a finger can put a cursor in; a rule may go bigger, never smaller.
   scripts/mobile-zoom-check.js fails the build if one sneaks back in. */
input, textarea, select { font-size: max(16px, 1em); }
label {
  display: block;
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--muted);
  margin: 28px 0 10px;
}
label.inline {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
  font-size: 15px;
  color: var(--ink);
}
input[type="text"], input[type="email"], input[type="url"], input[type="password"],
input[type="number"], input[type="search"], textarea, select {
  width: 100%;
  font-family: var(--sans);
  font-size: 19px;
  padding: 15px 16px;
  border: 1px solid var(--border);
  border-radius: 0;
  background: var(--surface);
  color: var(--ink);
  min-height: 56px;
}
/* Prose runs full height; the page or sheet scrolls. Never height-cap a text
   box — a capped box hides the reader's own words from them. */
textarea { min-height: 130px; resize: vertical; line-height: 1.5; }
input:focus, textarea:focus, select:focus { outline: 2px solid var(--ink); outline-offset: -1px; }
input[type="checkbox"] { width: 20px; height: 20px; min-height: 0; accent-color: var(--ink); }
.field-error {
  color: var(--ink);
  background: var(--canvas-soft);
  border-left: 3px solid var(--ink);
  padding: 8px 12px;
  font-size: 14px;
  margin-top: 6px;
}

/* Never use a native <datalist> — the unstyled system popup breaks the paper
   ground. Build the picker from .list-row instead. */

/* ------------------------------------------------- the next-action card */
/* On a PROSE page (no archetype hook), surface white marks what the person
   does next and nothing else on the page gets it. On a card-kit screen white
   is the grouping and every card wears it — there, the one-and-only mark of
   the action is the single filled element, not the white. */
.next-card {
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 20px;
  margin-top: 16px;
}

/* ----------------------------------------------------------------- tabs */
/* 48px tall because a thumb has to hit it, and the active one changes THREE
   ways — colour, weight, and the rule under it — so you never squint. */
.tabs {
  display: flex;
  gap: 4px;
  margin-top: 26px;
  border-bottom: 1px solid var(--hairline);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.tabs a, .tabs button {
  flex: none;
  display: inline-flex;
  align-items: center;
  min-height: 48px;
  padding: 0 14px;
  font-family: var(--sans);
  font-size: 15px;
  color: var(--muted);
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
}
.tabs a:hover, .tabs button:hover { color: var(--ink); }
.tabs a.on, .tabs button.on { color: var(--ink); font-weight: 700; border-bottom-color: var(--ink); }

/* ------------------------------------------------------------ list rows */
/* What replaced tables in app chrome. The whole row is the link; at most ONE
   button sits beside it, outside the anchor, so a tap never lands ambiguously.
   Tables are allowed inside rendered markdown only. */
.list-row { display: flex; align-items: center; gap: 12px; border-top: 1px solid var(--hairline); }
.list-row:last-child { border-bottom: 1px solid var(--hairline); }
.list-row-link { flex: 1; min-width: 0; display: block; padding: 16px 0; text-decoration: none; color: var(--ink); }
.list-row-link:hover .list-row-title { color: var(--link); }
.list-row-title { display: block; font-family: var(--serif-body); font-size: 19px; line-height: 1.3; }
.list-row-sub { display: block; font-size: 14px; color: var(--muted); margin-top: 4px; }
.list-row-body { flex: 1; min-width: 0; padding: 16px 0; }

/* ----------------------------------------------------------- disclosure */
.disclosure-toggle {
  font-family: var(--sans);
  font-size: 15px;
  color: var(--muted);
  background: none;
  border: 0;
  padding: 12px 0;
  min-height: 44px;
  cursor: pointer;
}
.disclosure-toggle::before { content: "+ "; }
.disclosure-toggle[aria-expanded="true"]::before { content: "– "; }
.disclosure-toggle:hover { color: var(--ink); }

/* --------------------------------------------------------- kind swatch */
.kind-dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  flex: none;
  border-radius: 0;
  background: var(--muted);
}

/* ------------------------------------------------------ status, in words */
/* Status is never carried by color alone. Pair the mark with a word — a red
   dot beside "Failed", not a red dot on its own. */
.status-ok { color: var(--status-ok); }
.status-warn { color: var(--status-warn); }
.status-danger { color: var(--status-danger); }

/* ------------------------------------------------------ hover quarantine */
/* Background and border hovers belong ONLY inside this block. On iOS a hover
   state applied without it sticks lit after the tap and will not clear.
   Color-only hovers above are safe and stay at base.
   scripts/native-check.js counts the rules in here and fails on a new one. */
@media (hover: hover) and (pointer: fine) {
  .btn:hover { background: var(--ink-soft); border-color: var(--ink-soft); }
  .btn-outline:hover { background: var(--canvas-soft); }
  .btn-quiet:hover { background: var(--canvas-soft); }
  .list-row-link:hover { background: var(--canvas-soft); }
}

/* Press states answer ANY coarse pointer — a tap must show it landed even on a
   touch laptop whose primary pointer is a trackpad. This is the counterpart to
   the hover quarantine above: hover is for mice, :active is for fingers.
   native-check.js requires .btn:active inside an (any-pointer: coarse) block. */
@media (any-pointer: coarse) {
  .btn:active { background: var(--ink-soft); border-color: var(--ink-soft); }
  .btn-outline:active, .btn-quiet:active { background: var(--canvas-soft); }
  .list-row-link:active { background: var(--canvas-soft); }
}

/* --------------------------------------------------------------- density */
/* 44px is a FINGER floor, not a screen floor. It holds wherever a coarse
   pointer exists — phone, tablet, touch laptop — and relaxes only where the
   machine reports no finger at all. Widening this query is the single most
   common way the kit gets broken; native-check.js pins it. */
@media (min-width: 900px) and (pointer: fine) and (not (any-pointer: coarse)) {
  .btn-sm { min-height: 36px; padding: 8px 12px; }
  .btn-text { min-height: 36px; padding: 8px 0; }
  .disclosure-toggle { min-height: 36px; padding: 8px 0; }
}

:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
/*
  card-kit.css — the Editorial Ink card layer.

  CANONICAL: this file leads. Source of truth is the pb-stack design-mine
  skill (07-pocketbase-stack). Historical provenance, do not re-vendor:
  33-barbados/Pocketbase/pb_public/assets/css/barbados.css, where this layer
  was incubated as an app overlay and generalized here 2026-08-29.

  Loads AFTER tokens.css and wired-base.css. The base separates with rules on
  the paper; this layer separates with white cards floating on it. Both are on
  the same palette — the cards are --surface on --canvas — so this is a
  different way of grouping, not a second theme. Anything with a radius takes
  its numbers from the tokens' radius ladder, so a card, a chip and a pill
  never disagree by a pixel.

  Geometry: --card-radius (14px) on a row card, --card-radius-lg (16px) on a
  section card, 12px on an input, 20px on the desktop dialog, and 999px on
  anything pill-shaped — 999 renders as height/2 at every height, which is
  the pill rule stated once instead of one literal per control.

  Shadows: exactly FOUR are sanctioned, and nothing else lifts —
    var(--card-shadow)                       a card on the paper
    0 1px 2px rgba(28, 26, 23, 0.12)         a dropdown over the page
    0 3px 10px rgba(28, 26, 23, 0.24)        the floating FAB
    0 -8px 20px -12px rgba(28, 26, 23, 0.18) the action-bar dock (wired-base)
  inset box-shadows are edge markers, not lifts, and don't count.
  scripts/native-check.js runs a census and fails on a fifth value.

  The tier @media blocks live at the END of this file and MUST STAY LAST in
  the concatenated stylesheet: almost every rule in them overrides a component
  rule by the same one-class specificity, so the winner is decided by source
  order. See the banner above them.
*/

/* -------------------------------------------------------- header tools --- */
/* The bar carries no rule under it. The page below is a column of white cards
   on warm paper, and a hairline across the top would draw an edge nobody
   needs — the cards already say where the content starts. */
.site-header { border-bottom: 0; }

/* At 360px — an ordinary Android, narrower than the 430px these screens are
   drawn at — the brand and the 44px controls beside it can add up to more
   than the bar. Every control is a target and none of them may give, so the
   name is what shrinks: it is the one thing in the bar a reader can afford
   to lose the tail of — they are already looking at it. */
.brand { min-width: 0; }
.brand__name {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.header-actions {
  display: flex;
  align-items: center;
  gap: 2px;
  margin-left: auto;
}
.header-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  min-height: 44px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--ink-soft, var(--body));
  cursor: pointer;
}
.header-icon svg { width: 20px; height: 20px; }
.header-icon.is-on { color: var(--link); }

/* Two states of one setting live in one pill, never in two boxes with a rule
   between them. The segments keep the 44px floor a thumb needs; the pill is
   what rounds around them. Default on-state is the ink fill (a header
   toggle); .seg--card is the form dialect — white segment lifted with the
   card shadow, for a switch that sits on the paper inside a form. */
.seg {
  display: flex;
  gap: 4px;
  border: 0;
  border-radius: 999px;
  background: var(--canvas-soft);
  padding: 3px;
}
.seg-btn {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  margin: 0;
  padding: 0 12px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
  text-transform: none;
  letter-spacing: normal;
  color: var(--muted);
  cursor: pointer;
}
.seg-btn.is-on {
  background: var(--ink);
  color: var(--surface);
}
.seg--card .seg-btn { font-size: 14px; font-weight: 400; }
.seg--card .seg-btn.is-on {
  background: var(--surface);
  box-shadow: var(--card-shadow);
  color: var(--ink);
  font-weight: 700;
}
/* A radio-driven segment: the input sits immediately before its label. */
.seg input { position: absolute; opacity: 0; pointer-events: none; }
.seg input:checked + .seg-btn { background: var(--ink); color: var(--surface); font-weight: 700; }
.seg--card input:checked + .seg-btn { background: var(--surface); box-shadow: var(--card-shadow); color: var(--ink); }
.seg input:focus-visible + .seg-btn { outline: 2px solid var(--link); outline-offset: 2px; }

/* -------------------------------------------------------- page chrome --- */
/* The 4px of left padding puts the arrow on the same rail as the content's
   bare text (see the 4px edge nudge on .results-head and .form-note): cards
   sit 4px further out, and matching them would align the arrow to the one
   thing it is not above. */
.back-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 10px 0 4px;
  font-family: var(--sans);
  font-size: 14.5px;
  color: var(--muted);
  text-decoration: none;
}
.back-link svg { flex: none; width: 18px; height: 18px; }
.back-link:hover { color: var(--ink); }

.page-title {
  margin: 0;
  padding: 6px 4px 18px;
  font-family: var(--serif-display);
  font-size: 27px;
  line-height: 1.15;
  font-weight: 400;
  letter-spacing: -0.3px;
}

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

/* ------------------------------------------------------- card surfaces --- */
/* A section card: one padded white card holding a whole block of the page. */
.card {
  padding: 18px;
  border-radius: var(--card-radius-lg);
  background: var(--surface);
  box-shadow: var(--card-shadow);
}

/* A list of row cards: each row is its own card, gapped rather than ruled.
   The whole row is the tap; at most ONE button sits beside it (.row-action),
   outside the anchor, so a tap never lands ambiguously. */
.card-stack {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.card-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  border: 0;
  border-radius: var(--card-radius);
  background: var(--surface);
  box-shadow: var(--card-shadow);
}
.row-main {
  flex: 1;
  min-width: 0;
  display: block;
  text-decoration: none;
  color: var(--ink);
}
/* The kicker wears the row's identity tint — which means the ink has to read
   at 11px, not just be distinguishable as a swatch. Written against
   .row-kicker rather than as a bare tint class: a bare .tint--rust would tie
   with the rule below and lose to it for being written first, which is a
   silent way to ship grey cards. */
.row-kicker {
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.7px;
  color: var(--muted);
}
.row-kicker.tint--rust { color: var(--tint-rust); }
.row-kicker.tint--slate { color: var(--tint-slate); }
.row-kicker.tint--ochre { color: var(--tint-ochre); }
.row-kicker.tint--olive { color: var(--tint-olive); }
.row-kicker.tint--plum { color: var(--tint-plum); }
.row-kicker.tint--stone { color: var(--tint-stone); }
.row-name {
  display: block;
  margin-top: 3px;
  font-family: var(--serif-body);
  font-size: 19px;
  line-height: 1.2;
  color: var(--ink);
  text-decoration: none;
}
.row-name:hover { text-decoration: underline; }
/* One line of evidence, always. Built longest-useful-first, so whatever the
   ellipsis eats was the part nobody would have missed. A second line of it
   would be detail, which is what the row's own page is for. */
.row-sub {
  display: block;
  margin-top: 5px;
  font-size: 13.5px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The one action on a card, filled: on a row that exists to end in this tap,
   this is the thing wearing ink. */
.row-action {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  border: 0;
  border-radius: 999px;
  background: var(--ink);
  color: var(--surface);
  text-decoration: none;
  cursor: pointer;
}
.row-action svg { width: 18px; height: 18px; }

/* Rows ruled INSIDE one card — the other list mode. One card rather than N:
   these are a single list to read down, and a gap between each pair would
   make them N things to consider. The rule sits on the TOP of each row but
   the first, so rows hidden behind "show all" cannot leave a stray line at
   the foot of the card. overflow:hidden keeps a row's pressed wash inside
   the corner it was drawn for. */
.card-list {
  border-radius: var(--card-radius);
  background: var(--surface);
  box-shadow: var(--card-shadow);
  overflow: hidden;
}
.card-list-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  min-height: 52px;
  padding: 0 16px;
  border: 0;
  background: none;
  font-family: var(--sans);
  font-size: 15px;
  color: var(--ink);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.card-list-row + .card-list-row { box-shadow: inset 0 1px 0 var(--card-rule); }
.card-list-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.card-list-meta {
  flex: none;
  font-size: 12.5px;
  color: var(--muted);
}

/* An empty list is still a list, so it gets the shape of one: the same card
   the rows would have been, saying why there aren't any. */
.empty {
  padding: 36px 16px;
  border: 0;
  border-radius: var(--card-radius);
  background: var(--surface);
  box-shadow: var(--card-shadow);
  text-align: center;
}
.empty-title {
  margin: 0;
  font-family: var(--serif-body);
  font-size: 18px;
  line-height: 1.35;
}
.empty-body {
  margin: 6px 0 0;
  font-size: 14.5px;
  color: var(--muted);
  text-wrap: pretty;
}
.empty .btn { margin-top: 16px; }

/* The head of a list: count on the left, sort or nothing on the right. The
   4px side padding is the bare-text edge nudge — uncarded text sits 4px in
   so it optically aligns with card interiors. */
.results-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 22px 4px 10px;
}
.results-count {
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--muted);
}
.results-sort { font-size: 12.5px; color: var(--muted); }

/* ------------------------------------------------------ identity marks --- */
/* The block behind a glyph at the head of a taxonomy page (52px) or inside a
   pick tile (32px). Which key wears which tint is decided once in project
   config; a key's ink never changes after users have learned it. */
.mark {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border-radius: 16px;
  background: var(--tint-stone);
  color: var(--surface);
}
.mark svg { width: 24px; height: 24px; }
.mark-sm { width: 32px; height: 32px; border-radius: 10px; }
.mark-sm svg { width: 17px; height: 17px; }
.mark.tint--rust { background: var(--tint-rust); }
.mark.tint--slate { background: var(--tint-slate); }
.mark.tint--ochre { background: var(--tint-ochre); }
.mark.tint--olive { background: var(--tint-olive); }
.mark.tint--plum { background: var(--tint-plum); }
.mark.tint--stone { background: var(--tint-stone); }

/* Scope a tint to a whole section (a panel and everything in it) with a data
   attribute; descendants read var(--tint) without repeating the colour. */
[data-tint="rust"] { --tint: var(--tint-rust); }
[data-tint="slate"] { --tint: var(--tint-slate); }
[data-tint="ochre"] { --tint: var(--tint-ochre); }
[data-tint="olive"] { --tint: var(--tint-olive); }
[data-tint="plum"] { --tint: var(--tint-plum); }
[data-tint="stone"] { --tint: var(--tint-stone); }

/* --------------------------------------------------------------- pills --- */
/* The base's square buttons become pills. 999px renders as height/2 at any
   height, so one rule keeps every size on the ladder. The border stays from
   the base (invisible on the fill, the edge on outline/quiet). */
.btn, .btn-outline, .btn-quiet, .btn-sm, .btn-lg { border-radius: 999px; }

/* The full-width primary: the one filled element that finishes a screen's
   job. 54px tall — a docked or hero CTA, not an inline button. */
.btn-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  min-height: 54px;
  margin: 0;
  padding: 0 14px;
  border: 0;
  border-radius: 999px;
  background: var(--ink);
  color: var(--surface);
  text-decoration: none;
  font-family: var(--sans);
  font-size: 16px;
  font-weight: 700;
  cursor: pointer;
}
.btn-cta svg { flex: none; width: 19px; height: 19px; }
/* Quiet until there is something to submit. It STILL submits: the server is
   what knows whether the form is finished, and a button that cannot be
   pressed cannot say why not. Pressing this one answers that. */
.btn-cta.is-waiting, .btn.is-waiting {
  background: var(--btn-waiting);
  color: var(--muted);   /* quiet, but still AA on the waiting fill — it IS enabled */
}

/* A choice chip: paper tone inside a white card (a white chip on white is an
   outline of nothing), filled with the ink when picked. Real inputs inside;
   the ring goes on the chip. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  margin: 0;
  padding: 0 14px;
  border: 0;
  border-radius: 999px;
  background: var(--canvas-soft);
  font-family: var(--sans);
  font-size: 13.5px;
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
  text-decoration: none;
  color: var(--ink-soft);
  cursor: pointer;
}
.chip input { position: absolute; opacity: 0; pointer-events: none; }
.chip.is-on, .chip:has(input:checked) {
  background: var(--ink);
  color: var(--surface);
  font-weight: 700;
}
/* A negative pick is the same weight of answer as a positive one, and it
   should not look like the louder one — muted fill, never a red one. */
.chip.is-bad:has(input:checked), .chip.is-on.is-bad { background: var(--muted); }
.chip:has(input:focus-visible) { outline: 2px solid var(--link); outline-offset: 2px; }
.chip-row { display: flex; flex-wrap: wrap; gap: 8px; }

/* The filter is a filled chip because it is a STATE, not a choice: it says
   what the list is showing right now. It is not the tap target — the bare
   link-button beside it ("Clear") is what undoes it. */
.filter-chip {
  display: inline-flex;
  align-items: center;
  height: 34px;
  padding: 0 14px;
  border: 0;
  border-radius: 999px;
  background: var(--ink);
  font-size: 13.5px;
  font-weight: 700;
  color: var(--surface);
}
.link-btn {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 4px;
  border: 0;
  background: none;
  font-family: var(--sans);
  font-size: 14.5px;
  color: var(--link);
  cursor: pointer;
}
.link-btn:hover { color: var(--ink); }

/* The tally pill: a count in its shortest form, paper tone, beside the thing
   it counts. */
.count-pill {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 28px;
  padding: 0 10px;
  border-radius: 999px;
  background: var(--canvas-soft);
  font-size: 13px;
  font-weight: 700;
  color: var(--ink-soft);
  white-space: nowrap;
}
.count-pill svg { width: 13px; height: 13px; }

/* ---------------------------------------------------------------- tabs --- */
/* Full-width underline tabs. The rule under them belongs to the tabs — it is
   the track the active one is marked on, not a divider between sections. The
   active tab changes three ways: colour, weight, and the rule under it. */
.tab-row { display: flex; margin-top: 16px; }
.tab {
  flex: 1;
  min-height: 44px;
  padding: 0 8px;
  border: 0;
  background: none;
  box-shadow: inset 0 -1.5px 0 var(--hairline);
  font-family: var(--sans);
  font-size: 14.5px;
  color: var(--muted);
  cursor: pointer;
}
.tab.is-on {
  color: var(--ink);
  font-weight: 700;
  box-shadow: inset 0 -2px 0 var(--ink);
}

/* ----------------------------------------------------------------- FAB --- */
/* The floating action follows the scroll, because the moment a reader needs
   it is the moment they were reading the list — not the moment they reached
   the bottom of it. It parks on the right of the COLUMN and stops there:
   max() pins it to the column's edge once the window is wider than the
   column, so it never drifts out to a laptop's far corner. Both halves read
   --page-measure, so the pill follows the column through all three tiers. */
.fab {
  position: fixed;
  right: max(
    var(--fab-gutter, 16px),
    calc(50% - var(--page-measure, 430px) / 2 + var(--fab-gutter, 16px))
  );
  bottom: calc(20px + env(safe-area-inset-bottom));
  z-index: 50;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  min-height: 52px;
  padding: 0 20px 0 17px;
  border: 0;
  border-radius: 999px;
  background: var(--ink);
  color: var(--surface);
  box-shadow: 0 3px 10px rgba(28, 26, 23, 0.24);
  font-family: var(--sans);
  font-size: 15.5px;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
}
.fab svg { width: 19px; height: 19px; }

/* The same action in the shape a rail wants: full width, no shadow, at the
   foot of the controls rather than floating over the list. Printed on every
   screen that has a rail and shown only where the split happens (see the
   1080px tier), so the two never appear at once. */
.rail-cta {
  display: none;
  align-items: center;
  justify-content: center;
  gap: 9px;
  width: 100%;
  min-height: 52px;
  margin-top: 20px;
  border: 0;
  border-radius: 999px;
  background: var(--ink);
  color: var(--surface);
  font-family: var(--sans);
  font-size: 15.5px;
  font-weight: 700;
  cursor: pointer;
}
.rail-cta svg { width: 19px; height: 19px; }

/* --------------------------------------------------------------- sheet --- */
/* A form arrives OVER the list rather than at the foot of it. On a phone it
   rises from the bottom edge; the 1080px tier turns it into the centered
   dialog it always was underneath. */
.sheet {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  background: var(--scrim);
}
/* `display: flex` above outranks the UA's `[hidden]`, so the hidden state has
   to be spelled out. */
.sheet[hidden] { display: none; }
.sheet-scrim { flex: 1; }
.sheet-panel {
  width: 100%;
  max-width: 430px;
  margin-inline: auto;
  padding: 8px 16px calc(20px + env(safe-area-inset-bottom));
  border-radius: 20px 20px 0 0;
  background: var(--canvas);
  max-height: 88vh;
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* The grab bar. It does nothing — the sheet is closed with the × or the
   scrim — but it is the shape that says "this came up from the bottom". */
.sheet-grip {
  display: block;
  width: 38px;
  height: 4px;
  margin: 6px auto 14px;
  border-radius: 999px;
  background: var(--border);
}
.sheet-head { display: flex; align-items: center; gap: 12px; }
.sheet-title {
  flex: 1;
  font-family: var(--serif-body);
  font-size: 20px;
  font-weight: 400;
  line-height: 1.2;
  color: var(--ink);
}
.sheet-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  margin-right: -10px;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
}
.sheet-close svg { width: 20px; height: 20px; }
/* Inside a sheet the ground is already the paper, so a field is borderless
   white. Doubled up on the type: the base floors text fields at 56px through
   `input[type="..."]`, and a type-and-attribute selector outranks a lone
   class — a bare `.sheet-input` would never once apply to an <input>. The
   16px font floor is untouched: that is about iOS zoom and not ours to
   lower. */
input.sheet-input,
select.sheet-input {
  display: block;
  width: 100%;
  height: 52px;
  min-height: 52px;
  padding: 0 16px;
  border: 0;
  border-radius: 12px;
  background: var(--surface);
  font-family: var(--sans);
  font-size: 16px;
  color: var(--ink);
}
input.sheet-input { -webkit-appearance: none; appearance: none; }

/* ---------------------------------------------------- accordion panels --- */
/* One card per section, head inside it, built on <details>. Give every
   details the same `name` and the browser holds one panel open at a time.
   `overflow: hidden` keeps the head's pressed wash inside the corner it was
   drawn for. The chevron is the page's only moving part — which is how a
   reader knows the row is a lid and not a link. */
.panels {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.panel {
  border-radius: var(--card-radius-lg);
  background: var(--surface);
  box-shadow: var(--card-shadow);
  overflow: hidden;
}
.panel-head {
  list-style: none;
  display: flex;
  align-items: center;
  gap: 11px;
  min-height: 60px;
  padding: 16px 18px;
  cursor: pointer;
}
.panel-head::-webkit-details-marker { display: none; }
.panel-head:active { background: var(--canvas-soft); }
.panel-head:focus-visible { outline: 2px solid var(--ink); outline-offset: -2px; }
/* The section's pigment dot: which colour means which section never changes
   after users learn it, so "the terracotta one" still opens Contact on the
   third visit. */
.panel-dot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--tint, var(--muted));
}
.panel-titles {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.panel-title {
  font-family: var(--serif-display);
  font-size: 18px;
  line-height: 1.22;
}
/* One line, always. The digest is built longest-useful-first, so whatever
   the ellipsis eats was the part nobody would have missed. */
.panel-meta {
  font-size: 13px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chev {
  flex: none;
  width: 18px;
  height: 18px;
  color: var(--muted);
  transition: transform 0.15s ease;
}
details[open] > summary .chev { transform: rotate(180deg); }
/* The body sits in the same card as its head, under one line — a card says
   "this belongs together" by being a card, so no rail and no inset. */
.panel-body {
  margin: 0;
  padding: 14px 18px 18px;
  border-top: 1px solid var(--card-rule);
}

/* Ruled between rows, not under the last one: the card's own bottom edge is
   already there, and a rule drawn just inside it reads as a double line. */
.tap-row {
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 52px;
  padding: 8px 0;
  text-decoration: none;
  color: var(--ink);
}
.tap-row + .tap-row { border-top: 1px solid var(--card-rule); }
.tap-icon { display: inline-flex; color: var(--tint, var(--muted)); }
.tap-icon svg { width: 18px; height: 18px; }
.tap-label { flex: 1; min-width: 0; font-size: 15px; word-break: break-word; }
/* The verb, not the noun: "Open", "Send", "Directions". */
.tap-action {
  flex: none;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--muted-soft);
}

/* --------------------------------------------------------- inset cards --- */
/* Inside a white card a second white block is invisible, so nested content
   sits on the paper tone instead — one soft card each, which is also what
   makes items read as separate contributions rather than as rows of a table
   nobody meant to draw. */
.inset-card {
  padding: 14px;
  border-radius: 13px;
  background: var(--canvas-soft);
}
.fact-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px;
}
.fact {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 14px;
  border-radius: 13px;
  background: var(--canvas-soft);
}
.fact-label {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--muted);   /* on the inset's paper tone, muted-soft falls under AA */
}
.fact-value { font-size: 15.5px; color: var(--ink-soft); }

/* ----------------------------------------------------------- step form --- */
/* The card being the grouping turns a form inside out. A field used to BE a
   white card floating on paper; inside a step card that is white on white,
   which is nothing at all — so boxes take an edge here and give up their
   shadow. Steps are numbered because a long form with no shape is one people
   leave in the middle: the numbers say how much is left without a progress
   bar pretending to know how long it takes. */
.step-card {
  padding: 18px;
  border-radius: var(--card-radius-lg);
  background: var(--surface);
  box-shadow: var(--card-shadow);
}
.step-card + .step-card { margin-top: 12px; }
.step-head { display: flex; align-items: center; gap: 10px; }
.step-num {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 999px;
  background: var(--canvas-soft);
  font-family: var(--sans);
  font-size: 12.5px;
  font-weight: 700;
  color: var(--muted);
}
/* Written as a class, not left to the bare h2: the base sets h2 to 26px with
   12px of top margin, and a heading that shoves the card's own padding
   around is a heading nobody placed. */
.step-title {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-family: var(--serif-display);
  font-size: 19px;
  line-height: 1.2;
  font-weight: 400;
  color: var(--ink);
}
.step-optional {
  font-family: var(--sans);
  font-size: 13.5px;
  color: var(--muted-soft);
}

/* An input on a white card: bordered with the edge weight, rounded to the
   input radius. Doubled up on the type for the same specificity reason as
   .sheet-input. */
input.card-input,
select.card-input,
textarea.card-input {
  display: block;
  width: 100%;
  height: 52px;
  min-height: 52px;
  padding: 0 15px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  box-shadow: none;
  font-family: var(--sans);
  /* 16px minimum, or iOS zooms the whole page on focus. */
  font-size: max(16px, 1rem);
  color: var(--ink);
  -webkit-appearance: none;
  appearance: none;
}
textarea.card-input {
  height: auto;
  min-height: 92px;
  padding: 14px 15px;
  line-height: 1.45;
  resize: none;
}

/* Two short fields pair up as soon as the card is wide enough (see the 720px
   tier) — a 780px-wide field holding a phone number reads as a mistake. */
.pair-grid {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 14px;
}

/* The pick tile: a pressable choice with an edge, not a shadow. Held, not
   filled, when picked — the tile keeps its colour and its glyph, and what
   changes is the edge closing around it. The inset shadow doubles the line
   rather than thickening the border, so a picked tile does not grow a pixel
   and shove the rest of its row. */
.pick-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  margin-top: 14px;
  border: 0;
}
.pick-tile {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 58px;
  margin: 0;
  padding: 0 12px;
  border: 1px solid var(--card-edge);
  border-radius: 13px;
  background: var(--surface);
  box-shadow: none;
  font-family: var(--sans);
  font-size: 14.5px;
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
  color: var(--body);
  cursor: pointer;
}
.pick-tile input { position: absolute; opacity: 0; pointer-events: none; }
.pick-tile.is-on, .pick-tile:has(input:checked) {
  border-color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink);
  color: var(--ink);
  font-weight: 700;
}
.pick-tile:has(input:focus-visible) { outline: 2px solid var(--link); outline-offset: 2px; }
/* Wraps instead of clipping: two lines of 14.5px still sit inside the tile,
   and every label stays readable on a 390px phone. */
.pick-tile .pick-name {
  min-width: 0;
  font-family: var(--sans);
  font-size: 14.5px;
  line-height: 1.25;
  overflow: hidden;
}

/* Bare fine print under the cards, on the 4px text rail. */
.form-note {
  margin-top: 16px;
  padding: 0 4px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted);
  text-wrap: pretty;
}

/* The docked CTA: the button that finishes the job never scrolls away. The
   gradient is what keeps it honest — the page visibly continues under it, so
   nobody reads the bar as the end of the form when there is still a field
   below the fold. (The sticky .action-bar in the base is the PROSE page's
   dock; this fixed one belongs to the form archetype, whose main pads 120px
   for it.) */
.form-dock {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 50;
  background: linear-gradient(
    to bottom,
    rgba(var(--canvas-rgb), 0) 0%,
    rgba(var(--canvas-rgb), 0.92) 34%,
    var(--canvas) 62%
  );
}
.form-dock-inner {
  max-width: var(--page-measure, 430px);
  margin-inline: auto;
  padding: 26px var(--page-gutter, 12px) calc(14px + env(safe-area-inset-bottom));
}
/* One line over the button saying what is about to happen, clipped rather
   than wrapped — it is a receipt, not reading. */
.dock-note {
  margin: 0;
  padding: 0 4px 8px;
  font-size: 13px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ------------------------------------------------------------ settings --- */
/* Bare by default: a switch you came here to think about reads better as
   prose than as a thing in a box. Only an offer wears a card. */
.settings-blocks {
  display: flex;
  flex-direction: column;
  gap: 24px;
  margin-top: 22px;
}
.settings-block { padding: 0 4px; }
.settings-block--card {
  padding: 18px;
  border-radius: var(--card-radius-lg);
  background: var(--surface);
  box-shadow: var(--card-shadow);
}

/* ------------------------------------------------------ archetype hooks --- */
/* Every app screen declares ONE archetype class on the first child of main:
     .home           search + list, splits into rail + results at 1080
     .list-page      a stack of row cards
     .form-page      numbered step cards over a docked CTA
     .settings-page  prose blocks, reading measure
     .detail-page    hero card + accordion panels, splits at 1080
   The measure is declared ONCE per page, on body, and read by BOTH the
   header inner and main — a page's width is one fact, not two that have to
   agree. Written against body so it can reach a header printed BEFORE the
   element that identifies the page. Engines without :has() fall back to the
   base 680px measure — a light, acceptable degradation.

   REGISTER NEW HOOKS IN ALL THREE LISTS BELOW (and in the tier blocks), or
   the page gets a 680px header over a 430px column. */
body:has(main > .home),
body:has(main > .list-page),
body:has(main > .form-page),
body:has(main > .settings-page),
body:has(main > .detail-page) {
  --page-measure: 430px;
  --page-gutter: 12px;
}

body:has(main > .home) .site-header__inner,
body:has(main > .list-page) .site-header__inner,
body:has(main > .form-page) .site-header__inner,
body:has(main > .settings-page) .site-header__inner,
body:has(main > .detail-page) .site-header__inner {
  max-width: var(--page-measure);
  padding-left: calc(var(--page-gutter) + env(safe-area-inset-left, 0px));
  padding-right: calc(var(--page-gutter) + env(safe-area-inset-right, 0px));
}

/* One phone column. Gutters COMPOSE with safe areas rather than replacing
   them. The foot pad leaves room for what floats over each archetype: 96px
   under a FAB, 120px under a form dock, 40px under prose. */
main:has(> .home),
main:has(> .list-page),
main:has(> .form-page),
main:has(> .settings-page),
main:has(> .detail-page) {
  max-width: var(--page-measure);
  padding-top: 8px;
  padding-left: calc(var(--page-gutter) + env(safe-area-inset-left, 0px));
  padding-right: calc(var(--page-gutter) + env(safe-area-inset-right, 0px));
  padding-bottom: calc(96px + env(safe-area-inset-bottom));
}
/* A list, form, settings or detail page opens on a 44px back link, which
   brings its own air. */
main:has(> .list-page),
main:has(> .detail-page) { padding-top: 4px; }
main:has(> .form-page) {
  padding-top: 4px;
  padding-bottom: calc(120px + env(safe-area-inset-bottom));
}
main:has(> .settings-page),
main:has(> .detail-page) {
  padding-bottom: calc(40px + env(safe-area-inset-bottom));
}
main:has(> .settings-page) { padding-top: 4px; }

/* The rail: on the split archetypes, everything the list is narrowed WITH.
   Position becomes sticky at the 1080px tier. */
.rail { margin-bottom: 12px; }

/* ------------------------------------------------------ hover quarantine --- */
/* Background hovers belong ONLY inside this block — on iOS a hover state
   applied without it sticks lit after the tap. Color-only hovers above are
   safe and stay at base. */
@media (hover: hover) and (pointer: fine) {
  .card-list-row:hover { background: var(--canvas-soft); }
}
/* Press states answer ANY coarse pointer — a tap must show it landed even on
   a touch laptop whose primary pointer is a trackpad. */
@media (any-pointer: coarse) {
  .card-list-row:active { background: var(--canvas-soft); }
}

/* ==================================================== the wider window ===
   Three sizes, one layout language. Each time the column grows it changes
   what it holds rather than only stretching:

   - 720px — the column widens and the cards pair off into a grid. Lists
     that were a stack of one-line rows become a field of cards, and a
     "show all" toggle has nothing left to say.
   - 1080px — the split archetypes divide. Everything the list is narrowed
     WITH moves into a rail that stays put while the list scrolls beside
     it, so a filter is never something a reader scrolls back up to reach.

   Each archetype keeps its own measure, because each holds a different
   thing: a directory of cards reads at 1160px; a form does not, because a
   name field 850px wide reads as a mistake; settings is reading and stops
   sooner still.

   Nothing here reaches a phone: every value is inside a min-width query,
   and the 430px hook block above is what a phone gets.

   THIS BLOCK IS LAST ON PURPOSE AND MUST STAY LAST. Almost every rule in
   it overrides a component rule by the same one-class specificity, which
   means the winner is decided by source order — written earlier,
   `.card-stack { display: grid }` here silently loses to
   `display: flex` hundreds of lines below it, and the cards stay in one
   column with nothing to say why. scripts/native-check.js fails the build
   if an unconditioned rule lands after this line.

   Two things that look like they belong here are not here: rows hidden
   behind a "show all" toggle. They are held by the `hidden` attribute, and
   the base opens with `[hidden] { display: none !important }` — which no
   selector can outrank. Take the attribute off in JS at these widths
   instead; that is the better answer anyway, because a row that is on
   screen should not still be telling a screen reader it is not.
   ==================================================================== */

@media (min-width: 720px) {
  body:has(main > .home),
  body:has(main > .list-page),
  body:has(main > .detail-page) { --page-measure: 760px; }
  body:has(main > .form-page),
  body:has(main > .settings-page) { --page-measure: 700px; }

  body:has(main > .home),
  body:has(main > .list-page),
  body:has(main > .form-page),
  body:has(main > .settings-page),
  body:has(main > .detail-page) {
    --page-gutter: 24px;
    --fab-gutter: 24px;
  }

  /* The cards pair off. auto-fill with a 320px floor rather than a fixed
     two columns, so the count follows the window; minmax(0, …) so one long
     name cannot set the width of the card beside it. */
  .card-stack {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 8px;
  }

  /* Rows ruled into one card read as a list on a phone. The same thing in
     two columns reads as a table nobody meant to draw — so each row becomes
     its own card here and the rules come off. The sibling rule has to be
     answered by name: what it sets is a box-shadow, and the shadow is what
     draws the rule between two rows. */
  .card-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: 8px;
    background: none;
    box-shadow: none;
    overflow: visible;
  }
  .card-list-row,
  .card-list-row + .card-list-row {
    border-radius: var(--card-radius);
    background: var(--surface);
    box-shadow: var(--card-shadow);
  }

  /* Three tiles across. Not a --page-* token: the picker counts tiles. */
  .pick-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }

  /* Two short fields side by side. Below this width they stack, because two
     boxes sharing a 400px line are two boxes nobody can type in. */
  .pair-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* Settings blocks pair off; when one is hidden the survivor takes the
     width instead of sitting in half a page like a column that lost its
     pair. */
  .settings-blocks {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 24px;
    align-items: start;
    margin-top: 24px;
  }
  .settings-blocks:has(> .settings-block[hidden]) {
    grid-template-columns: minmax(0, 1fr);
  }

  /* The sheet stops widening BEFORE the column does. A bottom sheet as wide
     as a tablet is a page, and its four fields would sit alone in the
     middle of it. */
  .sheet-panel { max-width: 560px; }
}

@media (min-width: 1080px) {
  body:has(main > .home),
  body:has(main > .list-page) { --page-measure: 1160px; }
  body:has(main > .detail-page) { --page-measure: 1120px; }
  body:has(main > .form-page) { --page-measure: 880px; }
  body:has(main > .settings-page) { --page-measure: 840px; }

  body:has(main > .home),
  body:has(main > .list-page),
  body:has(main > .form-page),
  body:has(main > .settings-page),
  body:has(main > .detail-page) {
    --page-gutter: 32px;
    --fab-gutter: 32px;
  }

  /* The split. Two columns, and the left one holds still — clear of the
     3.5rem bar above it. */
  .home {
    display: grid;
    grid-template-columns: 360px minmax(0, 1fr);
    gap: 40px;
    align-items: start;
  }
  .detail-page {
    display: grid;
    grid-template-columns: 340px minmax(0, 1fr);
    gap: 32px;
    align-items: start;
  }
  /* A notice is about the page, not about either column. */
  .home > .alert, .detail-page > .alert { grid-column: 1 / -1; }
  .rail {
    position: sticky;
    top: 76px;
    margin-bottom: 0;
  }

  /* A pill floats where thumbs reach. On a desk the action belongs in the
     rail with the other things done to the list, not hovering over it — so
     a split page swaps one for the other. Split pages only: a plain list
     has no rail to put it in, and keeps its pill. */
  body:has(main > .home) .fab,
  body:has(main > .detail-page) .fab { display: none; }
  .rail-cta { display: inline-flex; }

  /* Nothing rises from the foot of a window this tall. The sheet becomes
     what it always was underneath — a dialog — and the grab bar goes,
     because there is no edge left for it to claim it came from. */
  .sheet {
    flex-direction: row;
    align-items: center;
    justify-content: center;
    padding: 24px;
  }
  .sheet-scrim { position: absolute; inset: 0; flex: none; }
  .sheet-panel {
    position: relative;
    max-width: 520px;
    padding: 24px 28px 28px;
    border-radius: 20px;
  }
  .sheet-grip { display: none; }
}
