/* =====================================================================
   1. FONT
   ===================================================================== */

@font-face {
  font-family: "Archivo";
  src: url("/s/archivo-var.woff2") format("woff2-variations");
  font-weight: 400 700;
  font-stretch: 88% 125%;
  font-style: normal;
  font-display: swap;
}

/* Metric-matched fallback. Kills the layout shift during the swap.
   Values tuned against Helvetica Neue; re-measure if the stack changes. */
@font-face {
  font-family: "Archivo Fallback";
  src: local("Helvetica Neue"), local("Helvetica"), local("Arial");
  size-adjust: 97%;
  ascent-override: 92%;
  descent-override: 24%;
  line-gap-override: 0%;
}


/* =====================================================================
   2. TOKENS — DARK
   ===================================================================== */

:root {
  color-scheme: dark light;

  /* Surfaces — constant hue 33-36°, saturation 22-28% across the whole ramp */
  --canvas: #0E0C09; --surface: #191510; --surface-2: #221C13; --surface-3: #2C251A;
  --surface-2-a: rgba(34, 28, 19, 0.86);   /* backdrop-filter fill */
  --overlay: rgba(9, 7, 4, 0.62);
  --canvas-grad: radial-gradient(ellipse 120% 60% at 50% -10%, var(--surface) 0%, var(--canvas) 62%);

  /* Borders */
  --border-subtle: #2A231A; --border: #3B3225;
  --border-strong: #7A6C58;   /* 3.35:1 on surface-2 — meets WCAG 1.4.11 non-text contrast */

  /* Text — near-neutral bone, NOT #F3ECDC (that yellow is what reads as parchment) */
  --text: #E8E4DA;            /* 15.39:1 canvas, 14.46:1 surface */
  --text-2: #A69E8D;          /* 7.35:1 canvas */
  --text-3: #8D8574;          /* 5.34:1 canvas · 5.02:1 surface · 4.63:1 surface-2 */
  --text-placeholder: rgba(232, 228, 218, 0.50);

  /* Accent — gold, a LINE, never a field */
  --accent: #C9A130;          /* brand gold unchanged. 8.00:1 canvas, 7.52:1 surface */
  --accent-hover: #E3C56B; --accent-line: rgba(201, 161, 48, 0.34);
  --accent-bg: rgba(201, 161, 48, 0.12); --accent-ring: rgba(201, 161, 48, 0.24);
  --text-on-accent: #0E0C09;

  /* Danger — brand oxblood, lifted for dark (#8E2029 alone is only 2.22:1 here) */
  --danger: #E0736A;          /* 6.36:1 canvas — text/icon use */
  --danger-hover: #EE8A81; --danger-fill: #8E2029; --danger-fill-hover: #A5252F;
  --danger-bg: rgba(224, 115, 106, 0.12); --danger-border: rgba(224, 115, 106, 0.38);

  /* Success — bronze-green, hue-distinct from gold for protan/deutan readers */
  --success: #6FAE8C;         /* 7.56:1 canvas */
  --success-bg: rgba(111, 174, 140, 0.12); --success-border: rgba(111, 174, 140, 0.34);

  /* Elevation — dark strategy: a luminous top edge + wide black ambient.
     A pure drop shadow is invisible on near-black; the highlight (warm
     white 255,252,244, never pure white) does the lifting. */
  --shadow-1: inset 0 1px 0 rgba(255,252,244,.045), 0 1px 2px rgba(0,0,0,.60);
  --shadow-2: 0 -1px 0 rgba(255,252,244,.06), 0 -12px 32px rgba(0,0,0,.70);
  --shadow-3: 0 0 0 1px rgba(255,252,244,.06), 0 16px 40px -8px rgba(0,0,0,.80);
  --glow-accent: 0 0 0 1px rgba(201,161,48,.32), 0 8px 28px -12px rgba(201,161,48,.35);
  --shadow-logo: 0 18px 40px rgba(0, 0, 0, 0.55);   /* was index.ts:115 */

  /* The crest ring — used at :focus-visible and the confirmation thumbnail ONLY */
  --ring-crest: 0 0 0 2px var(--canvas), 0 0 0 4px var(--accent);

  --font: "Archivo", "Archivo Fallback", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  /* Type scale — bimodal by design: 1.125 ratio across the UI band (many
     near-equal small sizes should read as one instrument), a hard break
     to display sizes. --fs-md (16px) is the floor for EVERY input — iOS
     Safari zooms the viewport below 16px, costing a tap to undo. */
  --fs-micro: 0.6875rem; --fs-xs: 0.75rem; --fs-sm: 0.875rem; --fs-md: 1rem;
  --fs-lg: 1.125rem; --fs-xl: 1.25rem; --fs-2xl: 1.4375rem; --fs-3xl: 1.75rem;
  --fs-4xl: 2.25rem; --fs-5xl: 3rem;   /* 5xl: confirmation + public hero/coming-soon headline */
  --lh-tight: 1.05; --lh-snug: 1.22; --lh-normal: 1.5; --lh-relaxed: 1.6;
  --track-display: -0.022em; --track-tight: -0.01em; --track-normal: 0;
  --track-label: 0.06em; --track-slab: 0.18em;   /* the slab micro-label */
  --fw-regular: 400; --fw-medium: 500; --fw-semibold: 600; --fw-bold: 700;
  --wd-narrow: 88%; --wd-normal: 100%; --wd-wide: 112%; --wd-expanded: 125%;

  /* Spacing — 4px base */
  --space-3xs:2px; --space-2xs:4px; --space-xs:8px; --space-sm:12px; --space-md:16px;
  --space-lg:24px; --space-xl:32px; --space-2xl:48px; --space-3xl:64px; --space-4xl:96px;
  --gutter: 20px;   /* not 16 — generous edge reads as "expensive" */

  /* Radii — buttons and inputs SHARE --r-md so a form reads as one machined object */
  --r-xs:3px; --r-sm:6px; --r-md:10px; --r-lg:14px; --r-pill:999px;
  --bw:1px; --bw-thick:2px;
  --w-form:34rem; --w-prose:44rem; --w-list:76rem;
  --z-base:0; --z-raised:10; --z-sticky:100; --z-banner:200; --z-overlay:300; --z-toast:400;

  /* Touch */
  --tap-min:44px; --tap-comfortable:52px; --tap-action:64px; --tap-hero:104px;
  --safe-b: env(safe-area-inset-bottom, 0px); --safe-t: env(safe-area-inset-top, 0px);
  --actionbar-h: calc(var(--tap-action) + var(--space-xl) + var(--safe-b));

  --dur-instant:90ms; --dur-fast:140ms; --dur:200ms; --dur-slow:320ms; --dur-slower:560ms;
  --ease-out: cubic-bezier(.22,1,.36,1); --ease-in-out: cubic-bezier(.65,0,.35,1);
  --ease-spring: cubic-bezier(.34,1.56,.64,1);   /* confirmation only */
}


/* =====================================================================
   3. TOKENS — LIGHT
   ===================================================================== */

