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

/* Americana display serif — self-hosted (Noto Serif Display, already on this
   system) under a project-specific name, same convention as the map's era
   fonts. Used for headings/wordmark only; body copy stays on --font for
   readability. */
@font-face { font-family: 'AmericanaDisplay'; src: url('/static/fonts/americana-display-regular.ttf'); font-weight: 400; font-style: normal; }
@font-face { font-family: 'AmericanaDisplay'; src: url('/static/fonts/americana-display-bold.ttf');    font-weight: 700; font-style: normal; }
@font-face { font-family: 'AmericanaDisplay'; src: url('/static/fonts/americana-display-italic.ttf');  font-weight: 400; font-style: italic; }

/* Script wordmark font — matched to the real logo files (NAS "Heritage
   American/HA Logo 1-5.jpg") by rendering candidate Google Fonts and
   comparing letterforms directly; Alex Brush was the closest match (H/A
   loop shapes, stroke contrast, i-dot style). Scoped deliberately to brand
   moments only (topbar wordmark, hero taglines) — a script face isn't
   legible as a functional page/card heading at 16-22px, confirmed via
   real mockups the user reviewed before choosing this scope. */
@font-face { font-family: 'AlexBrush'; src: url('/static/fonts/alex-brush.ttf'); font-weight: 400; font-style: normal; }

/* Body-face candidates for the 2026-08-16 aesthetic pass — self-hosted the
   same way as AmericanaDisplay/AlexBrush (system fonts renamed under a
   project name, no CDN). --font itself has named 'Inter' since the brand
   pass with no font file and no @font-face behind it — every page has
   actually been rendering system-ui the whole time. These two are declared
   now so a scratch mockup harness can render real screenshots of each
   before committing --font to one; whichever isn't chosen stays declared-
   but-unused, same as any other unused @font-face, no visual cost. */
@font-face { font-family: 'HeritageSerifText'; src: url('/static/fonts/heritage-serif-text-regular.ttf'); font-weight: 400; font-style: normal; }
@font-face { font-family: 'HeritageSerifText'; src: url('/static/fonts/heritage-serif-text-bold.ttf');    font-weight: 700; font-style: normal; }
@font-face { font-family: 'HeritageSerifText'; src: url('/static/fonts/heritage-serif-text-italic.ttf');  font-weight: 400; font-style: italic; }
@font-face { font-family: 'HeritageSansText';  src: url('/static/fonts/heritage-sans-text-regular.ttf');  font-weight: 400; font-style: normal; }
@font-face { font-family: 'HeritageSansText';  src: url('/static/fonts/heritage-sans-text-bold.ttf');     font-weight: 700; font-style: normal; }
@font-face { font-family: 'HeritageSansText';  src: url('/static/fonts/heritage-sans-text-italic.ttf');   font-weight: 400; font-style: italic; }

/* "Americana" heritage palette (replaces the earlier "Spring" board,
   2026-08-02) — sourced from the real logo/illustration set above: parchment
   cream, colonial navy, brick terra, antique gold, and forest sage (a
   deliberate accent addition, not present in the source art, chosen to echo
   the trees/cotton plants in the icon set). Backgrounds are warm parchment
   tones by design (the family map at /tree keeps its own separate dark
   theme and is deliberately not touched by this file). Accent/accent-hi/
   danger are
   darkened from their raw swatches to clear WCAG AA contrast (4.5:1+)
   against the parchment background — forest sage and colonial navy already
   clear AA at their raw values, so --accent/--success/--text use the raw
   swatch directly. The untouched raw swatches live below as
   --parchment/--navy/--brick/--gold/--sage for decorative use (dividers,
   illustration accents, buttons with white text) where AA text contrast
   doesn't apply. */
