/* Fegnome Dispensary Guide — web demo.

   This is not a marketing page with the app's colours on it. Every token below
   is lifted from apps/fegnome/theme/tokens.ts and theme/restyle.ts — the same
   semantic palette, the same 4pt spacing rhythm, the same type scale, the same
   6px card / 12px pill radii — so a visitor who has seen the App Store
   screenshots recognises this as the same product.

   FONTS. The app sets editorial text in Fraunces and data in JetBrains Mono,
   and so does this page: all five faces are SELF-HOSTED out of fonts/, built
   by scripts/fetch_fonts.py from the upstream repositories (googlefonts/
   fraunces 1.000 and JetBrains/JetBrainsMono v2.304), both SIL Open Font
   License 1.1, both licence texts shipped alongside in fonts/. Subset to Latin
   plus the marks the snapshot uses, woff2, one file per weight the app's text
   variants actually name — and fetched at BUILD time, so the runtime page
   still makes exactly one outbound request and it is the email POST.

   The one non-app face is Be Vietnam ExtraBold, which fegnome.com self-hosts
   for the wordmark; it is here for the wordmark and nothing else.
*/

@font-face {
  font-family: 'Be Vietnam';
  src: url('fonts/BeVietnam_800ExtraBold.ttf') format('truetype');
  font-weight: 800;
  font-style: normal;
  font-display: swap;
}

/* Fraunces — SIL OFL 1.1, fonts/OFL-Fraunces.txt. Instanced from the upstream
   variable font at opsz=14 SOFT=0 WONK=1, which is metrically identical to the
   @expo-google-fonts static the app itself loads. */