/* Light — porcelain, not cream. #F6F4F1 canvas + pure-white cards. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    color-scheme: light dark;
    --canvas: #F6F4F1; --surface: #FFFFFF; --surface-2: #FBFAF7; --surface-3: #F1EEE8;
    --surface-2-a: rgba(251,250,247,.88); --overlay: rgba(25,21,16,.38);
    --canvas-grad: radial-gradient(ellipse 120% 60% at 50% -10%, #FFF 0%, var(--canvas) 62%);
    --border-subtle: #EAE7E0; --border: #DCD8CE; --border-strong: #8E8676;   /* 3.60:1 on white */
    --text: #191510;            /* 16.77:1 on canvas — brand/colors.txt verbatim */
    --text-2: #5C564A; --text-3: #6E6659; --text-placeholder: rgba(25,21,16,.45);
    --accent: #7D5D18;          /* 5.50:1 — #C9A130 is only 2.4:1 on white, unusable as text */
    --accent-hover: #6A4E12; --accent-line: rgba(125,93,24,.26);
    --accent-bg: rgba(125,93,24,.09); --accent-ring: rgba(125,93,24,.18); --text-on-accent: #FFF;
    --danger: #8E2029;          /* 8.02:1 — brand red works as-is here */
    --danger-hover: #741A22; --danger-fill: #8E2029; --danger-fill-hover: #A5252F;
    --danger-bg: rgba(142,32,41,.08); --danger-border: rgba(142,32,41,.30);
    --success: #2E6B4F; --success-bg: rgba(46,107,79,.09); --success-border: rgba(46,107,79,.28);
    /* Light strategy: real layered shadows tinted with the warm black —
       a pure-black shadow on a warm page reads grey and dead. */
    --shadow-1: 0 1px 2px rgba(25,21,16,.06), 0 1px 1px rgba(25,21,16,.04);
    --shadow-2: 0 -1px 0 var(--border-subtle), 0 -8px 24px rgba(25,21,16,.08);
    --shadow-3: 0 12px 32px -8px rgba(25,21,16,.18), 0 2px 6px rgba(25,21,16,.08);
    --glow-accent: 0 0 0 1px rgba(125,93,24,.18), 0 8px 24px -12px rgba(125,93,24,.22);
    --shadow-logo: 0 18px 40px rgba(25, 21, 16, 0.22);
  }
}

/* Manual override (D9). Placed AFTER the media query above — deliberately.
   :root[data-theme="light"] and the media query's :root:not([data-theme="dark"])
   have identical specificity (0-2-0); CSS resolves the tie by source order,
   last-declared wins. Placed here, "Always light" wins unconditionally
   regardless of the visitor's OS setting. */
:root[data-theme="light"] {
  color-scheme: light dark;
  --canvas: #F6F4F1; --surface: #FFFFFF; --surface-2: #FBFAF7; --surface-3: #F1EEE8;
  --surface-2-a: rgba(251,250,247,.88); --overlay: rgba(25,21,16,.38);
  --canvas-grad: radial-gradient(ellipse 120% 60% at 50% -10%, #FFF 0%, var(--canvas) 62%);
  --border-subtle: #EAE7E0; --border: #DCD8CE; --border-strong: #8E8676;
  --text: #191510;
  --text-2: #5C564A; --text-3: #6E6659; --text-placeholder: rgba(25,21,16,.45);
  --accent: #7D5D18;
  --accent-hover: #6A4E12; --accent-line: rgba(125,93,24,.26);
  --accent-bg: rgba(125,93,24,.09); --accent-ring: rgba(125,93,24,.18); --text-on-accent: #FFF;
  --danger: #8E2029;
  --danger-hover: #741A22; --danger-fill: #8E2029; --danger-fill-hover: #A5252F;
  --danger-bg: rgba(142,32,41,.08); --danger-border: rgba(142,32,41,.30);
  --success: #2E6B4F; --success-bg: rgba(46,107,79,.09); --success-border: rgba(46,107,79,.28);
  --shadow-1: 0 1px 2px rgba(25,21,16,.06), 0 1px 1px rgba(25,21,16,.04);
  --shadow-2: 0 -1px 0 var(--border-subtle), 0 -8px 24px rgba(25,21,16,.08);
  --shadow-3: 0 12px 32px -8px rgba(25,21,16,.18), 0 2px 6px rgba(25,21,16,.08);
  --glow-accent: 0 0 0 1px rgba(125,93,24,.18), 0 8px 24px -12px rgba(125,93,24,.22);
  --shadow-logo: 0 18px 40px rgba(25, 21, 16, 0.22);
}

:root[data-theme="dark"] {
  color-scheme: dark light;
  --canvas: #0E0C09; --surface: #191510; --surface-2: #221C13; --surface-3: #2C251A;
  --surface-2-a: rgba(34, 28, 19, 0.86);
  --overlay: rgba(9, 7, 4, 0.62);
  --canvas-grad: radial-gradient(ellipse 120% 60% at 50% -10%, var(--surface) 0%, var(--canvas) 62%);
  --border-subtle: #2A231A; --border: #3B3225;
  --border-strong: #7A6C58;
  --text: #E8E4DA;
  --text-2: #A69E8D; --text-3: #8D8574; --text-placeholder: rgba(232, 228, 218, 0.50);
  --accent: #C9A130;
  --accent-hover: #E3C56B; --accent-line: rgba(201, 161, 48, 0.34);
  --accent-bg: rgba(201, 161, 48, 0.12); --accent-ring: rgba(201, 161, 48, 0.24);
  --text-on-accent: #0E0C09;
  --danger: #E0736A;
  --danger-hover: #EE8A81; --danger-fill: #8E2029; --danger-fill-hover: #A5252F;
  --danger-bg: rgba(224, 115, 106, 0.12); --danger-border: rgba(224, 115, 106, 0.38);
  --success: #6FAE8C;
  --success-bg: rgba(111, 174, 140, 0.12); --success-border: rgba(111, 174, 140, 0.34);
  --shadow-1: inset 0 1px 0 rgba(255,252,244,.045), 0 1px 2px rgba(0,0,0,.60);
  --shadow-2: 0 -1px 0 rgba(255,252,244,.06), 0 -12px 32px rgba(0,0,0,.70);
  --shadow-3: 0 0 0 1px rgba(255,252,244,.06), 0 16px 40px -8px rgba(0,0,0,.80);
  --glow-accent: 0 0 0 1px rgba(201,161,48,.32), 0 8px 28px -12px rgba(201,161,48,.35);
  --shadow-logo: 0 18px 40px rgba(0, 0, 0, 0.55);
}


/* =====================================================================
   4. RESET + BASE
   ===================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;            /* dvh second: pre-15.4 Safari keeps vh */
  background-color: var(--canvas);
  background-image: var(--canvas-grad);
  background-attachment: fixed;
  color: var(--text);
  font-family: var(--font);
  font-size: var(--fs-md);
  font-weight: var(--fw-regular);
  font-stretch: var(--wd-normal);
  line-height: var(--lh-normal);
  letter-spacing: var(--track-normal);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-variant-numeric: slashed-zero;
}

h1, h2, h3, h4, p, figure, fieldset { margin: 0; }
fieldset { border: 0; padding: 0; min-width: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
img, svg, video { display: block; max-width: 100%; height: auto; }
button, input, select, textarea { font: inherit; color: inherit; margin: 0; }
button { background: none; border: 0; padding: 0; }
table { border-collapse: collapse; width: 100%; }

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
@media (hover: hover) {
  a:hover { color: var(--accent-hover); }
}

::selection { background: var(--accent-bg); }
::placeholder { color: var(--text-placeholder); opacity: 1; }

/* Keyboard focus is the brand mark's geometry: the concentric double-rule
   of the seal. First of exactly two uses of --ring-crest. */
:focus-visible {
  outline: none;
  box-shadow: var(--ring-crest);
  border-radius: var(--r-xs);
}
:focus:not(:focus-visible) { outline: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}


/* =====================================================================
   5. LAYOUT PRIMITIVES
   ===================================================================== */

.wrap {
  width: 100%;
  max-width: var(--w-form);
  margin-inline: auto;
  padding-inline: var(--gutter);
  padding-top: calc(var(--space-lg) + var(--safe-t));
}
.wrap--wide  { max-width: var(--w-list); }
.wrap--prose { max-width: var(--w-prose); }

/* Reserves room for the fixed action bar so the last field is never
   trapped underneath it. */
.wrap--form { padding-bottom: calc(var(--actionbar-h) + var(--space-xl)); }

/* Today screen: header pinned top, button stack optically centred,
   orphan line pinned bottom. The screen ends ~180px short of the fold
   if the stack sits at the top; centring makes the emptiness read as
   deliberate rather than unfinished. */
.today {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}
.today__main {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-sm);
  padding-block: var(--space-xl);
}
.today__foot {
  padding-bottom: calc(var(--space-lg) + var(--safe-b));
}