:root {
  --bg:        #faf5ec;
  --surface:   #f5ede0;
  --border:    #e3d5b9;
  --text:      #1e2a44;
  --muted:     #5a6478;
  --accent:    #5c6b4a;
  --accent-hi: #495536;
  --danger:    #8c4f3b;
  --success:   #5c6b4a;
  --navy-hi:   #161f33;
  --radius:    8px;
  /* Fixed 2026-08-16: this named 'Inter' with no font file and no
     @font-face behind it since the brand pass — every page has actually
     been silently rendering system-ui the whole time. Named honestly now;
     see the HeritageSerifText/HeritageSansText @font-face block above for
     the two self-hosted candidates evaluated to replace it outright. */
  --font:         system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --font-display: 'AmericanaDisplay', Georgia, 'Times New Roman', serif;
  --font-script:  'AlexBrush', cursive;
  /* Self-hosted serif body face -- "Full Heritage" (variant C), user-selected
     2026-08-16 from a 3-variant mockup review. .ha-scoped (see body rule
     below), NOT a change to --font itself, so admin stays on the neutral
     system font while in-scope pages get the document-like serif texture. */
  --font-serif-text: 'HeritageSerifText', Georgia, 'Times New Roman', serif;

  /* Raw palette swatches, undarkened — decorative use only */
  --parchment: #f5ede0;
  --navy:      #1e2a44;
  --brick:     #9c5a45;
  --gold:      #c4a46c;
  --sage:      #5c6b4a;

  /* ── 2026-08-16 aesthetic pass: the missing token layers ────────────────
     Additive only — nothing above is renamed or removed. Every rule that
     CONSUMES these (heading sizes beyond the bare h1/h2/h3 default, the
     card heading rule, elevation, ornaments, texture) is scoped under
     .ha (see the scope block below), so a page that doesn't opt in is
     visually unaffected by anything past this point. Tokens themselves
     are global and inert until referenced — declaring a custom property
     nobody reads yet cannot change anything on screen. */

  /* Type scale — px (not rem): the a11y large-text mode below keys off px
     and the whole site is already px, so rem would be the one inconsistent
     unit on the page. ~1.25 ratio above 16px, ~0.87 below. Deliberately no
     10px/11px step — those two sizes account for 217 of the ~700 font-size
     declarations across the site and are the single biggest reason it
     reads as a dense web app rather than a keepsake; both should resolve
     up to --fs-2xs/--fs-xs once pages adopt the scale. */
  --fs-2xs: 11px; --fs-xs: 13px; --fs-sm: 14px; --fs-base: 16px;
  --fs-md:  18px; --fs-lg: 21px; --fs-xl: 27px; --fs-2xl: 34px; --fs-3xl: 44px;
  --lh-tight: 1.15; --lh-snug: 1.35; --lh-base: 1.65; --lh-loose: 1.75;
  --ls-display: -0.015em; --ls-eyebrow: 0.12em;

  /* Spacing — 4px base */
  --space-1: 4px;  --space-2: 8px;  --space-3: 12px; --space-4: 16px;
  --space-5: 20px; --space-6: 24px; --space-8: 32px; --space-10: 40px; --space-12: 48px;

  /* Radius additions — --radius (8px) itself is unchanged */
  --radius-sm: 6px; --radius-lg: 14px; --radius-pill: 999px;

  /* Elevation — navy-tinted to match the two shadow literals already in
     this file (moved to --shadow-xl/--shadow-lg below verbatim, so
     .notif-dropdown and the mobile nav drawer render pixel-identically
     once they're switched to the token). */
  --shadow-xs: 0 1px 2px rgba(30,42,68,.06);
  --shadow-sm: 0 1px 2px rgba(30,42,68,.05), 0 2px 6px rgba(30,42,68,.06);
  --shadow-md: 0 1px 3px rgba(30,42,68,.06), 0 6px 16px rgba(30,42,68,.09);
  --shadow-lg: 0 2px 6px rgba(30,42,68,.08), 0 14px 34px rgba(30,42,68,.13);
  --shadow-xl: 0 12px 32px rgba(30,42,68,.14);
  --edge-light: inset 0 1px 0 rgba(255,255,255,.6);
  --ring-accent: 0 0 0 3px rgba(92,107,74,.18);
  --ring-gold:   0 0 0 3px rgba(196,164,108,.30);

  /* Motion */
  --t-fast: 120ms cubic-bezier(.4,0,.2,1);
  --t-base: 180ms cubic-bezier(.4,0,.2,1);
  --t-slow: 280ms cubic-bezier(.4,0,.2,1);

  /* Z-index — codifies values already in use elsewhere on the site */
  --z-topbar: 100; --z-nav: 150; --z-dropdown: 200; --z-overlay: 900; --z-modal: 1000;

  /* Heritage-specific */
  --surface-raised: #fbfcf8; /* the "pearl" home.html already uses locally as --card-bg */
  --rule-gold: rgba(196,164,108,.55);
  --rule-hair: rgba(196,164,108,.28);
  --paper-tint: rgba(196,164,108,.10);
  --grain-opacity: .07; /* "Full Heritage" (variant C), user-selected 2026-08-16 */
}