@font-face {
  font-family: 'Fraunces';
  src: url('fonts/Fraunces-300.woff2') format('woff2');
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Fraunces';
  src: url('fonts/Fraunces-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Fraunces';
  src: url('fonts/Fraunces-400-italic.woff2') format('woff2');
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

/* JetBrains Mono — SIL OFL 1.1, fonts/OFL-JetBrainsMono.txt. */
@font-face {
  font-family: 'JetBrains Mono';
  src: url('fonts/JetBrainsMono-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'JetBrains Mono';
  src: url('fonts/JetBrainsMono-500.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

:root {
  /* semantic colours — theme/tokens.ts, darkColors */
  --bg-canvas: #1a1918;
  --bg-elevated: #262524;
  --bg-recessed: #0e0d0c;
  --text-primary: #faf8f1;
  --text-body: #e8e4d7;
  --text-muted: #a8a39a;
  --text-hint: #6b6a66;
  --border-subtle: #3a3936;
  --accent: #c8924a;
  --accent-soft: #8e6532;
  --on-accent: #1a1918;
  --sativa: #cc7a66;
  --indica: #6c91b0;
  --hybrid: #7fa05a;
  --fresh: #7a9b7a;
  --stale: #a8543a;
  /* brand chrome only — never a UI accent (Wordmark.tsx) */
  --magenta: #a44364;
  --cream: #f4f0e6;

  /* 4pt rhythm — theme/restyle.ts spacing */
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px; --s8: 64px;
  --r-card: 6px; --r-pill: 12px;

  /* The app's two faces, self-hosted. The fallbacks behind them are what a
     visitor sees for the few hundred milliseconds of font-display: swap, and
     nothing else — they are not the design. */
  --serif: 'Fraunces', 'Iowan Old Style', 'Palatino Linotype', Palatino,
           Georgia, 'Times New Roman', serif;
  --mono: 'JetBrains Mono', ui-monospace, 'SF Mono', SFMono-Regular, Menlo,
          Consolas, 'Liberation Mono', monospace;
  --brand: 'Be Vietnam', system-ui, -apple-system, sans-serif;

  --col: 480px;
  --bar: 56px;
}

* { box-sizing: border-box; }

html { background: var(--bg-recessed); }

body {
  margin: 0;
  background: var(--bg-recessed);
  color: var(--text-body);
  font: 400 15px/22px var(--serif);
  -webkit-font-smoothing: antialiased;
  text-wrap: pretty;
}

/* The phone column. On a desktop the page is the app at its own width rather
   than a stretched web layout — the thing being demonstrated is a phone app. */
#device {
  max-width: var(--col);
  margin: 0 auto;
  min-height: 100vh;
  background: var(--bg-canvas);
  position: relative;
  padding-bottom: calc(var(--bar) + var(--s6));
}
@media (min-width: 520px) {
  #device {
    border-left: 1px solid var(--border-subtle);
    border-right: 1px solid var(--border-subtle);
  }
}

/* ── the type scale (theme/restyle.ts textVariants) ───────────────── */
.display-xl { font: 300 32px/32px var(--serif); color: var(--text-primary); margin: 0; }
.display-lg { font: 400 24px/26px var(--serif); color: var(--text-primary); margin: 0; }
.card-title { font: italic 400 20px/24px var(--serif); color: var(--text-primary); margin: 0; }
.body-lg { font: 400 17px/25px var(--serif); color: var(--text-body); margin: 0; }
.body-md { font: 400 15px/22px var(--serif); color: var(--text-body); margin: 0; }
.body-sm { font: 400 13px/19px var(--serif); color: var(--text-muted); margin: 0; }
.eyebrow {
  font: 500 11px/14px var(--mono);
  letter-spacing: .55px; text-transform: uppercase;
  color: var(--accent); margin: 0;
}
.eyebrow.muted, .eyebrow.hint { color: var(--text-muted); }
.data-lg { font: 500 18px/22px var(--mono); color: var(--accent); }
/* Every mono run in this page is a column of figures under a heading or beside
   a name, so the digits are locked to one width — during font-display: swap the
   fallback may not be monospaced, and a price column that reflows when the real
   face lands reads as a bug. */
.data-lg, .data-md, .data-sm, .eyebrow, .chip, .terp span, .analyte span,
.lv b, .lv span, .prow .price, .btn, .pillbtn { font-variant-numeric: tabular-nums; }
.data-md { font: 400 14px/18px var(--mono); color: var(--text-muted); }
.data-sm { font: 400 12px/16px var(--mono); color: var(--text-hint); }
p.data-lg, p.data-md, p.data-sm, span.data-lg, span.data-md, span.data-sm { margin: 0; }
b, strong { font-weight: 600; }

/* ── the persistent header (AppHeader.tsx) ────────────────────────── */
#bar .iconbtn { color: var(--text-primary); }
#bar {
  position: sticky; top: 0; z-index: 30;
  display: flex; align-items: center; justify-content: space-between; gap: var(--s2);
  padding: var(--s4) var(--s4) var(--s3);
  background: var(--bg-canvas);
  border-bottom: 1px solid var(--border-subtle);
}
.wordmark {
  font-family: var(--brand); font-weight: 800; font-size: 21px; line-height: 1;
  letter-spacing: .2px; white-space: nowrap;
}
.wordmark .fe { color: var(--magenta); }
.wordmark .gnome { color: var(--cream); }
.wordmark .sm { color: var(--text-muted); font-size: .5em; vertical-align: super; }
.barright { display: flex; align-items: center; gap: var(--s2); }

/* ── chips (Chip.tsx / StateChip.tsx / StrainTypeChip.tsx) ────────── */
.chip {
  display: inline-flex; align-items: center; gap: var(--s1);
  font: 500 11px/14px var(--mono); letter-spacing: .55px; text-transform: uppercase;
  border: 1px solid var(--border-subtle); color: var(--text-muted);
  background: none; padding: var(--s1) var(--s2); border-radius: var(--r-pill);
  cursor: default; white-space: nowrap;
}
button.chip { cursor: pointer; font-family: var(--mono); }
.chip.square { border-radius: 2px; }
.chip.accent, .chip.on { border-color: var(--accent); color: var(--accent); }
.chip.sativa { border-color: var(--sativa); color: var(--sativa); }
.chip.indica { border-color: var(--indica); color: var(--indica); }
.chip.hybrid { border-color: var(--hybrid); color: var(--hybrid); }
.chip.name { text-transform: none; letter-spacing: .2px; font-family: var(--serif); font-size: 13px; }
.chip.fresh { border-color: var(--fresh); color: var(--fresh); }

/* ── the tab bar (app/(tabs)/_layout.tsx) ─────────────────────────── */
#tabs {
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 40;
  max-width: var(--col); margin: 0 auto;
  height: var(--bar);
  display: flex; align-items: stretch;
  background: var(--bg-elevated);
  border-top: 1px solid var(--border-subtle);
}
#tabs button {
  flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: var(--s1);
  background: none; border: 0; padding: 6px 0 0; cursor: pointer;
  font: 500 8px/10px var(--mono); letter-spacing: .8px;
  color: var(--text-hint);
}
#tabs button svg { width: 20px; height: 20px; }
#tabs button.on { color: var(--accent); }