.stack             { display: flex; flex-direction: column; }
.stack > * + *     { margin-top: var(--space-md); }
.stack--lg > * + * { margin-top: var(--space-lg); }
.stack--xl > * + * { margin-top: var(--space-xl); }

.two-up {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-sm);
}

.hidden { display: none !important; }

/* The standard clip-rect visually-hidden pattern — NOT display:none.
   Load-bearing on the two file inputs in New Card (§2.3): display:none
   breaks <label for> click-to-open-picker association on some mobile
   browsers, silently killing the primary capture path. */
.vis-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: var(--gutter); top: -100px;
  z-index: var(--z-toast);
  padding: var(--space-sm) var(--space-md);
  background: var(--surface-2);
  border-radius: var(--r-md);
  color: var(--text);
  text-decoration: none;
}
.skip-link:focus { top: calc(var(--space-sm) + var(--safe-t)); }


/* =====================================================================
   6. THE SLAB LABEL  (signature device)

   Taken from the printed label bar on a graded-card slab:
   micro-caps, wide-tracked, tabular numerals. Appears in exactly four
   places — Today's header, the confirmation header, the <details>
   summary, and list-row meta. Never decorative.
   ===================================================================== */

.slab {
  font-size: var(--fs-micro);
  font-weight: var(--fw-semibold);
  font-stretch: var(--wd-normal);
  letter-spacing: var(--track-slab);
  text-transform: uppercase;
  color: var(--accent);
  line-height: 1;
  font-variant-numeric: tabular-nums slashed-zero;
}
.slab--quiet { color: var(--text-3); }

.slabstrip {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md);
  padding-bottom: var(--space-xs);
  border-bottom: var(--bw) solid var(--accent-line);
}

.rule {
  height: var(--bw);
  border: 0;
  margin: 0;
  background: var(--border-subtle);
}
.rule--gold { background: var(--accent-line); }


/* =====================================================================
   7. TYPE UTILITIES
   ===================================================================== */

.title {
  font-size: var(--fs-3xl);
  font-weight: var(--fw-semibold);
  font-stretch: var(--wd-wide);
  letter-spacing: var(--track-display);
  line-height: var(--lh-snug);
  text-wrap: balance;
}
.title--hero {
  font-size: var(--fs-4xl);
  font-stretch: var(--wd-expanded);
  line-height: var(--lh-tight);
}

.lede {
  font-size: var(--fs-lg);
  line-height: var(--lh-relaxed);
  color: var(--text-2);
  text-wrap: pretty;
}

.meta {
  font-size: var(--fs-xs);
  color: var(--text-3);
  font-variant-numeric: tabular-nums slashed-zero;
}

.quiet {
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  color: var(--text-3);
  text-wrap: pretty;
}

.num {
  font-variant-numeric: tabular-nums slashed-zero;
  font-stretch: var(--wd-expanded);
  letter-spacing: var(--track-tight);
}


/* =====================================================================
   8. BUTTONS
   ===================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  min-height: var(--tap-comfortable);
  padding-inline: var(--space-lg);
  border: var(--bw) solid transparent;
  border-radius: var(--r-md);
  background: transparent;
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  font-stretch: var(--wd-normal);
  letter-spacing: var(--track-tight);
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  touch-action: manipulation;              /* kills the 300ms dbl-tap delay */
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color     var(--dur-fast) var(--ease-out),
              color            var(--dur-fast) var(--ease-out),
              transform        var(--dur-instant) var(--ease-out),
              box-shadow       var(--dur-fast) var(--ease-out);
}
.btn:active { transform: translateY(1px); }
.btn[disabled] { cursor: default; opacity: 0.62; transform: none; }

/* PRIMARY — maximum ink. Inverts automatically with the theme:
   bone-on-black in dark (15.39:1), black-on-white in light (16.77:1).
   Never gold: a 104px field of #C9A130 reads as costume jewellery, and
   a big bright accent block is the generic dark-admin answer. */
.btn--primary {
  background: var(--text);
  color: var(--canvas);
}
@media (hover: hover) {
  .btn--primary:hover { box-shadow: var(--glow-accent); }
}

/* Today's "+ Add a Card". Full width, thumb-height, dominant. */
.btn--hero {
  width: 100%;
  min-height: var(--tap-hero);
  border-radius: var(--r-lg);
  font-size: var(--fs-xl);
  font-stretch: var(--wd-wide);
  letter-spacing: var(--track-display);
}

/* The fixed-bar PUBLISH. Uppercase + slab tracking so it reads as the
   terminal action, not just another button. */
.btn--action {
  min-height: var(--tap-action);
  border-radius: var(--r-md);
  font-size: var(--fs-lg);
  font-stretch: var(--wd-wide);
  letter-spacing: var(--track-slab);
  text-transform: uppercase;
}

.btn--ghost {
  border-color: var(--border);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-1);
}
.btn--ghost:active { background: var(--surface-3); }
@media (hover: hover) {
  .btn--ghost:hover {
    border-color: var(--border-strong);
    background: var(--surface-2);
  }
}

/* QUIET — "Save for later", "Add another card", "Start fresh".
   Deliberately not a peer of primary. */
.btn--quiet {
  min-height: var(--tap-min);
  padding-inline: var(--space-sm);
  color: var(--text-2);
  font-weight: var(--fw-medium);
  letter-spacing: var(--track-normal);
}
@media (hover: hover) {
  .btn--quiet:hover { color: var(--text); text-decoration: underline; }
}

/* DANGER.
   NOTE: there is no filled danger button in this system, on purpose.
   Rule 3 removes every confirmation dialog, so destructive actions are
   quiet and reversible. A loud red fill would be theatre for an action
   that undoes itself for 30 seconds. */
.btn--danger {
  color: var(--danger);
  border-color: var(--danger-border);
}
.btn--danger:active { background: var(--danger-bg); }
@media (hover: hover) {
  .btn--danger:hover {
    background: var(--danger-bg);
    border-color: var(--danger);
  }
}

.btn--full  { width: 100%; }
.btn--block { display: flex; width: 100%; }

/* --- In-flight state (Rule 10) --- */
.btn[aria-busy="true"] {
  cursor: progress;
  pointer-events: none;
  opacity: 0.78;
}
.btn[aria-busy="true"]::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  height: 2px; width: 40%;
  background: var(--accent);
  animation: kj-indeterminate 1.1s var(--ease-in-out) infinite;
}
@keyframes kj-indeterminate {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(250%); }
}
@media (prefers-reduced-motion: reduce) {
  .btn[aria-busy="true"]::after {
    animation: none;
    width: 100%;
    opacity: 0.5;
  }
}


/* =====================================================================
   9. TILES  (capture tiles on New Card; nav tiles on Today)
   ===================================================================== */

.tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-height: 112px;
  padding: var(--space-md);
  border: var(--bw) solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-1);
  color: var(--text);
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color     var(--dur-fast) var(--ease-out),
              transform        var(--dur-instant) var(--ease-out);
}
.tile:active { background: var(--surface-3); transform: translateY(1px); }
@media (hover: hover) {
  .tile:hover { border-color: var(--border-strong); }
}

.tile--short    { min-height: 64px; flex-direction: row; gap: var(--space-xs); }
.tile--shortest { min-height: 52px; flex-direction: row; gap: var(--space-xs); }

.tile__glyph {
  width: 24px; height: 24px;
  color: var(--accent);
  flex: 0 0 auto;
}
.tile__label {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--track-tight);
}
.tile__sub {
  font-size: var(--fs-xs);
  color: var(--text-3);
}

.card {
  padding: var(--space-lg);
  border: var(--bw) solid var(--border-subtle);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}


/* =====================================================================
   10. STATS + CHIPS
   ===================================================================== */

.stat {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-height: var(--tap-min);
  text-decoration: none;
  color: inherit;
}
@media (hover: hover) {
  .stat:hover .stat__value { color: var(--accent); }
}

/* .stat__label carries .slab alongside it in markup. */
.stat__value {
  font-size: var(--fs-4xl);
  font-weight: var(--fw-semibold);
  font-stretch: var(--wd-expanded);
  letter-spacing: var(--track-display);
  line-height: 1;
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--text);
  transition: color var(--dur-fast) var(--ease-out);
}

.chip {
  display: inline-flex;
  align-items: center;
  padding: 3px 9px;
  border: var(--bw) solid var(--border);
  border-radius: var(--r-pill);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-3);
  white-space: nowrap;
  line-height: 1.4;
}
.chip--live  { color: var(--accent); border-color: var(--accent-line); }
.chip--draft { color: var(--text-3); }
.chip--count {
  color: var(--accent);
  border-color: var(--accent-line);
  font-variant-numeric: tabular-nums slashed-zero;
}
/* Archived rows are already dimmed by .row--archived; a red chip on top
   of that is noise. Removed deliberately. */


/* =====================================================================
   11. FORM FIELDS
   ===================================================================== */

.field { display: block; }
.field + .field { margin-top: var(--space-lg); }

/* 14px, NOT the 11px slab treatment. The slab micro-label is for
   METADATA ONLY — a required field label at 11px, on a phone, in a
   hurry, is a failure dressed as sophistication. */
.field__label {
  display: block;
  margin-bottom: var(--space-xs);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-2);
}

.field__hint {
  margin-top: var(--space-xs);
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  color: var(--text-3);
  text-wrap: pretty;
}

.field__error {
  margin-top: var(--space-xs);
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  color: var(--danger);
}

.input,
.textarea,
.select {
  display: block;
  width: 100%;
  min-height: var(--tap-comfortable);
  padding: 14px var(--space-md);
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--r-md);
  background: var(--surface-2);
  color: var(--text);
  font-size: var(--fs-md);        /* NEVER lower — iOS zooms below 16px */
  line-height: var(--lh-normal);
  appearance: none;
  -webkit-appearance: none;
  transition: border-color     var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out),
              box-shadow       var(--dur-fast) var(--ease-out);
}

.input:focus,
.textarea:focus,
.select:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--surface-3);
  box-shadow: 0 0 0 3px var(--accent-ring);
}

.textarea {
  min-height: 104px;
  resize: vertical;
}

.input--invalid,
.textarea--invalid {
  border-color: var(--danger);
}

/* --- Prefix/suffix chrome INSIDE the field's own border — the "$" cell.
       A flex wrapper that owns the border, with a borderless input inside,
       reads machined. An absolutely-positioned floating "$" reads bolted
       on. --- */
.field__group {
  display: flex;
  align-items: stretch;
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--r-md);
  background: var(--surface-2);
  overflow: hidden;
  transition: border-color var(--dur-fast) var(--ease-out),
              box-shadow   var(--dur-fast) var(--ease-out);
}
.field__group:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
.field__group .input {
  border: 0;
  background: transparent;
  box-shadow: none;
  border-radius: 0;
  padding-left: var(--space-2xs);
  font-variant-numeric: tabular-nums slashed-zero;
}
.field__group .input:focus {
  background: transparent;
  box-shadow: none;
}
.field__affix {
  display: flex;
  align-items: center;
  padding-left: var(--space-md);
  color: var(--text-3);
  font-variant-numeric: tabular-nums slashed-zero;
  user-select: none;
  -webkit-user-select: none;
}

/* --- Select chevron.
       Drawn from border-color so it re-themes for free. An SVG data-URI
       background cannot read a CSS custom property, which is why the
       usual approach breaks in dark mode. --- */
.field__select { position: relative; }
.field__select .select { padding-right: var(--space-2xl); }
.field__select::after {
  content: "";
  position: absolute;
  right: 20px; top: 50%;
  width: 7px; height: 7px;
  border: solid var(--text-2);
  border-width: 0 1.5px 1.5px 0;
  transform: translateY(-70%) rotate(45deg);
  pointer-events: none;
}

/* --- Checkbox (list-view bulk select only) --- */
.check {
  flex: 0 0 auto;
  width: 22px; height: 22px;
  border: 1.5px solid var(--border-strong);
  border-radius: var(--r-xs);
  background: var(--surface-2);
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  position: relative;
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color     var(--dur-fast) var(--ease-out);
}
.check:checked {
  background: var(--accent);
  border-color: var(--accent);
}
.check:checked::after {
  content: "";
  position: absolute;
  left: 6px; top: 2px;
  width: 6px; height: 11px;
  border: solid var(--text-on-accent);
  border-width: 0 2px 2px 0;
  transform: rotate(40deg);
}


/* =====================================================================
   12. <details> DISCLOSURE

   The one structural device standing between this admin and a worse
   off-the-shelf CMS. 11 of the 18 card fields live in here, and it is
   never rendered with the `open` attribute — not on load, not after a
   restore, not when it holds values.
   ===================================================================== */

.disclose {
  border-top: var(--bw) solid var(--border-subtle);
  border-bottom: var(--bw) solid var(--border-subtle);
  margin-block: var(--space-xl);
}

.disclose__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  min-height: var(--tap-comfortable);
  padding-block: var(--space-sm);
  list-style: none;
  cursor: pointer;
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--track-tight);
  color: var(--text-2);
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--dur-fast) var(--ease-out);
}
.disclose__summary::-webkit-details-marker { display: none; }
.disclose__summary::marker { content: ""; }
@media (hover: hover) {
  .disclose__summary:hover { color: var(--text); }
}

.disclose__meta {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex: 0 0 auto;
}

.disclose__chev {
  width: 8px; height: 8px;
  border: solid var(--text-3);
  border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform var(--dur-fast) var(--ease-out);
  flex: 0 0 auto;
}
.disclose[open] .disclose__chev {
  transform: rotate(-135deg) translate(-2px, -2px);
}

.disclose__body {
  padding-bottom: var(--space-lg);
}


/* =====================================================================
   13. BANNERS
   ===================================================================== */

.banner {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  border: var(--bw) solid var(--border-subtle);
  border-left-width: 3px;
  border-radius: var(--r-md);
  background: var(--surface);
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  color: var(--text);
  text-wrap: pretty;
}

.banner--success {
  border-color: var(--success-border);
  border-left-color: var(--success);
  background: var(--success-bg);
}
.banner--error {
  border-color: var(--danger-border);
  border-left-color: var(--danger);
  background: var(--danger-bg);
}
.banner--undo,
.banner--restore {
  border-color: var(--accent-line);
  border-left-color: var(--accent);
  background: var(--accent-bg);
}