/* ── Aesthetic-pass scope hook ────────────────────────────────────────────
   In-scope pages add `<html class="ha">` as their literal first line
   (before <title> — none of these files have their own <!DOCTYPE>/<html>/
   <head>). Per standard HTML5 parsing, a leading <html> token is adopted
   as the real root element with its attributes intact, so this is normal
   browser behavior, not a hack. Every "richer heritage character" rule
   below is written .ha-scoped so admin/tree — which never carry the class
   — are protected by construction rather than by care. */

h1, h2, h3 { font-family: var(--font-display); font-weight: 700; }

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.6;
  min-height: 100vh;
}

/* ── Layout ──────────────────────────────────────────────────────────────── */

.topbar {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 24px;
  height: 56px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 100;
}

.topbar .logo {
  font-family: var(--font-script);
  font-size: 24px;
  font-weight: 700;
  color: var(--navy);
  letter-spacing: 0;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
.topbar .logo::before {
  content: "";
  display: inline-block;
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23495536'%3E%3Cpolygon points='12,2 14.9,9.1 22,9.5 16.5,14.3 18.2,21.5 12,17.5 5.8,21.5 7.5,14.3 2,9.5 9.1,9.1'/%3E%3C/svg%3E") no-repeat center/contain;
}

.topbar nav { display: flex; gap: 4px; flex: 1; }

.topbar nav a {
  color: var(--muted);
  text-decoration: none;
  padding: 6px 12px;
  border-radius: var(--radius);
  font-size: 14px;
  transition: color .15s, background .15s;
}
.topbar nav a:hover, .topbar nav a.active { color: var(--text); background: var(--border); }

.topbar-right { display: flex; align-items: center; gap: 12px; }

.page { max-width: 1100px; margin: 0 auto; padding: 32px 24px; }

/* ── Cards ───────────────────────────────────────────────────────────────── */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 24px;
  margin-bottom: 20px;
}

.card h2 { font-size: 16px; font-weight: 600; margin-bottom: 16px; }

/* ── 2026-08-16 aesthetic pass: heritage character (opt-in via .ha) ───────
   Everything below is scoped under .ha (see the scope-hook comment above
   the :root block) so it only ever reaches pages that carry the class —
   admin/tree are structurally unaffected. Values are pulled from the
   token layer above so the intensity can be tuned in one place. */

/* Paper grain — a fixed, full-viewport, tiled feTurbulence layer, same
   "tiled SVG data-URI behind a mask" technique home.html's hero band
   already proved out, generalized site-wide for in-scope pages. One
   composited layer for the whole document, never per-card (a filter per
   .card would be genuinely expensive on a page like /home that renders a
   dozen-plus cards). mix-blend-mode:multiply so it reads as texture in the
   parchment, not a visible haze; --grain-opacity is 0 under high-contrast
   (see that block below) since a multiply layer over text would reduce
   contrast, not just decorate. */
.ha body { font-family: var(--font-serif-text); }

.ha body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: var(--grain-opacity);
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* "Full Heritage" (variant C, user-selected 2026-08-16 from the 3-variant
   mockup review) -- gold-bordered cards, deeper shadow. */
.ha .card {
  border-radius: var(--radius-lg);
  border-color: var(--rule-gold);
  box-shadow: var(--edge-light), var(--shadow-lg);
  padding: var(--space-6);
}
.ha .card:hover { box-shadow: var(--edge-light), var(--shadow-xl); }

.ha .card h2 {
  font-size: var(--fs-lg);
  font-weight: 700;
  margin-bottom: var(--space-4);
}
.ha .card h2::after {
  content: "";
  display: block;
  width: 52px;
  height: 3px;
  background: var(--rule-gold);
  margin-top: var(--space-2);
  border-radius: 1px;
}

/* Gold topbar rule -- highest impact-to-risk ratio in this pass: every
   in-scope page, one line, no reflow, no markup change. */
.ha .topbar { box-shadow: inset 0 -2px 0 var(--rule-gold); }

/* Page-specific "card-shaped" components that predate .card or have their
   own header/body split (so converting their markup to .card outright
   would risk real layout changes) -- listed explicitly here rather than
   silently left flat, so they still pick up the same elevation language
   as everything else in this pass. .result-card (claim.html) intentionally
   excluded: it's a clickable list row, not a content card, and the gold
   border would read as permanently-selected. */