/* ── inactive controls ────────────────────────────────────────────
   The app has controls this demo has no data to honour. They are RENDERED —
   omitting them would misrepresent the product — dimmed, with a dashed edge,
   each carrying a one-line reason, and tapping one says that line out loud.
   A control that looks live and does nothing is the failure this prevents. */
.dead { opacity: .45; cursor: help; }
button.dead, a.dead { border-style: dashed; }
.deadrow { display: flex; align-items: center; gap: var(--s4); flex-wrap: wrap; }
.iconbtn {
  background: none; border: 0; padding: var(--s1); cursor: pointer;
  color: var(--text-muted); display: inline-flex; align-items: center;
  position: relative;
}
.iconbtn svg { width: 22px; height: 22px; }

/* toast (ToastHost.tsx) — where an inactive control says why */
#toast {
  position: fixed; left: 0; right: 0;
  bottom: calc(var(--bar) + var(--s3));
  z-index: 60; width: calc(100% - var(--s6)); max-width: 420px;
  margin: 0 auto;
  background: var(--bg-elevated); border: 1px solid var(--accent);
  border-radius: var(--r-card); padding: var(--s3) var(--s4);
  color: var(--text-body); font: 400 13px/19px var(--serif);
  box-shadow: 0 8px 24px rgba(0,0,0,.5);
}
#toast[hidden] { display: none; }

/* ── sections (DetailSection.tsx) ─────────────────────────────────── */
.section {
  padding: var(--s4);
  border-bottom: 1px solid var(--border-subtle);
  display: flex; flex-direction: column; gap: var(--s3);
}
.section.tight { gap: var(--s2); }
.row { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); }
.row.top { align-items: flex-start; }
.row.base { align-items: baseline; }
.col { display: flex; flex-direction: column; gap: var(--s1); min-width: 0; }
.col.grow { flex: 1 1 auto; min-width: 0; }
.col.right { align-items: flex-end; text-align: right; flex: 0 0 auto; }
.wrapline { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center; }
.clip { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; display: block; }

/* ── detail header (DetailHeader.tsx) ─────────────────────────────── */
/* DetailHeader.tsx: 8px above the back control, 12px below the title block. */
.detail-head {
  padding: var(--s2) var(--s4) var(--s3);
  border-bottom: 1px solid var(--border-subtle);
  display: flex; flex-direction: column; gap: var(--s2);
}
.back {
  display: inline-flex; align-items: center; gap: var(--s1);
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--text-muted); font: 400 13px/19px var(--serif);
  align-self: flex-start;
}
.back svg { width: 24px; height: 24px; color: var(--text-primary); }

/* ── cards (InventoryRow.tsx / DiscoverStrainCard.tsx) ────────────── */
.card {
  display: block; width: 100%; text-align: left;
  background: var(--bg-elevated); border: 1px solid var(--border-subtle);
  border-radius: var(--r-card); padding: var(--s4); margin: 0 0 var(--s3);
  cursor: pointer; color: inherit; font: inherit;
}
.card > * + * { margin-top: var(--s3); }
.list { padding: var(--s4) var(--s4) 0; }

/* ── the chemistry rows (ChemistrySection.tsx) ────────────────────── */
.terp { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s3); }
.terp em { font-style: normal; font: 400 15px/22px var(--serif); color: var(--text-body); }
.terp span { font: 400 12px/16px var(--mono); color: var(--text-hint); white-space: nowrap; }
.cannab { display: flex; gap: var(--s5); font: 400 14px/18px var(--mono); color: var(--text-muted); }
.safe { font: 400 13px/19px var(--serif); color: var(--fresh); }
.safe.bad { color: var(--stale); }
.pillbtn {
  align-self: flex-start;
  border: 1px solid var(--accent); color: var(--accent); background: none;
  border-radius: var(--r-pill); padding: var(--s1) var(--s3); cursor: pointer;
  font: 500 11px/14px var(--mono); letter-spacing: .55px; text-transform: uppercase;
}

/* why this matters */
.why { display: flex; flex-direction: column; gap: var(--s2); }
.cite { font: italic 400 12px/16px var(--mono); color: var(--text-hint); }

/* ── price list (ComparePricesSection.tsx) ────────────────────────── */
.prow {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
  width: 100%; text-align: left; background: none; border: 0;
  border-bottom: 1px solid var(--border-subtle);
  padding: var(--s2) 0; cursor: pointer; color: inherit; font: inherit;
}
/* The right-hand side of a ladder row is a ROW, not a stack: the app puts the
   "cheapest" chip beside the price on a centred line (ComparePricesSection.tsx),
   and a chip stacked over the number reads as a badge that floated loose. */