.banner__body {
  flex: 1 1 auto;
  min-width: 0;
}
.banner__body strong {
  font-weight: var(--fw-semibold);
}

.banner__actions {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  flex: 0 0 auto;
}
.banner__actions form { display: contents; }

.banner__timer {
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--text-3);
  font-size: var(--fs-xs);
  min-width: 2.5ch;
  text-align: right;
}

.banner + .banner { margin-top: var(--space-sm); }


/* =====================================================================
   14. PHOTO GRID
   ===================================================================== */

.photos {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
  gap: var(--space-xs);
  margin-top: var(--space-md);
}

/* 5:7 is the actual proportion of a trading card (2.5" x 3.5").
   A square thumbnail is the default answer and it crops every card. */
.photo {
  position: relative;
  aspect-ratio: 5 / 7;
  border: var(--bw) solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface-3);
  overflow: hidden;
}
.photo__img { width: 100%; height: 100%; object-fit: cover; }

/* A post's cover crops 3:2, so its admin tile must too — otherwise the one
   surface the screenshot suite covers would squash exactly the way the
   public pages just stopped doing. */
.photos[data-kind="post"] .photo { aspect-ratio: 3 / 2; }

/* Rule 2, in pure CSS. He never chooses a cover; the first photo IS the
   cover, and the badge tells him the rule instead of asking him a question.
   Remove the first photo and the badge moves. Zero JS. */
.photos .photo:first-child::after {
  content: "Cover";
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 3px 0;
  background: var(--overlay);
  color: var(--accent);
  font-size: var(--fs-micro);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--track-slab);
  text-transform: uppercase;
  text-align: center;
}

.photo__bar {                    /* upload progress */
  position: absolute; left: 0; bottom: 0; height: 2px;
  background: var(--accent);
  width: 0;
  transition: width var(--dur) var(--ease-out), opacity var(--dur-slow) var(--ease-out);
}
.photo[data-state="done"] .photo__bar { width: 100%; opacity: 0; }
.photo[data-state="failed"] { border-color: var(--danger-border); }
.photo[data-state="failed"] .photo__bar { width: 100%; background: var(--danger); opacity: 1; }

/* upload.js's live-percentage bar is a real <progress> element (never
   el.style.width — that's an inline-style write and this admin's CSP has
   no style-src 'unsafe-inline'). Overriding the base .photo__bar's
   width:0/background here is deliberate: a <progress> track needs full
   width for its native value/max fraction to read correctly. */
progress.photo__bar {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  border: 0;
  background: var(--border-subtle);
}
progress.photo__bar::-webkit-progress-bar { background: transparent; }
progress.photo__bar::-webkit-progress-value { background: var(--accent); transition: width var(--dur) var(--ease-out); }
progress.photo__bar::-moz-progress-bar { background: var(--accent); }

.photo[data-state="queued"] .photo__img,
.photo[data-state="preparing"] .photo__img,
.photo[data-state="uploading"] .photo__img { opacity: 0.55; }

.photo__note {                   /* "Will send with the card." */
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 3px 4px;
  background: var(--overlay);
  color: var(--text);
  font-size: 10px;
  line-height: 1.2;
  text-align: center;
}

/* 44px hit area, 22px glyph. */
.photo__remove {
  position: absolute; top: 0; right: 0;
  width: var(--tap-min); height: var(--tap-min);
  display: grid; place-items: center;
  border: 0; background: transparent; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.photo__remove::before {
  content: "";
  width: 22px; height: 22px;
  border-radius: var(--r-pill);
  background: var(--overlay);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.18);
}
.photo__remove::after {
  content: "×";
  position: absolute;
  color: var(--text);
  font-size: 15px; line-height: 1;
}

/* Opposite corner from .photo__remove — same 44px hit area, 22px glyph. */
.photo__crop {
  position: absolute; top: 0; left: 0;
  width: var(--tap-min); height: var(--tap-min);
  display: grid; place-items: center;
  border: 0; background: transparent; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.photo__crop::before {
  content: "";
  width: 22px; height: 22px;
  border-radius: var(--r-pill);
  background: var(--overlay);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.18);
}
.photo__crop::after {
  content: "";
  position: absolute;
  width: 11px; height: 11px;
  border: 1.5px solid var(--text);
  border-radius: 2px;
}

/* Crop/zoom editor — expands in place below the tapped tile (#20b,
   admin.ts's handleSetMediaCrop). The stage and every preview move the
   photo by setting its width/height attributes and scrolling an
   overflow:hidden box, NEVER el.style — same CSP constraint as
   .photo__bar above, this admin has no style-src 'unsafe-inline'. */
.cropedit {
  grid-column: 1 / -1;
  margin-top: var(--space-sm);
  padding: var(--space-md);
  border: var(--bw) solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface-2);
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}
.cropedit__stage {
  position: relative;
  width: 100%;
  max-width: 320px;
  margin-inline: auto;
  overflow: hidden;
  touch-action: none;
  border-radius: var(--r-sm);
  background: var(--surface-3);
  cursor: grab;
}
.cropedit__stage:active { cursor: grabbing; }
.cropedit__ratio--57 { aspect-ratio: 5 / 7; }
.cropedit__ratio--32 { aspect-ratio: 3 / 2; }
.cropedit__img, .cropedit__pimg {
  display: block;
  max-width: none;   /* the whole mechanism needs room to zoom past the box */
}
.cropedit__zoom { width: 100%; max-width: 320px; margin-inline: auto; accent-color: var(--accent); }
.cropedit__warn {
  margin: 0; text-align: center;
  font-size: var(--fs-sm); color: var(--text-3);
}
.cropedit__warn.hidden { display: none; }
.cropedit__previews {
  display: flex;
  justify-content: center;
  gap: var(--space-md);
}
.cropedit__preview {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2xs);
}
.cropedit__pstage {
  position: relative;
  width: 64px;
  overflow: hidden;
  border-radius: var(--r-xs);
  border: var(--bw) solid var(--border);
  background: var(--surface-3);
}
.cropedit__plabel { font-size: var(--fs-micro); color: var(--text-3); letter-spacing: var(--track-label); text-transform: uppercase; }
.cropedit__actions { display: flex; justify-content: flex-end; gap: var(--space-xs); }

/* Slab thumbnail — confirmation screen only. Second and last use of the
   crest ring. */
.slabshot {
  width: 148px;
  aspect-ratio: 5 / 7;
  margin-inline: auto;
  border-radius: var(--r-sm);
  overflow: hidden;
  background: var(--surface-3);
  box-shadow: 0 0 0 1px var(--border),
              0 0 0 5px var(--canvas),
              0 0 0 6px var(--accent-line),
              var(--shadow-3);
}


/* =====================================================================
   15. STICKY ACTION BAR
   ===================================================================== */

/* FIXED, not sticky. `position: sticky; bottom: 0` only pins when the
   scroll container is taller than the viewport — on a short form it
   drops to the end of the document and stops being "always visible",
   which is the one thing this bar has to be. */
.actionbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: var(--z-sticky);
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-md) var(--gutter) calc(var(--space-md) + var(--safe-b));
  background: var(--surface-2-a);
  backdrop-filter: blur(14px) saturate(1.25);
  -webkit-backdrop-filter: blur(14px) saturate(1.25);
  border-top: var(--bw) solid var(--border);
  box-shadow: var(--shadow-2);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .actionbar { background: var(--surface-2); }
}
.actionbar__inner {
  display: flex; align-items: center; gap: var(--space-sm);
  width: 100%; max-width: var(--w-form); margin-inline: auto;
}
/* Ghost LEFT, primary RIGHT: a right thumb rests right-of-centre, so the
   reversible action sits where mis-taps land and PUBLISH sits where
   deliberate taps land. */