.ha .preview-card, .ha .success-box, .ha .product-card {
  border-radius: var(--radius-lg);
  border-color: var(--rule-gold);
  box-shadow: var(--edge-light), var(--shadow-lg);
}

/* Full-page h1 (welcome banners, page titles) reads at --fs-2xl in variant
   C, one step larger than the base --fs-xl the scale otherwise reserves
   for it -- keeping --fs-xl itself untouched rather than mutating the
   token, in case something else ever wants that in-between size. */
.ha h1 {
  font-size: var(--fs-2xl);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-display);
}
.ha h3 { font-size: var(--fs-md); }

.ha .eyebrow {
  display: inline-block;
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--accent-hi);
}

/* Gold-hairline variant of the existing star divider -- for in-card
   section breaks, where a full star ornament would be too heavy. */
.ha .americana-divider--rule::before,
.ha .americana-divider--rule::after { background: var(--rule-gold); }
.ha .americana-divider--rule .ad-star { display: none; }

/* ── Forms ───────────────────────────────────────────────────────────────── */

.form-group { margin-bottom: 16px; }
.form-group label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 6px; }

input[type=text], input[type=email], input[type=password] {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  padding: 10px 14px;
  font-size: 15px;
  outline: none;
  transition: border-color .15s;
}
input:focus { border-color: var(--accent); }
input[type=range], input[type=checkbox], input[type=radio] { accent-color: var(--accent); }

/* select/textarea/number/date were never styled here at all -- every
   in-scope page with one (donate, preferences, onboarding) rebuilt the
   whole input recipe itself, usually at 6px radius instead of this file's
   8px. Deliberately .ha-scoped and WITHOUT a width rule: admin has 13
   <select> elements (only 3 locally styled) that a global, width:100%
   rule would visually break. */
.ha select, .ha textarea, .ha input[type=number], .ha input[type=date] {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  padding: 10px 14px;
  font-size: 15px;
  font-family: var(--font-serif-text);
  outline: none;
  transition: border-color var(--t-base);
}
.ha select:focus, .ha textarea:focus,
.ha input[type=number]:focus, .ha input[type=date]:focus { border-color: var(--accent); }

/* Real focus rings -- added 2026-08-16. `:focus-visible` (not `:focus`) so
   a mouse click doesn't ring every input/button, only real keyboard focus,
   which is the actual a11y gap this fixes: inputs previously only changed
   border-color on focus, buttons had no focus indicator at all. Global
   (not .ha-scoped) since this is a straightforward accessibility fix that
   should reach admin too, not a stylistic choice. */
input:focus-visible, select:focus-visible, textarea:focus-visible { box-shadow: var(--ring-accent); }
.btn:focus-visible, a:focus-visible, button:focus-visible { outline: none; box-shadow: var(--ring-accent); }

/* ── Buttons ─────────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 9px 18px;
  border-radius: var(--radius);
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  border: none;
  text-decoration: none;
  white-space: nowrap;
  transition: opacity var(--t-base), background var(--t-base), transform var(--t-fast), box-shadow var(--t-base);
}
.btn:hover { opacity: .85; }
.btn:disabled { opacity: .4; cursor: default; }

.btn-primary  { background: var(--navy);     color: #fff; }
.btn-ghost    { background: transparent; color: var(--muted); border: 1px solid var(--border); }
.btn-danger   { background: var(--danger);   color: #fff; }
.btn-success  { background: var(--success);  color: #fff; }

/* Slightly more tactile hover on the primary heritage-scoped surfaces --
   a small lift + real shadow instead of the sitewide opacity fade, which
   stays as the default everywhere else (admin included). */
.ha .btn-primary:hover, .ha .btn-success:hover, .ha .btn-danger:hover {
  opacity: 1;
  transform: translateY(-1px);
  box-shadow: var(--shadow-sm);
}

/* ── Auth pages ──────────────────────────────────────────────────────────── */