.prow .pright { display: flex; align-items: center; gap: var(--s2); flex: 0 0 auto; }
.prow .price { font: 400 14px/18px var(--mono); color: var(--text-body); }
/* The second line of a ladder row is textHint in the app, a shade quieter than
   the body-sm textMuted used elsewhere. */
.prow .body-sm { color: var(--text-hint); }
.prow.cheapest .price { color: var(--accent); }
.prow .was { text-decoration: line-through; }

/* spread rail */
.spread { display: flex; flex-direction: column; gap: var(--s1); }
.spread .track {
  position: relative; height: 16px;
  border-top: 1px solid var(--border-subtle); margin-top: var(--s2);
}
.spread .track i {
  position: absolute; top: -3px; width: 5px; height: 5px; border-radius: 50%;
  background: var(--text-hint); transform: translateX(-50%);
}
.spread .track i.lo { background: var(--accent); width: 7px; height: 7px; top: -4px; }
.spread .track i.hi { background: var(--stale); width: 7px; height: 7px; top: -4px; }

/* ── labelled values (LabeledValue.tsx) ───────────────────────────── */
.lv { display: flex; justify-content: space-between; gap: var(--s4); align-items: baseline; }
.lv b {
  font: 500 11px/14px var(--mono); letter-spacing: .55px;
  text-transform: uppercase; color: var(--text-hint);
}
.lv span { font: 400 14px/18px var(--mono); color: var(--text-body); text-align: right; }
.stack { display: flex; flex-direction: column; gap: var(--s2); }

/* ── the lab-report tier banner ───────────────────────────────────── */
.tier {
  border-left: 3px solid var(--border-subtle); padding-left: var(--s3);
  display: flex; flex-direction: column; gap: var(--s1);
}
.tier.batch { border-left-color: var(--accent); }
.tier.brand { border-left-color: var(--accent-soft); }
.tier.ref { border-left-color: var(--stale); }
.tierhead { font: 400 15px/22px var(--serif); color: var(--text-primary); margin: 0; }
.tiersub { font: 400 13px/19px var(--serif); color: var(--text-muted); margin: 0; }
.analyte { display: flex; justify-content: space-between; gap: var(--s3); align-items: baseline; }
.analyte em { font-style: normal; font: 400 14px/20px var(--serif); color: var(--text-body); }
.analyte span { font: 400 12px/16px var(--mono); color: var(--text-muted); }

.spark { width: 100%; height: 44px; display: block; }

/* ── search ───────────────────────────────────────────────────────── */
.searchband { padding: var(--s4) var(--s4) 0; }
#q {
  width: 100%; background: var(--bg-elevated); color: var(--text-primary);
  border: 1px solid var(--border-subtle); border-radius: var(--r-card);
  padding: var(--s3) var(--s4); font: 400 15px/22px var(--serif);
}
#q::placeholder { color: var(--text-hint); }
#q:focus { outline: none; border-color: var(--accent); }
.typerow { display: flex; flex-wrap: wrap; gap: var(--s2); padding: var(--s3) var(--s4) 0; }

/* The empty-search state does work: it names the real catalogue and offers
   the twenty this demo carries, so it is never a dead end. */
.empty { display: flex; flex-direction: column; gap: var(--s3); padding: var(--s5) var(--s4); }
.empty .chips { display: flex; flex-wrap: wrap; gap: var(--s2); }
.empty .chips button {
  text-transform: none; letter-spacing: .2px;
  font: 400 13px/19px var(--serif); color: var(--text-body);
}

/* ── the wall / notices ───────────────────────────────────────────── */
.wall {
  margin: var(--s4); padding: var(--s4);
  background: var(--bg-recessed); border: 1px solid var(--border-subtle);
  border-radius: var(--r-card);
  display: flex; flex-direction: column; gap: var(--s2);
}
.notice {
  margin: var(--s4); padding: var(--s4);
  background: var(--bg-elevated); border: 1px solid var(--accent);
  border-radius: var(--r-card);
  display: flex; flex-direction: column; gap: var(--s3);
}
.acts { display: flex; flex-wrap: wrap; gap: var(--s2); }
.btn {
  font: 500 11px/14px var(--mono); letter-spacing: .55px; text-transform: uppercase;
  border-radius: var(--r-pill); padding: var(--s2) var(--s4); cursor: pointer;
  border: 1px solid var(--accent); background: none; color: var(--accent);
  text-decoration: none; display: inline-flex; align-items: center;
}
.btn.primary { background: var(--accent); color: var(--on-accent); }
.btn.quiet { border-color: var(--border-subtle); color: var(--text-muted); }
.funnel { margin: 0; font: 400 13px/19px var(--serif); }
.funnel a, .body-sm a, .body-md a { color: var(--accent); }