.actionbar__primary { flex: 1 1 auto; }
.actionbar__quiet   { flex: 0 0 auto; }


/* =====================================================================
   16. LIST VIEW (desktop-first)
   ===================================================================== */

.filterbar {
  display: flex; flex-wrap: wrap; gap: var(--space-sm);
  align-items: flex-end;
  padding-bottom: var(--space-md);
  border-bottom: var(--bw) solid var(--border-subtle);
}
.filterbar__search { flex: 1 1 260px; }

.row {
  display: grid;
  grid-template-columns: 40px 1fr auto auto;
  align-items: center;
  gap: var(--space-md);
  min-height: 64px;
  padding-block: var(--space-sm);
  border-bottom: var(--bw) solid var(--border-subtle);
}
.row--archived { opacity: 0.55; }
.row__thumb { width: 40px; aspect-ratio: 5/7; border-radius: var(--r-xs);
              overflow: hidden; background: var(--surface-3); }
.row__title { font-weight: var(--fw-medium); letter-spacing: var(--track-tight); }
.row__meta  { font-size: var(--fs-xs); color: var(--text-3);
              font-variant-numeric: tabular-nums slashed-zero; }
.row__actions { display: flex; gap: var(--space-xs); }

.pager {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-md);
  padding-top: var(--space-lg);
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--text-2);
  font-size: var(--fs-sm);
}

.empty {
  padding: var(--space-3xl) var(--space-lg);
  text-align: center;
  color: var(--text-2);
}


/* =====================================================================
   17. CONFIRMATION — the one orchestrated moment
   ===================================================================== */

.reveal > * { animation: kj-rise var(--dur-slow) var(--ease-out) both; }
.reveal > *:nth-child(1) { animation-delay: 0ms; }
.reveal > *:nth-child(2) { animation-delay: 70ms; }
.reveal > *:nth-child(3) { animation-delay: 140ms; }
.reveal > *:nth-child(4) { animation-delay: 210ms; }
.reveal > *:nth-child(5) { animation-delay: 280ms; }
.reveal .slabshot { animation: kj-seat var(--dur-slower) var(--ease-out) both; }

@keyframes kj-rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes kj-seat { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: none; } }

.urlbox {
  padding: var(--space-sm) var(--space-md);
  border: var(--bw) solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  color: var(--text-2);
  word-break: break-all;
  text-align: left;
}


/* =====================================================================
   18. TRIX EDITOR (Post screen only)

   The vendored trix-2.0.8.css supplies the toolbar/editor structure; this
   re-themes it so it doesn't render as a light-mode-only widget dropped
   onto a dark admin. Load-order note: admin.ts's adminPage() now injects
   TRIX_CSS_LINK (screens.ts) into <head> BEFORE this stylesheet's own
   <link> — every selector below shares its exact specificity with a
   vendor rule targeting the same element, so this file has to load LAST
   to win the tie. It used to lose silently: the vendor CSS was emitted
   inline in the page body (after this file's <head> link), which made it
   load later and override every rule below with its hardcoded light-mode
   colors (near-black button text, light-gray borders, a light-blue active
   state, and #eee/#666/#333 inside the editable content itself) — the
   actual cause of "hard to read," not a deliberate palette choice.
   ===================================================================== */

.trix-content {
  display: block;
  min-height: 200px;
  padding: var(--space-md);
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--r-md);
  background: var(--surface-2);
  color: var(--text);
  font-size: var(--fs-md);
  line-height: var(--lh-relaxed);
}
.trix-content:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}

/* --- Toolbar chrome --- */
trix-toolbar .trix-button-row {
  border: var(--bw) solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  margin-bottom: var(--space-sm);
}
trix-toolbar .trix-button-group {
  border-color: var(--border);
}
trix-toolbar .trix-button {
  color: var(--text-2);
  border-bottom-color: var(--border-subtle);
}
trix-toolbar .trix-button:not(:first-child) {
  border-left-color: var(--border-subtle);
}
trix-toolbar .trix-button.trix-active {
  background: var(--accent-bg);
  color: var(--accent);
}
trix-toolbar .trix-button:disabled {
  color: var(--text-3);
  opacity: 0.4;
}
/* Every toolbar icon (bold, italic, heading, link, attach, ...) is a
   hardcoded black SVG baked into background-image — no currentColor, so
   `color` can never reach it. Inverting it is the only way to make a
   black-stroke icon visible against this admin's dark surface; the
   surrounding opacity rules (vendor CSS, unchanged) still handle
   hover/active/disabled emphasis on top of the inverted color. Dark is
   this admin's default (mirrors §2/§3's own token pattern exactly): invert
   by default, cancel it under an actual light appearance via higher
   specificity (:root-qualified), regardless of load order. */
trix-toolbar .trix-button--icon::before {
  filter: invert(1);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) trix-toolbar .trix-button--icon::before {
    filter: none;
  }
}
:root[data-theme="light"] trix-toolbar .trix-button--icon::before {
  filter: none;
}
:root[data-theme="dark"] trix-toolbar .trix-button--icon::before {
  filter: invert(1);
}

/* --- Dialogs (the link-insert popover, attachment caption prompt) --- */
trix-toolbar .trix-dialog {
  background: var(--surface-2);
  color: var(--text);
  box-shadow: var(--shadow-3);
  border-top-color: var(--accent-line);
}
trix-toolbar .trix-input--dialog {
  border-color: var(--border-strong);
  background-color: var(--surface);
  color: var(--text);
}

/* --- In-editor attachment toolbar (the small overlay that appears on a
   selected image — remove/caption controls) — floats directly over the
   editor's own surface-2 background, so it needs the same treatment as
   the main toolbar, not the vendor's #666-on-transparent. --- */
trix-editor .trix-button {
  color: var(--text-2);
}
trix-editor .trix-button:not(:first-child) {
  border-left-color: var(--border-subtle);
}
trix-editor .trix-button.trix-active {
  background: var(--accent-bg);
}

/* --- Rendered content inside the editable area itself — this is what's
   actually on screen while composing a post, not just chrome around it --- */
.trix-content pre {
  background-color: var(--surface-3);
  color: var(--text);
}
.trix-content blockquote {
  border-color: var(--border);
}
.trix-content .attachment--preview .attachment__caption {
  color: var(--text-2);
}
.trix-content .attachment--file {
  color: var(--text);
  border-color: var(--border);
}


/* =====================================================================
   PUBLIC SITE TOKENS — exactly two new custom properties (everything else
   below reuses §2-3's existing ramp: no new colors, elevation, spacing,
   radii, durations, or keyframes).
   ===================================================================== */

:root {
  /* Sticky-nav offset. Mobile's .sitenav row (44px) is sticky; the 52px
     brandmark row above it is NOT, so --nav-h tracks only the sticky row.
     Used by the sticky gallery `top`, [id] scroll-margin-top, and the
     hero's layout. */
  --nav-h: 44px;
  /* --track-display (-0.022em) is wrong-signed for all-caps text;
     --track-normal (0) is acceptable but not ideal for wide-tracked caps.
     Used by the hero eyebrow, nav labels, and .btn--action-weight caps. */
  --track-caps: 0.02em;
}
@media (min-width: 900px) {
  :root {
    /* >=900px's header is ONE sticky row (mark + nav combined, unlike
       mobile's two-row split) — 64px is that row's actual height:
       32-36px crest plus padding. */
    --nav-h: 64px;
  }
}