.auth-wrap {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.auth-box {
  width: 100%;
  max-width: 420px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 40px;
}

.auth-box .brand {
  text-align: center;
  margin-bottom: 32px;
}
.auth-box .brand h1 {
  font-size: 22px;
  font-weight: 700;
  color: var(--accent-hi);
  letter-spacing: -0.5px;
}
.auth-box .brand p { color: var(--muted); font-size: 14px; margin-top: 4px; }

/* Real logo (5 hand-picked concepts, one shown at random per page load —
   see LOGO_VARIANTS in each auth page's script). Includes its own star +
   flourish motif, so pages using it drop the separate .americana-divider. */
.auth-box .brand .brand-logo {
  display: block;
  width: 100%;
  max-width: 260px;
  height: auto;
  margin: 0 auto 6px;
}

/* Gold frame around the logo -- user request, 2026-08-16, added alongside
   variant C. Padding + its own surface (rather than a border flush against
   the artwork) so the frame reads as a keepsake mount, not just an outline
   drawn over the image -- the source art's own background is a similar
   cream that would otherwise make a flush border nearly invisible. */
.ha .auth-box .brand .brand-logo {
  border: 2px solid var(--rule-gold);
  border-radius: var(--radius-lg);
  padding: var(--space-3);
  background: var(--surface-raised);
  box-shadow: var(--shadow-sm);
}

/* ── Americana motif: star divider ──────────────────────────────────────────
   Small reusable ornament for section/brand breaks. Markup:
   <div class="americana-divider"><svg class="ad-star" viewBox="0 0 24 24"
     fill="currentColor"><polygon points="12,2 14.9,9.1 22,9.5 16.5,14.3 18.2,21.5
     12,17.5 5.8,21.5 7.5,14.3 2,9.5 9.1,9.1"/></svg></div> */
.americana-divider {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 14px 0;
}
.americana-divider::before, .americana-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}
.americana-divider .ad-star { flex-shrink: 0; width: 13px; height: 13px; color: var(--gold); }

.auth-footer {
  text-align: center;
  margin-top: 20px;
  font-size: 13px;
  color: var(--muted);
}
.auth-footer a { color: var(--accent-hi); text-decoration: none; }

/* ── Utility ─────────────────────────────────────────────────────────────── */

.error-msg {
  background: rgba(140,79,59,.1);
  border: 1px solid rgba(140,79,59,.3);
  color: var(--danger);
  border-radius: var(--radius);
  padding: 10px 14px;
  font-size: 14px;
  margin-bottom: 16px;
  display: none;
}
.error-msg.show { display: block; }

.muted { color: var(--muted); font-size: 13px; }
.mt-8  { margin-top: 8px; }
.mt-16 { margin-top: 16px; }

/* ── Story engagement (likes/comments) ──────────────────────────────────── */

.engagement-bar {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 8px;
}
.eng-like-btn, .eng-comment-btn {
  display: flex;
  align-items: center;
  gap: 5px;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--muted);
  font-size: 13px;
  padding: 4px 2px;
  transition: color .15s, transform .1s;
}
.eng-like-btn:active, .eng-comment-btn:active { transform: scale(0.92); }
.eng-like-btn:hover, .eng-comment-btn:hover { color: var(--text); }
.eng-like-btn.liked { color: var(--danger); }
.eng-like-icon { font-size: 15px; }

.eng-comments {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--border);
}
.eng-comments-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 8px;
  max-height: 180px;
  overflow-y: auto;
}
.eng-comment {
  font-size: 13px;
  line-height: 1.45;
}
.eng-comment-author {
  font-weight: 600;
  color: var(--accent-hi);
  margin-right: 6px;
}
.eng-comment-text { color: var(--text); }
.eng-comments-empty {
  font-size: 12px;
  color: var(--muted);
  font-style: italic;
}
.eng-comment-input-row {
  display: flex;
  gap: 6px;
}
.eng-comment-input {
  flex: 1;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font-size: 13px;
  padding: 7px 10px;
  outline: none;
}
.eng-comment-input:focus { border-color: var(--accent); }
.eng-comment-submit {
  background: var(--navy);
  color: #fff;
  border: none;
  border-radius: var(--radius);
  padding: 7px 14px;
  font-size: 13px;
  cursor: pointer;
}
.eng-comment-submit:hover { background: var(--navy-hi); }

/* ── Notifications (bell icon, shared across all pages) ───────────────────── */
.notif-wrap { position: relative; }
.notif-bell {
  background: none; border: none; cursor: pointer; font-size: 17px;
  position: relative; padding: 4px 6px; line-height: 1;
}
.notif-bell-icon { display: inline-flex; vertical-align: middle; color: var(--muted); }
.notif-bell:hover .notif-bell-icon { color: var(--text); }