/* ── sheets: the state picker and the about panel (ListingSheet.tsx) ─ */
.sheet {
  position: fixed; inset: 0; z-index: 50;
  background: rgba(0,0,0,.45);
  display: flex; align-items: flex-end; justify-content: center;
  overflow-y: auto;
}
.sheet[hidden] { display: none; }
.sheetcard {
  width: 100%; max-width: var(--col);
  background: var(--bg-elevated);
  border-top-left-radius: 16px; border-top-right-radius: 16px;
  padding: var(--s5) var(--s4) var(--s8);
  position: relative;
  display: flex; flex-direction: column; gap: var(--s3);
  max-height: 92vh; overflow-y: auto;
}
.sheetcard h2 { font: 400 24px/26px var(--serif); color: var(--text-primary); margin: 0; }
.sheetcard h3 {
  font: 500 11px/14px var(--mono); letter-spacing: .55px; text-transform: uppercase;
  color: var(--accent); margin: var(--s3) 0 0;
}
.close {
  position: absolute; top: var(--s3); right: var(--s3);
  background: none; border: 0; color: var(--text-muted);
  font-size: 26px; line-height: 1; cursor: pointer; padding: var(--s1) var(--s2);
}
.strow {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
  width: 100%; background: none; border: 0; text-align: left; color: inherit;
  padding: var(--s4) 0; border-bottom: 1px solid var(--border-subtle);
  font: inherit; cursor: pointer;
}
.strow .left { display: flex; align-items: center; gap: var(--s3); min-width: 0; }

/* ── signup ───────────────────────────────────────────────────────── */
.ecform { display: flex; flex-direction: column; gap: var(--s2); position: relative; }
.ecrow { display: flex; flex-wrap: wrap; gap: var(--s2); }
.ecrow input {
  flex: 1 1 180px; min-width: 0;
  background: var(--bg-canvas); color: var(--text-primary);
  border: 1px solid var(--border-subtle); border-radius: var(--r-card);
  padding: var(--s2) var(--s3); font: 400 15px/22px var(--serif);
}
.ecrow input:focus { outline: none; border-color: var(--accent); }
/* The honeypot. Clipped rather than parked at -9999px: an off-canvas box is
   still a box, and verify_layout rightly counts anything left of the viewport
   as an overflow. A bot still sees and fills it; a person never does. */