/* =====================================================================
   19. PUBLIC HEADER + NAV
   ===================================================================== */

.sitehead {
  position: relative;
}
.brandmark {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 52px;
  border-bottom: var(--bw) solid var(--border-subtle);
}
.brandmark__link {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}
.brandmark__link img { border-radius: var(--r-xs); }

.sitenav {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 44px;
  padding-inline: var(--gutter);
  background: var(--surface-2-a);
  backdrop-filter: blur(14px) saturate(1.25);
  -webkit-backdrop-filter: blur(14px) saturate(1.25);
  border-bottom: var(--bw) solid var(--border);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .sitenav { background: var(--surface-2); }
}
/* .sitenav__brand (crest + wordmark inside the sticky nav) is desktop-only
   — mobile already shows the crest in the separate 52px .brandmark row
   above, so showing it a second time here would duplicate it. */
.sitenav__brand { display: none; }
.sitenav__links {
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-xs);
}
.sitenav__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-2);
  text-decoration: none;
  border-bottom: 2px solid transparent;
}
@media (hover: hover) {
  .sitenav__link:hover { color: var(--text); }
}
.sitenav__link[aria-current="page"] {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

.announce {
  padding: var(--space-xs) var(--gutter);
  text-align: center;
  font-size: var(--fs-sm);
  color: var(--text-2);
  background: var(--surface);
  border-bottom: var(--bw) solid var(--border-subtle);
}

@media (min-width: 900px) {
  .brandmark { display: none; }
  .sitenav {
    height: var(--nav-h);
    justify-content: flex-start;
    gap: var(--space-2xl);
  }
  .sitenav__brand {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    margin-right: auto;
    text-decoration: none;
  }
  .sitenav__brand img { border-radius: var(--r-xs); }
  .sitenav__brand span {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    letter-spacing: var(--track-label);
    text-transform: uppercase;
    color: var(--text);
  }
  .sitenav__links {
    flex: 0 0 auto;
    justify-content: flex-start;
    gap: var(--space-xl);
  }
}


/* =====================================================================
   20. PUBLIC PAGE FURNITURE
   ===================================================================== */

[id] { scroll-margin-top: calc(var(--nav-h) + var(--space-md)); }

.section { padding-block: var(--space-3xl); }
@media (min-width: 900px) {
  .section { padding-block: var(--space-4xl); }
}

.pagehead {
  padding-bottom: var(--space-lg);
  margin-bottom: var(--space-xl);
  border-bottom: var(--bw) solid var(--accent-line);
}
.pagehead .title { margin-bottom: var(--space-xs); }

.backlink {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text-2);
  text-decoration: none;
}
@media (hover: hover) {
  .backlink:hover { color: var(--text); text-decoration: underline; }
}

.filters {
  display: flex;
  gap: var(--space-xs);
  overflow-x: auto;
  padding-bottom: var(--space-md);
  margin-bottom: var(--space-lg);
  border-bottom: var(--bw) solid var(--border-subtle);
  -webkit-overflow-scrolling: touch;
}
.filter {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  padding-inline: var(--space-md);
  border: var(--bw) solid var(--border);
  border-radius: var(--r-pill);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-2);
  text-decoration: none;
  white-space: nowrap;
}
@media (hover: hover) {
  .filter:hover { border-color: var(--border-strong); color: var(--text); }
}
.filter.chip--live {
  color: var(--accent);
  border-color: var(--accent-line);
  background: var(--accent-bg);
}


/* =====================================================================
   21. HERO
   ===================================================================== */

.hero {
  display: grid;
  gap: var(--space-xl);
  align-items: center;
}
.hero__media {
  order: -1;
}
.hero__title {
  font-size: clamp(var(--fs-3xl), 5.5vw, var(--fs-5xl));
}
.hero__eyebrow {
  letter-spacing: var(--track-caps);
}
.hero--statement .hero__title {
  font-stretch: var(--wd-expanded);
}
/* Fills the statement hero's media slot when there's no product to show
   yet (empty catalog) — same crest asset the coming-soon page uses. */
.hero__media--brand {
  display: flex;
  align-items: center;
  justify-content: center;
}
.hero__crest {
  width: clamp(160px, 40vw, 280px);
  height: auto;
  margin-inline: auto;
  filter: drop-shadow(var(--shadow-logo));
}
@media (min-width: 900px) {
  .hero {
    grid-template-columns: 1fr minmax(320px, 420px);
    gap: var(--space-4xl);
  }
  .hero__media { order: 0; }
}


/* =====================================================================
   22. SLAB FRAME + PRODUCT CARD
   ===================================================================== */

.slabframe {
  border: var(--bw) solid var(--border);
  border-radius: var(--r-md);
  box-shadow: inset 0 0 0 1px var(--accent-line);
  background: var(--surface);
  overflow: hidden;
}
.slabframe__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-xs);
  min-height: 26px;
  padding-inline: var(--space-sm);
  font-size: var(--fs-micro);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--track-slab);
  text-transform: uppercase;
  color: var(--accent);
  font-variant-numeric: tabular-nums slashed-zero;
  border-bottom: var(--bw) solid var(--accent-line);
}
.slabframe__img {
  display: block;
  width: 100%;
  aspect-ratio: 5 / 7;
  object-fit: cover;
  background: var(--surface-3);
}
.slabframe--natural .slabframe__img { aspect-ratio: auto; }
/* A coverless entry's placeholder is a bare <div>, not an <img> with
   intrinsic dimensions — aspect-ratio:auto (from --natural above) has
   nothing to size itself against and collapses to zero height. Forces the
   5:7 ratio back regardless of --natural. */
.slabframe__img--placeholder { aspect-ratio: 5 / 7 !important; }

.pgrid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-md);
}
@media (min-width: 640px) {
  .pgrid { grid-template-columns: repeat(3, 1fr); }
}
@media (min-width: 1024px) {
  .pgrid { grid-template-columns: repeat(4, 1fr); }
}

.pcard { display: block; text-decoration: none; color: inherit; }
.pcard__meta {
  margin-top: var(--space-sm);
  font-size: var(--fs-xs);
  color: var(--text-3);
}
.pcard__title {
  margin-top: var(--space-2xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--track-tight);
  color: var(--text);
}
.pcard__price {
  margin-top: var(--space-2xs);
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--text-2);
}
.pcard--sold .slabframe__img {
  filter: saturate(0.35);
  opacity: 0.82;
}
/* The brand oxblood's only public use — there are no destructive actions
   on a public site, so --danger reused here keeps red entirely out of
   navigation, matching Anthony's rejection of the reference's red nav bar. */
.pcard__sold-tag {
  color: var(--danger);
  border-color: var(--danger-border);
  background: var(--danger-bg);
}


/* =====================================================================
   23. ENTRY DETAIL
   ===================================================================== */

.entry {
  display: grid;
  gap: var(--space-xl);
}
@media (min-width: 900px) {
  .entry {
    grid-template-columns: minmax(320px, 460px) 1fr;
    gap: var(--space-4xl);
    align-items: start;
  }
  .entry__gallery {
    position: sticky;
    top: calc(var(--nav-h) + var(--space-lg));
  }
}
.entry__gallery-extra {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-xs);
  margin-top: var(--space-sm);
}
.entry__gallery-extra img {
  aspect-ratio: 5 / 7;
  object-fit: cover;
  border-radius: var(--r-sm);
  border: var(--bw) solid var(--border);
}