.topbar-icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 4px 6px; border-radius: var(--radius); color: var(--muted);
  text-decoration: none; line-height: 1; transition: color .15s, background .15s;
}
.topbar-icon-btn:hover { color: var(--text); background: var(--border); }
.topbar-icon-btn.active { color: var(--accent-hi); }
.notif-badge {
  position: absolute; top: -2px; right: -2px;
  background: var(--danger); color: #fff; font-size: 10px; font-weight: 700;
  border-radius: 10px; min-width: 16px; height: 16px; padding: 0 4px;
  display: flex; align-items: center; justify-content: center;
}
.notif-dropdown {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 200;
  width: 320px; max-width: 88vw; max-height: 420px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 12px 32px rgba(30,42,68,.14);
}
.notif-dropdown-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 14px; border-bottom: 1px solid var(--border);
  font-size: 13px; font-weight: 600; position: sticky; top: 0; background: var(--surface);
}
.notif-mark-all {
  background: none; border: none; color: var(--accent-hi); font-size: 12px;
  cursor: pointer; font-weight: 500;
}
.notif-item {
  display: block; width: 100%; text-align: left; background: none; border: none;
  border-bottom: 1px solid var(--border); padding: 12px 14px; cursor: pointer;
  color: var(--text); font-size: 13px; line-height: 1.5;
}
.notif-item:last-child { border-bottom: none; }
.notif-item:hover { background: var(--bg); }
.notif-item.unread { background: rgba(92,107,74,.08); }
.notif-item.unread::before { content: "●"; color: var(--accent-hi); font-size: 8px; margin-right: 6px; }
.notif-time { color: var(--muted); font-size: 11px; margin-top: 2px; }
.notif-empty { padding: 24px 16px; text-align: center; color: var(--muted); font-size: 13px; }

/* ── Mobile nav (hamburger, injected by mobile-nav.js on pages with a
   .topbar nav) ──────────────────────────────────────────────────────────── */
.topbar-hamburger {
  display: none;
  background: none; border: none; cursor: pointer;
  padding: 8px; margin: -8px 0; border-radius: var(--radius);
  color: var(--text); align-items: center; justify-content: center;
  flex-shrink: 0;
}
.topbar-hamburger:hover { background: var(--border); }

/* ── Global mobile breakpoint ─────────────────────────────────────────────
   Shared layout/touch-target rules for every page. Page-specific grids and
   content (home feed, admin tables, tree.html's separate dark UI) get their
   own targeted overrides alongside their own styles, not here. */
@media (max-width: 720px) {
  .page { padding: 20px 14px; }
  .card { padding: 16px; }

  .topbar { height: auto; min-height: 56px; padding: 8px 10px; gap: 6px; flex-wrap: nowrap; }
  .topbar .logo { font-size: 15px; white-space: nowrap; }
  .topbar-right { gap: 8px; }
  .topbar-hamburger { display: inline-flex; }
  .topbar-right span.muted { display: none; }

  .topbar nav {
    display: none;
    position: fixed;
    top: 56px; left: 0; right: 0;
    flex-direction: column;
    gap: 2px;
    padding: 8px;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    box-shadow: 0 12px 24px rgba(30,42,68,.14);
    z-index: 150;
  }
  .topbar nav.nav-open { display: flex; }
  .topbar nav a { padding: 12px 14px; font-size: 15px; }

  /* Touch targets: buttons/links/inputs get a comfortable minimum tap area */
  .btn { padding: 11px 18px; min-height: 44px; font-size: 15px; }
  .topbar-icon-btn, .notif-bell { min-width: 40px; min-height: 40px; }
  /* The topbar itself has limited width to work with (logo + icons + this
     button all on one row) -- the general .btn touch-target bump above
     made "Sign out" wide enough to overflow the viewport at narrow widths.
     Caught live via screenshot: page.evaluate() confirmed a real 24px
     horizontal overflow, not just a visual guess. Sized to match the
     40px already established for the topbar's other mobile touch targets
     (.topbar-icon-btn/.notif-bell above) rather than inventing a new size. */
  .topbar-right .btn { padding: 8px 14px; min-height: 40px; font-size: 13px; }
  input[type=text], input[type=email], input[type=password] {
    padding: 12px 14px; font-size: 16px; /* 16px avoids iOS auto-zoom-on-focus */
  }

  /* Page-level action buttons that don't carry .btn. The rule above only ever
     covered .btn, so buttons styled by a page's own CSS stayed at their desktop
     height. Measured live at 390px with a real touch context before adding this:
     /admin/quotes action buttons 28px, .btn-skip 27px, admin .tab-btn 34px --
     all well under the 44px touch minimum.

     Deliberately NOT a bare `button` rule. Compact icon controls are small on
     purpose and a blanket bump would wreck them: the story ♡/💬 pair (26x21),
     the side-panel close × (11x18), the reel arrows (32x32). Those are listed
     here so the omission reads as a decision rather than an oversight. */
  .tab-btn,
  .save-btn,
  .btn-skip,
  .btn-text-toggle,
  .qc-actions button,
  .qc-actions .btn {
    min-height: 44px;
    padding-top: 10px;
    padding-bottom: 10px;
  }

  /* A <select> is a tap target too, and the inputs rule above only covers
     text/email/password. 16px for the same iOS auto-zoom reason stated there. */
  select {
    min-height: 44px;
    padding: 10px 12px;
    font-size: 16px;
  }

  .auth-box { padding: 28px 22px; }
}