.echp {
  position: absolute; left: 0; top: 0; width: 1px; height: 1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* ── the other three tab screens ──────────────────────────────────
   Faves, Discover and You are laid out the way the app lays them out: a
   ScrollView with 16px padding, a displayLg title, then either DetailSections
   (You) or free blocks with a 24px gap between them (Discover). */
.screen { padding: var(--s4); display: flex; flex-direction: column; gap: var(--s5); }
.screen > h1.display-lg { margin-bottom: 0; }
.screen .subcount { margin-top: calc(var(--s1) - var(--s5)); }
/* You is built out of .section, which brings its own 16px padding and its own
   divider — so the screen stops adding a second one around it. */
.screen.flush { padding: 0; gap: 0; }
.screen.flush .screenpad { padding: var(--s4) var(--s4) 0; }
.screen.flush > .wall { margin-top: var(--s4); }

/* The empty state the app actually shows when nothing is saved: centred, with
   the app's own sentence in it and 32px of air above and below. */
.voidbox {
  padding: var(--s6) var(--s4);
  display: flex; flex-direction: column; align-items: center; gap: var(--s3);
  text-align: center;
}

/* The one line per screen that says what the app does here and what this page
   is instead. Same register as an inactive control's reason, so it reads as
   the same voice — but it is not a control, so it is not dimmed and carries no
   data-why. */
.honest {
  font: 400 13px/19px var(--serif);
  color: var(--text-muted);
  border-left: 2px solid var(--accent-soft);
  padding-left: var(--s3);
  margin: 0;
}

/* Faves' five group headers, in the app's collapsed shape: chevron, label,
   count chip. */
.groups { display: flex; flex-direction: column; gap: var(--s3); }
.grouphdr {
  display: flex; align-items: center; gap: var(--s2);
  background: none; border: 0; padding: 0; text-align: left;
  color: var(--accent); font: inherit; width: 100%;
}
.grouphdr svg { width: 16px; height: 16px; flex: 0 0 auto; }
button.grouphdr.dead { border-style: none; }

/* ── Discover ─────────────────────────────────────────────────────
   Section header (label + subtitle + optional "See all") over a horizontal
   rail of cards. The rail scrolls sideways exactly as the app's FlatList does,
   and it is the one place on this page allowed to overflow its own box — the
   verifier checks the PAGE's scrollWidth, and the rail clips to the column. */
.dsec { display: flex; flex-direction: column; gap: var(--s3); }
.rail {
  display: flex; gap: var(--s3);
  overflow-x: auto; scroll-snap-type: x proximity;
  margin: 0 calc(var(--s4) * -1); padding: 0 var(--s4) var(--s1);
  scrollbar-width: none;
}
.rail::-webkit-scrollbar { display: none; }
.dcard {
  flex: 0 0 220px; width: 220px; scroll-snap-align: start;
  display: flex; flex-direction: column; gap: var(--s2);
  background: var(--bg-elevated); border: 1px solid var(--border-subtle);
  border-radius: var(--r-card); padding: var(--s4);
  text-align: left; color: inherit; font: inherit; cursor: pointer;
}
.dcard.wide { flex-basis: 260px; width: 260px; }
.dcard .row { width: 100%; }
/* A card title may run to two lines and then stop — the app's numberOfLines={2}. */
.clip2 {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; min-width: 0;
}
/* A rail that has nothing honest to put in it still occupies the shape of one,
   so the screen reads as the product rather than as a gap. */
.dnote {
  background: var(--bg-elevated); border: 1px solid var(--border-subtle);
  border-radius: var(--r-card); padding: var(--s4);
  display: flex; flex-direction: column; gap: var(--s2);
}
/* A rail that is empty for a reason says so at the weight of a note: unfilled,
   but a SOLID edge, because it is not a control and must not be mistaken for a
   dimmed one. Only the ZIP prompt — a real app control the demo cannot honour —
   is dashed, and it keeps the full bodyMd the app gives it. */
.dnote.quiet { background: none; }
div.dnote.dead { border-style: dashed; }

/* ── You ──────────────────────────────────────────────────────────
   A settings row: label over value on the left, chevron on the right, the
   whole row tappable. */
.yourow {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
  width: 100%; background: none; border: 0; padding: var(--s1) 0;
  text-align: left; color: inherit; font: inherit; cursor: pointer;
  text-decoration: none;
}
button.yourow.dead, a.yourow.dead { border-style: none; }
.yourow svg { width: 20px; height: 20px; color: var(--text-hint); flex: 0 0 auto; }
.yourow .left { display: flex; align-items: center; gap: var(--s3); min-width: 0; flex: 1 1 auto; }

/* The consent switch, drawn rather than imported: the app's Switch with the
   subtle track it has when it is off, which is the state this page is in and
   cannot leave. */
.switch {
  flex: 0 0 auto; width: 44px; height: 26px; padding: 0;
  border-radius: 13px; border: 1px solid var(--border-subtle);
  background: var(--bg-recessed); position: relative; cursor: help;
}
.switch i {
  position: absolute; top: 2px; left: 2px; width: 20px; height: 20px;
  border-radius: 50%; background: var(--text-hint);
}
button.switch.dead { border-style: solid; }

/* ── footer ───────────────────────────────────────────────────────── */
footer {
  padding: var(--s5) var(--s4) var(--s6);
  display: flex; flex-direction: column; gap: var(--s2);
  border-top: 1px solid var(--border-subtle);
}

.sigil { width: 26px; height: 26px; color: var(--accent); flex: 0 0 auto; }
.sigil.big { width: 34px; height: 34px; }

code { font-family: var(--mono); font-size: .9em; color: var(--accent); }

/* visually-hidden label for an icon-only control */
.sr {
  position: absolute; left: 0; top: 0; width: 1px; height: 1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
button.cite { background: none; border: 0; padding: 0; text-align: left; }

/* A button in a flex row must not be allowed to shrink into a three-line
   stack: the icon + label lockup is one object. */
.btn { flex: 0 0 auto; white-space: nowrap; gap: var(--s2); }
.btn svg { width: 16px; height: 16px; }
.chip svg { width: 13px; height: 13px; }