.spec {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  margin-top: var(--space-lg);
}
.spec > div {
  padding-block: var(--space-sm);
  border-top: var(--bw) solid var(--border-subtle);
}
.spec dt {
  font-size: var(--fs-micro);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--track-slab);
  text-transform: uppercase;
  color: var(--text-3);
}
.spec dd {
  margin: var(--space-2xs) 0 0;
  font-size: var(--fs-sm);
  color: var(--text);
}

.buybar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: var(--z-sticky);
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-md) var(--gutter) calc(var(--space-md) + var(--safe-b));
  background: var(--surface-2-a);
  backdrop-filter: blur(14px) saturate(1.25);
  -webkit-backdrop-filter: blur(14px) saturate(1.25);
  border-top: var(--bw) solid var(--border);
  box-shadow: var(--shadow-2);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .buybar { background: var(--surface-2); }
}
.buybar__recap { flex: 1 1 auto; min-width: 0; }
.buybar__recap .title { font-size: var(--fs-md); }
/* On body, not .wrap--detail: the buybar is position:fixed and the footer
   follows .entry in the DOM, so the reserved space has to sit at the true
   end of the document (after the footer) or the fixed bar still covers the
   footer once scrolled to the bottom of the page. */
body.has-buybar { padding-bottom: calc(var(--actionbar-h) + var(--space-xl)); }
@media (min-width: 900px) {
  .buybar {
    position: static;
    background: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-top: 0;
    box-shadow: none;
    padding: var(--space-lg) 0 0;
  }
  body.has-buybar { padding-bottom: 0; }
}


/* =====================================================================
   24. ARTICLE TYPOGRAPHY
   ===================================================================== */

.article {
  max-width: var(--w-prose);
  margin-inline: auto;
  font-size: var(--fs-lg);
  line-height: var(--lh-relaxed);
}
.article > * + * { margin-top: 1em; }
.article p, .article div, .article pre, .article blockquote,
.article ul, .article ol, .article figure { max-width: 62ch; }
.article h1[data-demoted-heading],
.article h2 {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-semibold);
  font-stretch: var(--wd-wide);
  letter-spacing: var(--track-tight);
  line-height: var(--lh-snug);
  margin-top: 1.6em;
}
.article h3 {
  font-size: var(--fs-xl);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--track-tight);
  margin-top: 1.4em;
}
.article strong, .article b { font-weight: var(--fw-semibold); }
.article ul, .article ol { list-style: revert; padding-left: 1.4em; }
.article li::marker { color: var(--accent); }
.article a { text-underline-offset: 3px; }
.article blockquote {
  padding-left: var(--space-md);
  border-left: 2px solid var(--accent-line);
  color: var(--text-2);
}
.article pre {
  padding: var(--space-md);
  overflow-x: auto;
  background: var(--surface-2);
  border: var(--bw) solid var(--border-subtle);
  border-radius: var(--r-md);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--fs-sm);
}
.article img { border-radius: var(--r-sm); }
.article figure { margin: 0; }
.article figure > figcaption {
  margin-top: var(--space-xs);
  font-size: var(--fs-sm);
  color: var(--text-3);
  text-align: center;
}
.article del { color: var(--text-3); }

.postlist { display: flex; flex-direction: column; }
.postlist__row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-xs);
  padding-block: var(--space-lg);
  border-bottom: var(--bw) solid var(--border-subtle);
  text-decoration: none;
  color: inherit;
}
.postlist__excerpt {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
@media (min-width: 900px) {
  .postlist__row {
    grid-template-columns: 96px 1fr;
    align-items: center;
    gap: var(--space-lg);
  }
  .postlist__thumb {
    aspect-ratio: 3 / 2;
    object-fit: cover;
    border-radius: var(--r-sm);
  }
}


/* =====================================================================
   25. SHOWS
   ===================================================================== */

.showlist { display: flex; flex-direction: column; }
.showrow {
  display: grid;
  grid-template-columns: 64px 1fr;
  gap: var(--space-md);
  padding-block: var(--space-lg);
  border-bottom: var(--bw) solid var(--border-subtle);
  text-decoration: none;
  color: inherit;
}
.showdate {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding-right: var(--space-md);
  border-right: var(--bw) solid var(--accent-line);
  text-align: center;
}
.showdate .slab { color: var(--accent); }
.showdate .num { font-size: var(--fs-3xl); line-height: 1; }
.showrow--past .showdate { opacity: 0.55; }
.chip--countdown {
  min-width: 11ch;
  justify-content: center;
  font-variant-numeric: tabular-nums slashed-zero;
}


/* =====================================================================
   26. FOOTER
   ===================================================================== */

.sitefoot {
  border-top: var(--bw) solid var(--accent-line);
  padding-block: var(--space-3xl);
}
.sitefoot__grid {
  display: grid;
  gap: var(--space-xl);
}
.sitefoot__links {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}
.sitefoot__links a {
  color: var(--text-2);
  text-decoration: none;
  font-size: var(--fs-sm);
}
@media (hover: hover) {
  .sitefoot__links a:hover { color: var(--text); }
}
.sitefoot__sub { margin-top: var(--space-xs); }
.sitefoot__legal {
  margin-top: var(--space-xl);
  padding-top: var(--space-lg);
  border-top: var(--bw) solid var(--border-subtle);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  flex-wrap: wrap;
}
@media (min-width: 900px) {
  .sitefoot__grid { grid-template-columns: 1fr 2fr; }
  .sitefoot__links { flex-direction: row; gap: var(--space-2xl); }
}

.subscribeform__row {
  display: flex;
  gap: var(--space-xs);
  max-width: 22rem;
}
.subscribeform__row .input { flex: 1 1 auto; }
.themepick {
  min-height: var(--tap-min);
  padding: 0 var(--space-xs);
  border: 0;
  background: transparent;
  color: var(--text-3);
  font-size: var(--fs-xs);
}


/* =====================================================================
   27. COMING SOON
   ===================================================================== */

.comingsoon {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-xl) var(--gutter);
}
.comingsoon__inner {
  max-width: var(--w-prose);
  text-align: center;
}
.comingsoon__crest {
  width: clamp(200px, 34vw, 300px);
  margin-inline: auto;
  filter: drop-shadow(var(--shadow-logo));
}
.comingsoon__eyebrow {
  margin-top: var(--space-lg);
  letter-spacing: var(--track-caps);
}
.comingsoon__headline {
  margin-top: var(--space-xs);
  text-transform: uppercase;
  font-stretch: var(--wd-expanded);
  font-weight: var(--fw-semibold);
  font-size: clamp(var(--fs-3xl), 7vw, var(--fs-5xl));
  line-height: var(--lh-tight);
}
.comingsoon__pitch {
  margin-top: var(--space-md);
}
.comingsoon__form {
  margin: var(--space-xl) auto 0;
  max-width: 22rem;
}
.peek {
  margin-top: var(--space-3xl);
}
.peek .pgrid { grid-template-columns: repeat(3, 1fr); }
.peek .pcard { pointer-events: none; }
.peek .pcard__title { color: var(--text-2); }

@media (max-width: 899px) {
  body { background-attachment: scroll; }
}

/* Generic spacing utilities — style-src 'self' has no 'unsafe-inline', so
   an inline style="margin-top:..." is never an option on a public page;
   these exist for the handful of one-off vertical gaps that don't warrant
   their own named component class. */
.mt-xl  { margin-top: var(--space-xl); }
.mt-2xl { margin-top: var(--space-2xl); }
.ta-center { text-align: center; }