/* Below ~340px (legacy iPhone-5-era widths) even a legible script size
   overflows the topbar -- the script font's connecting strokes make it
   illegible if shrunk small enough to fit instead, so the wordmark text
   is dropped and only the star mark (::before, fixed pixel size) shows.
   Verified via page.evaluate() scrollWidth/clientWidth: 0px overflow at
   320/340/360/375/390/414, vs. 19px+ overflow before this rule existed. */
@media (max-width: 340px) {
  .topbar .logo { font-size: 0; }
}

/* ── Accessibility: larger text / high contrast ───────────────────────────
   Toggled from /preferences (a11y.js), persisted in localStorage, applied
   site-wide via data attributes on <html>. Most page text is set in px on
   specific elements rather than inherited, so this targets the common
   shared components directly rather than relying on a root font bump. */
:root[data-a11y-text="large"] body { font-size: 17px; }
:root[data-a11y-text="large"] .btn { font-size: 16px; }
:root[data-a11y-text="large"] input[type=text],
:root[data-a11y-text="large"] input[type=email],
:root[data-a11y-text="large"] input[type=password] { font-size: 17px; }
:root[data-a11y-text="large"] .card h2 { font-size: 18px; }
:root[data-a11y-text="large"] .muted { font-size: 15px; }
:root[data-a11y-text="large"] .topbar nav a { font-size: 16px; }
:root[data-a11y-text="large"] .topbar .logo { font-size: 28px; }

:root[data-a11y-contrast="high"] {
  --bg:        #ffffff;
  --surface:   #ffffff;
  --border:    #000000;
  --text:      #000000;
  --muted:     #3a3a3a;
  --accent:    #1f3550;
  --accent-hi: #0d1c2e;
  --danger:    #8a1f0f;
  --success:   #14401f;
  /* Fixed 2026-08-16: this block never touched the raw decorative swatches,
     so anything using var(--navy)/var(--gold) directly (e.g. .btn-primary,
     which uses --navy, not --text) silently ignored high-contrast mode.
     Also neutralizes the new elevation/texture tokens -- high-contrast is
     a deliberate flat black/white mode, and a multiply-blended grain layer
     over text would actively reduce contrast, making texture an a11y
     regression instead of decoration. */
  --navy:      #000000;
  --navy-hi:   #000000;
  --gold:      #000000;
  --brick:     #6b1e0c;
  --sage:      #14401f;
  --parchment: #ffffff;
  --surface-raised: #ffffff;
  --rule-gold: #000000;
  --rule-hair: #000000;
  --paper-tint: transparent;
  --grain-opacity: 0;
  --shadow-xs: none; --shadow-sm: none; --shadow-md: none; --shadow-lg: none; --shadow-xl: none;
  --edge-light: none;
}
:root[data-a11y-contrast="high"] .card,
:root[data-a11y-contrast="high"] .auth-box,
:root[data-a11y-contrast="high"] input[type=text],
:root[data-a11y-contrast="high"] input[type=email],
:root[data-a11y-contrast="high"] input[type=password] { border-width: 2px; }
:root[data-a11y-contrast="high"] .topbar { border-bottom-width: 2px; }

/* Added 2026-08-16 alongside the new --t-* motion tokens -- OS-level
   "reduce motion" preference, previously unhandled anywhere on the site. */
@media (prefers-reduced-motion: reduce) {
  :root { --t-fast: 0ms; --t-base: 0ms; --t-slow: 0ms; }
  *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
}
