/* =========================================================================
   Explain to a Caveman — stylesheet
   =========================================================================
   Layout & visuals only. No DOM logic. Theming is driven by CSS custom
   properties on :root, overridden per [data-theme="…"] palette. The single
   gradient (--grad) is reused for: output card top border, active
   mode-toggle underline, primary button accents, theme toggle ring, and
   (profile.html only) the cosmetic title system's grad/shine/sparkle/magma
   fills — headings themselves are plain color as of F10; a gradient
   text-fill on every H1 sitewide was one of the audit's named AI-tells.
   ========================================================================= */

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

/* ---------- Self-hosted type (F1) --------------------------------------
   Bricolage Grotesque (display, one static weight) + Hanken Grotesk (body,
   variable 400-700) — both woff2, both subset to latin only, ~57KB total
   (well under the 100KB budget). Self-hosted rather than the Google CDN:
   cache partitioning killed the old cross-site-cache benefit, so pulling
   from fonts.gstatic.com is now strictly worse than serving our own copy.
   font-display: swap on both so first paint never blocks on the network;
   only the display face's single weight is preloaded (see each HTML
   <head>) since it's what paints first (H1/hero text).

   Both are SIL Open Font License — self-hosting is redistribution, and the
   OFL requires the license text travel with the files. It's alongside them
   at assets/fonts/OFL.txt; keep it there if these ever move. */
@font-face {
  font-family: "Bricolage Grotesque";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("assets/fonts/bricolage-grotesque-700.woff2") format("woff2");
}
@font-face {
  font-family: "Hanken Grotesk";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("assets/fonts/hanken-grotesk-var.woff2") format("woff2");
}

/* ---------- Cross-document view transitions (F17) -------------------------
   Loads on all 20 pages, so both the departing and arriving document opt in
   at once — same-origin navigation is all this site ever does. A restrained
   old/new pair (the site's own enter/exit curves and timing, not the
   browser's 250ms default), plus a stable name on the HUD host so it
   PERSISTS across the nav instead of cross-fading with the rest of the page
   — that one line is what makes clicking around read as an app, not a
   stack of documents.

   Pure progressive enhancement: browsers without support (Firefox) just
   navigate normally, same as before this rule existed.

   Do NOT use this for the walkthrough's LETS GO handoff — that one keeps
   `etacLaunchHold` (index.html's <head>), which this rule does not replace.
   Two reasons, both from MDN/the audit, not just caution: (1) `@view-
   transition` is not Baseline — Firefox hasn't shipped it, so the hold has
   to keep working regardless; (2) a cross-document VT snapshots the new
   page at first PAINT, and the hold exists precisely because first paint is
   the empty state (HUD not hydrated, daily card not fetched yet) — the
   native transition would show exactly what the hold was built to hide.
   index.html's launch script calls skipTransition() on `pagereveal` for
   that one navigation so Chromium users don't get both effects stacked. */
@view-transition {
  navigation: auto;
}
::view-transition-old(root) {
  animation: etac-vt-exit 200ms var(--ease-exit) both;
}
::view-transition-new(root) {
  animation: etac-vt-enter 240ms var(--ease-enter) both;
}
@keyframes etac-vt-exit { to { opacity: 0; } }
@keyframes etac-vt-enter { from { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root), ::view-transition-new(root) { animation: none !important; }
}

/* ---------- Theme palettes (CSS variables) ----------
   Two free themes (Dark, Light) + four purchasable elemental themes.
   :root holds the Dark (default) palette plus shared values. Each
   [data-theme="..."] overrides only what differs. Switching themes is
   just setting <html data-theme="water">. */

/* Every theme follows the 60-30-10 rule:
     60% — NEUTRAL: --bg, the page field. Near-neutral with only a whisper
           of the theme's temperature, so content owns the page.
     30% — CONTRAST: --bg-elevated/--bg-input surfaces, --border(-hover),
           and the text ramp — the structural layer that builds hierarchy.
     10% — HIGHLIGHT: --accent/--grad, the theme's main color. Reserved for
           CTAs, links, active states, and the XP/ring fills — never fields.
   Each block below is ordered 60 → 30 → 10. */

:root,
[data-theme="dark"] {
  /* DARK — free default. Plain graphite field; quiet bone/brass highlight
     (warm enough to feel caveman, nothing like Fire's ember orange). */
  /* 60% neutral */
  --bg:           #141518;
  /* 30% contrast */
  --bg-elevated:  #1d1e22;
  --bg-input:     #18191d;

  --border:       #2c2d32;
  --border-hover: #3a3b41;

  --text:         #eceae6;
  --text-muted:   #9b978f;
  --text-dim:     #8a867e;

  /* 10% highlight */
  --accent:       #c2a06b;
  --accent-soft:  rgba(194, 160, 107, 0.13);
  /* Text ON --accent (buttons, chips) — hub.css's var(--accent-ink, #fff)
     fallback was never actually overridden anywhere, so every primary
     button silently rendered white-on-#c2a06b (2.46:1, fails AA) until
     this. Same dark ink hub.css:913 already uses for the spark chip. */
  --accent-ink:   #1a1206;

  --warning-bg:   rgba(255, 77, 77, 0.08);
  --warning-fg:   #ffb8b8;
  --warning-bd:   rgba(255, 77, 77, 0.25);
  --success-fg:   #7fd989;

  /* Per-theme gradient — used for H1 fill, output card top border,
     mode-toggle underline, primary button accents, theme-switcher ring */
  --grad: linear-gradient(135deg, #e9d8b1 0%, #c2a06b 50%, #8f6f42 100%);

  --caveman:        #eceae6;
  --caveman-option: #d3b071; /* gold for "Caveman" option in dropdown */
  /* Title chrome (hub.css .etac-title). All three contrast the GROUND, not
     the title — the catalog runs from near-black (Iridescent) to near-white
     (SHINY), so anything keyed to the title's own colour would vanish for
     half of them. --edge is the hard 1px drop behind the glyphs; --plate and
     --plate-bd are the small rounded rectangle they sit on. */
  --title-edge:   rgba(255, 255, 255, 0.5);
  --title-plate:    rgba(255, 255, 255, 0.055);
  --title-plate-bd: rgba(255, 255, 255, 0.10);
}

[data-theme="light"] {
  /* LIGHT — free. Plain paper field; the same bone/brass family as Dark,
     deepened for AA contrast on the light ground. */
  /* 60% neutral */
  --bg:           #f7f5f2;
  /* 30% contrast */
  --bg-elevated:  #ebe8e2;
  --bg-input:     #fffefc;

  --border:       #cec8bd;
  --border-hover: #a49d8f;

  --text:         #201d18;
  --text-muted:   #5a544a;
  /* Darkened from #7f786c (3.57:1 on --bg-elevated, under AA) to clear
     4.5:1 where panel labels/hints actually sit. */
  --text-dim:     #6e685e;

  /* 10% highlight — deep enough that --grad (output card border, cosmetic
     titles, etc.) stays readable on the paper field; the lightest grad
     stop keeps ≥3:1 against --bg. */
  --accent:       #6f5229;
  --accent-soft:  rgba(111, 82, 41, 0.14);
  --accent-ink:   #fff; /* 7.20:1 — same as the unset fallback, defined for completeness */

  --warning-bg:   rgba(192, 33, 33, 0.08);
  --warning-fg:   #9c2424;
  --warning-bd:   rgba(192, 33, 33, 0.25);
  --success-fg:   #2e7d3a;

  --grad: linear-gradient(135deg, #b08347 0%, #7d5e33 45%, #3a2913 100%);

  --caveman:        #201d18;
  --caveman-option: #8a6a3f;
  --title-edge:   rgba(0, 0, 0, 0.62);
  --title-plate:    rgba(0, 0, 0, 0.045);
  --title-plate-bd: rgba(0, 0, 0, 0.085);
}

[data-theme="fire"] {
  /* FIRE — dark elemental (Store unlock). Neutral charcoal field; warm
     ember highlight. */
  /* 60% neutral */
  --bg:           #16171a;
  /* 30% contrast */
  --bg-elevated:  #1f2024;
  --bg-input:     #1a1b1e;

  --border:       #2e2f34;
  --border-hover: #3a3b40;

  --text:         #ececec;
  --text-muted:   #9a9a9a;
  /* Bumped from #6e6e6e (~3.5:1 on --bg, under AA) to ~5:1 so the dim
     labels, footer note, and nav text stay legible without losing hierarchy. */
  --text-dim:     #8a8a8a;

  /* 10% highlight */
  --accent:       #ff8a3d;
  --accent-soft:  rgba(255, 138, 61, 0.12);
  --accent-ink:   #1a1206; /* white-on-#ff8a3d was 2.35:1 — the worst of the six, now 7.90:1 */

  --warning-bg:   rgba(255, 77, 77, 0.08);
  --warning-fg:   #ffb8b8;
  --warning-bd:   rgba(255, 77, 77, 0.25);
  --success-fg:   #7fd989;

  --grad: linear-gradient(135deg, #ffd84a 0%, #ff8a3d 50%, #ff4d4d 100%);

  --caveman:        #ececec;
  --caveman-option: #ffb43a;
  --title-edge:   rgba(255, 255, 255, 0.5);
  --title-plate:    rgba(255, 255, 255, 0.055);
  --title-plate-bd: rgba(255, 255, 255, 0.10);
}

[data-theme="air"] {
  /* AIR — light. Neutral warm-white field (was cream, which tinted the
     whole 60%); pink → magenta stays pure highlight. The 30% layer is
     deliberately deep for a light theme: near-white inputs on a dimmer
     field with firm borders, so buttons/cards read at a glance. */
  /* 60% neutral */
  --bg:           #f6f2ee;
  /* 30% contrast */
  --bg-elevated:  #ece5e0;
  --bg-input:     #fffefd;

  --border:       #cabcb5;
  --border-hover: #a3928a;

  --text:         #211c1e;
  --text-muted:   #5c5055;
  /* Darkened from #837579 (3.52:1 on --bg-elevated, under AA) to clear
     4.5:1 where panel labels/hints actually sit. */
  --text-dim:     #6f6367;

  /* 10% highlight (deep enough for AA link text on the neutral field) */
  --accent:       #bc2066;
  --accent-soft:  rgba(188, 32, 102, 0.14);
  --accent-ink:   #fff; /* 5.94:1 — same as the unset fallback, defined for completeness */

  --warning-bg:   rgba(192, 33, 33, 0.08);
  --warning-fg:   #9c2424;
  --warning-bd:   rgba(192, 33, 33, 0.25);
  --success-fg:   #2e7d3a;

  /* First stop darkened from #f9a8d4 (1.63:1 on --bg, illegible as gradient
     text) to clear 3:1, same discipline Light's grad already applies. */
  --grad: linear-gradient(135deg, #b37999 0%, #ec4899 50%, #a21caf 100%);

  --caveman:        #1a1a1a;
  --caveman-option: #be185d;
  --title-edge:   rgba(0, 0, 0, 0.62);
  --title-plate:    rgba(0, 0, 0, 0.045);
  --title-plate-bd: rgba(0, 0, 0, 0.085);
}

[data-theme="water"] {
  /* WATER — light. Cool near-white field; surfaces are neutral slate (was
     sky-tinted #f0f9ff, which leaked the highlight into the 30% layer);
     ocean blue stays pure highlight. Deep borders + white inputs keep
     interactive edges crisp on the light field. */
  /* 60% neutral */
  --bg:           #f6f8fa;
  /* 30% contrast */
  --bg-elevated:  #e8edf3;
  --bg-input:     #ffffff;

  --border:       #becbd9;
  --border-hover: #8b9fb5;

  --text:         #0f172a;
  --text-muted:   #43526a;
  /* Darkened from #71829a (3.33:1 on --bg-elevated, under AA) to clear
     4.5:1 where panel labels/hints actually sit. */
  --text-dim:     #5e6c80;

  /* 10% highlight (deep enough for AA link text on the neutral field) */
  --accent:       #0270aa;
  --accent-soft:  rgba(2, 112, 170, 0.14);
  --accent-ink:   #fff; /* 5.38:1 — same as the unset fallback, defined for completeness */

  --warning-bg:   rgba(192, 33, 33, 0.08);
  --warning-fg:   #9c2424;
  --warning-bd:   rgba(192, 33, 33, 0.25);
  --success-fg:   #15803d;

  /* First stop darkened from #7dd3fc (1.57:1 on --bg, illegible as gradient
     text) to clear 3:1, same discipline Light's grad already applies. */
  --grad: linear-gradient(135deg, #5996b3 0%, #0ea5e9 50%, #0c4a6e 100%);

  --caveman:        #0f172a;
  --caveman-option: #0270aa;
  --title-edge:   rgba(0, 0, 0, 0.62);
  --title-plate:    rgba(0, 0, 0, 0.045);
  --title-plate-bd: rgba(0, 0, 0, 0.085);
}

[data-theme="earth"] {
  /* EARTH — light. Neutral warm-stone field (was saturated tan, which made
     the theme color ~100% of the page); richer sienna as the highlight.
     Strong clay borders + bright inputs so controls stand off the stone. */
  /* 60% neutral */
  --bg:           #f0ebdf;
  /* 30% contrast */
  --bg-elevated:  #e2d9c3;
  --bg-input:     #fbf8ee;

  --border:       #bcae89;
  --border-hover: #93845c;

  --text:         #27200f;
  --text-muted:   #594e32;
  /* Darkened from #7d7355 (3.35:1 on --bg-elevated, under AA) to clear
     4.5:1 where panel labels/hints actually sit. */
  --text-dim:     #685f47;

  /* 10% highlight */
  --accent:       #82492a;
  --accent-soft:  rgba(130, 73, 42, 0.14);
  --accent-ink:   #fff; /* 7.15:1 — same as the unset fallback, defined for completeness */

  --warning-bg:   rgba(155, 33, 33, 0.08);
  --warning-fg:   #7a2424;
  --warning-bd:   rgba(155, 33, 33, 0.25);
  /* Nudged from #2e7d3a (4.29:1 on --bg, just under AA) to clear 4.5:1. */
  --success-fg:   #2d7938;

  /* First stop darkened from #d4b896 (1.59:1 on --bg, illegible as gradient
     text) to clear 3:1, same discipline Light's grad already applies. */
  --grad: linear-gradient(135deg, #99846c 0%, #8b5a3c 50%, #4a2c1a 100%);

  --caveman:        #2d1f0d;
  --caveman-option: #82492a;
  --title-edge:   rgba(0, 0, 0, 0.62);
  --title-plate:    rgba(0, 0, 0, 0.045);
  --title-plate-bd: rgba(0, 0, 0, 0.085);
}

/* ---------- Base ---------- */
body {
  font-family: var(--fsans);
  background: var(--bg);
  color: var(--text);
  margin: 0;
  /* Asymmetric padding biases the centered container upward — less space
     at the top, more at the bottom, so content lands a couple of lines
     above the true vertical center. */
  padding: 1rem 1rem 6rem;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  transition: background 0.2s, color 0.2s;
}

.container {
  max-width: 720px;
  width: 100%;
  margin: auto;   /* vertical + horizontal centering inside body flex */
}

/* ---------- Theme switcher (fixed top-right, hover-expanding pill) ----------
   The CONTAINER is the visible shape — a single pill (rectangle with
   semicircular ends, border-radius: 9999px). It carries the gradient
   ring and bg-elevated fill. Individual option buttons are transparent
   icon containers that live inside the pill.
   - Collapsed: container is 40×40 → renders as a single circle
     (because length == diameter when only the active option is visible).
   - Expanded (:hover or :focus-within): non-active options grow from
     width 0 → 40px. The container auto-sizes to fit them, smoothly
     stretching from circle into a pill. The gradient ring stretches too.
   script.js keeps the active option as the last DOM child so the
   collapsed circle always sits flush against the right corner. */

.theme-switcher {
  position: fixed;
  top: 1rem;
  right: 1rem;
  display: flex;
  align-items: center;
  height: 40px;
  /* Pill shape — radius is huge, so corners are always fully rounded */
  border-radius: 9999px;
  /* Two-background trick: solid fill inside, gradient acts as the border */
  border: 2px solid transparent;
  background:
    linear-gradient(var(--bg-elevated), var(--bg-elevated)) padding-box,
    var(--grad) border-box;
  /* Clip option icons to the pill outline while they grow during animation */
  overflow: hidden;
  z-index: 100;
}

.theme-option {
  width: 40px;
  height: 40px;
  border: 0;
  background: transparent;
  color: var(--text);
  font-size: 18px;
  cursor: pointer;
  display: grid;
  place-items: center;
  flex-shrink: 0;
  overflow: hidden;
  padding: 0;
  transition: width 0.25s ease, opacity 0.2s ease,
              background 0.2s, transform 0.12s;
}

/* Collapsed: only the active option is visible. Others have zero width. */
.theme-option:not(.active) {
  width: 0;
  opacity: 0;
  pointer-events: none;
}

/* Expanded: every option visible inside the pill. */
.theme-switcher:hover .theme-option:not(.active),
.theme-switcher:focus-within .theme-option:not(.active) {
  width: 40px;
  opacity: 1;
  pointer-events: auto;
}

/* In the expanded pill, give the active option a subtle accent-tinted
   disc so the user can tell which theme is currently selected. */
.theme-switcher:hover .theme-option.active,
.theme-switcher:focus-within .theme-option.active {
  background: var(--accent-soft);
  border-radius: 50%;
}

.theme-option:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  border-radius: 50%;
}

/* Unowned Store themes are hidden from the pickers entirely (hud.js sets
   [hidden] on their buttons) — they appear only once the bundle is owned. */

/* ---------- Header (caveman + title + subtitle) ---------- */
.page-header {
  text-align: center;
  margin-bottom: 1.25rem;
}

.caveman-svg {
  width: 72px;
  height: auto;
  display: block;
  margin: 0 auto 0.5rem;
  /* Caveman shapes inherit fill/stroke from currentColor */
  color: var(--caveman);
  fill: currentColor;
  stroke: currentColor;
}
/* Eyes / mouth / club separator dots cut out against background */
.caveman-svg .eye {
  fill: var(--bg);
  stroke: none;
}

/* Ragged last lines on a wrapped heading read as an accident, not a
   layout choice — text-wrap: balance evens the line lengths. Pure
   progressive enhancement (unsupported browsers just wrap normally). */
h1, h2, h3 {
  text-wrap: balance;
}

h1 {
  font-family: var(--fdisplay);
  font-size: 32px;
  font-weight: 700;
  margin: 0 0 0.35rem;
  color: var(--text);
  letter-spacing: -0.01em;
}

.subtitle {
  text-align: center;
  color: var(--text-muted);
  font-size: 14px;
  margin: 0;
}

/* ---------- Mode toggle (Character / Study) ----------
   The underline used to be a static ::after on the active button. Now it's
   ONE pseudo-element on the .mode-toggle parent, and its left/width are
   driven by CSS custom properties that script.js updates whenever the
   active button changes. Transitioning those properties produces a smooth
   gradient bar that slides between the two buttons. */
.mode-toggle {
  display: flex;
  gap: var(--space-4);
  margin: 1.5rem auto;
  padding: var(--space-4);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  width: fit-content;
  position: relative; /* anchor for the sliding underline */
}
.mode-btn {
  background: transparent;
  border: none;
  border-radius: 6px;
  color: var(--text-muted);
  cursor: pointer;
  padding: var(--space-6) var(--space-14);
  font-size: 13px;
  font-family: inherit;
  transition: color 0.2s, background 0.2s;
  position: relative;
}
.mode-btn:hover { color: var(--text); }
.mode-btn.active {
  background: var(--bg-input);
  color: var(--text);
  font-weight: 600;
}

/* The sliding gradient underline */
.mode-toggle::after {
  content: '';
  position: absolute;
  bottom: 6px;
  height: 2px;
  left: var(--slider-x, 18px);
  width: var(--slider-w, 90px);
  background: var(--grad);
  border-radius: 1px;
  transition: left 0.28s cubic-bezier(0.4, 0, 0.2, 1),
              width 0.28s cubic-bezier(0.4, 0, 0.2, 1);
  pointer-events: none;
}

/* ---------- The "Explain X to a Y" sentence builder ---------- */
.inputs {
  display: flex;
  align-items: center;
  justify-content: center;
  /* gap replaced with sibling-margin so we can animate the space away
     when a child collapses on mode switch (gap can't transition per-item) */
  font-size: 17px;
  flex-wrap: wrap;
  margin-bottom: 1.5rem;
}
.inputs > * + * { margin-left: var(--space-8); }

select,
input[type="text"] {
  padding: var(--space-7) 11px;
  font-size: 15px;
  border: 1px solid var(--border);
  background: var(--bg-input);
  color: var(--text);
  border-radius: 7px;
  font-family: inherit;
  min-width: 180px;
  outline: none;
  transition: border-color 0.15s, background 0.15s;
}
select:hover,
input[type="text"]:hover { border-color: var(--border-hover); }
select:focus,
input[type="text"]:focus { border-color: var(--accent); }

select option,
select optgroup { background: var(--bg-input); color: var(--text); }
/* Caveman option keeps a bold, theme-tinted highlight in every palette */
select option.caveman-option {
  color: var(--caveman-option);
  font-weight: 700;
}

/* -------- Mode-switch morph (Character ↔ Study) --------
   The four character-only elements collapse fluidly when the mode changes
   to Study: max-width + margin + padding + opacity all animate together,
   so the visible neighbors slide smoothly into the freed space instead of
   teleporting. `display 0.3s allow-discrete` flips display:none in/out at
   the boundaries so the elements actually leave/enter the layout cleanly.
   `@starting-style` provides the "from collapsed" state when they re-enter. */

/* Default max-widths and max-heights give each element a concrete starting
   size to animate down from (you can't transition between `auto`/`none`
   and `0`). Heights are generous — they don't constrain in normal use. */
#character-btn    { max-width: 320px; max-height: 60px; }
#to-a-word        { max-width: 100px; max-height: 40px; }
#surprise-btn     { max-width: 240px; max-height: 60px; }

#character-btn, #to-a-word, #surprise-btn {
  overflow: hidden;
  transition: opacity 0.3s ease,
              max-width 0.3s ease,
              max-height 0.3s ease,
              min-width 0.3s ease,
              margin 0.3s ease,
              padding 0.3s ease,
              border-width 0.3s ease,
              display 0.3s allow-discrete;
}

/* In Study Mode, also collapse the vertical dimensions (max-height +
   padding-top/bottom + border-top/bottom). This makes the element fully
   zero-sized by the time `display: none` kicks in, so there's no leftover
   vertical extent to disappear at the end of the transition. */
body[data-mode="plain"] #character-btn,
body[data-mode="plain"] #to-a-word,
body[data-mode="plain"] #surprise-btn {
  opacity: 0;
  max-width: 0;
  max-height: 0;
  min-width: 0;
  margin: 0;
  padding: 0;
  border-width: 0;
  pointer-events: none;
  display: none;
}

@starting-style {
  body[data-mode="character"] #character-btn,
  body[data-mode="character"] #to-a-word,
  body[data-mode="character"] #surprise-btn {
    opacity: 0;
    max-width: 0;
    max-height: 0;
    margin: 0;
    padding: 0;
    border-width: 0;
  }
}

/* Prefix word: two stacked text spans crossfade between "Explain" and
   "Break down" so there's never a moment where the slot is empty. Using
   inline-grid with all children in grid-area 1/1 places them on top of
   each other; the grid auto-sizes to the larger ("Break down"). */
#prefix-word {
  display: inline-grid;
  min-width: 5.5em;
  vertical-align: middle;
}
.prefix-text {
  grid-area: 1 / 1;
  text-align: center;
  transition: opacity 0.28s ease;
}
.prefix-text[data-when="character"] { opacity: 1; }
.prefix-text[data-when="plain"]     { opacity: 0; }

body[data-mode="plain"] .prefix-text[data-when="character"] { opacity: 0; }
body[data-mode="plain"] .prefix-text[data-when="plain"]     { opacity: 1; }

/* ---------- Action buttons ---------- */
.buttons {
  display: flex;
  /* gap replaced with sibling-margin (same reason as .inputs above) */
  justify-content: center;
  margin-bottom: 1.5rem;
  flex-wrap: wrap;
}
.buttons > * + * { margin-left: var(--space-10); }
button {
  padding: var(--space-8) var(--space-16);
  font-size: 14px;
  border: 1px solid var(--border);
  background: var(--bg-elevated);
  color: var(--text);
  border-radius: 7px;
  cursor: pointer;
  font-family: inherit;
  transition: background 0.15s, border-color 0.15s, transform 0.05s;
}
button:hover { background: var(--bg-input); border-color: var(--border-hover); }
button:active { transform: translateY(1px); }
button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}

button.primary {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-soft);
  font-weight: 600;
}
button.primary:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
  filter: brightness(1.1);
}

/* Press-down animation: scripts add `.pressed` momentarily on click,
   producing a quick "click feedback" bounce. Independent of :active so
   it fires for keyboard activation too. */
button.pressed {
  animation: press-down 0.22s ease-out;
}
@keyframes press-down {
  0%   { transform: scale(1)    translateY(0);  }
  40%  { transform: scale(0.93) translateY(2px); }
  100% { transform: scale(1)    translateY(0);  }
}

/* Sparkles — small particles emitted around the Explain button on click.
   Each sparkle is a fixed-position dot that flies outward (via per-element
   --dx / --dy custom properties) and fades out. */
.sparkle {
  position: fixed;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  pointer-events: none;
  z-index: 200;
  animation: sparkle-burst 0.55s ease-out forwards;
}
@keyframes sparkle-burst {
  0% {
    transform: translate(-50%, -50%) scale(1);
    opacity: 1;
  }
  60% {
    opacity: 0.9;
  }
  100% {
    transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(0.2);
    opacity: 0;
  }
}

/* ---------- Output card ---------- */
.output-card {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  position: relative;
  margin-top: 0.5rem;
}
.output-card::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 2px;
  background: var(--grad);
}
.output-card-inner { padding: 1.4rem 1.5rem 1.25rem; }

.output-tags {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  margin-bottom: 0.75rem;
  font-size: 13px;
  color: var(--text-muted);
  flex-wrap: wrap;
}
.tag {
  background: var(--bg-input);
  border: 1px solid var(--border);
  padding: 3px var(--space-10);
  border-radius: 6px;
  color: var(--text);
}
.tag.mode-tag {
  font-size: 12px;
  color: var(--text-dim);
  background: transparent;
  border: none;
  padding: 0;
}
.arrow { color: var(--accent); font-weight: 700; }

.rendered {
  font-size: 15px;
  line-height: 1.7;
  color: var(--text);
}
.rendered p { margin: 0.25rem 0; }
.rendered strong {
  display: block;
  margin-top: 0.75rem;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--accent);
}
.rendered strong:first-child { margin-top: 0; }

.result-actions {
  margin-top: 1rem;
  display: flex;
  gap: var(--space-8);
}
.result-actions button {
  padding: var(--space-6) var(--space-12);
  font-size: 13px;
}

/* ---------- Loading state (streaming pre-first-token) ---------- */
.loading {
  display: flex;
  align-items: center;
  gap: var(--space-10);
  padding: 1rem 1.25rem;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text-muted);
  font-size: 14px;
}
.loading-dots {
  display: inline-flex;
  gap: var(--space-4);
}
.loading-dots span {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--accent);
  animation: pulse 1.2s ease-in-out infinite;
}
.loading-dots span:nth-child(2) { animation-delay: 0.15s; }
.loading-dots span:nth-child(3) { animation-delay: 0.30s; }
@keyframes pulse {
  0%, 80%, 100% { opacity: 0.3; transform: scale(0.85); }
  40%           { opacity: 1;   transform: scale(1.1);  }
}

/* Subtle blinking caret while tokens are still arriving */
.streaming-cursor {
  display: inline-block;
  width: 7px;
  margin-left: var(--space-2);
  background: var(--accent);
  height: 1.05em;
  vertical-align: -0.15em;
  animation: caret 0.9s steps(1) infinite;
}
@keyframes caret {
  50% { opacity: 0; }
}

/* ---------- Friendly error / warning ---------- */
.warning {
  padding: var(--space-12) var(--space-16);
  background: var(--warning-bg);
  color: var(--warning-fg);
  border: 1px solid var(--warning-bd);
  border-radius: 7px;
  font-size: 14px;
  text-align: center;
  margin-top: 0.5rem;
}

/* ---------- Footer-note (below buttons, mode-aware text) ---------- */
.footer-note {
  margin: 1.5rem 0 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
  font-size: 12px;
  color: var(--text-dim);
  text-align: center;
}

/* ---------- Page footer (credits + access-code) ---------- */
.page-footer {
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
  font-size: 12px;
  color: var(--text-dim);
  text-align: center;
}
.page-footer p { margin: 0.25rem 0; }
.page-footer a {
  color: var(--text-muted);
  text-decoration: none;
  border-bottom: 1px dotted var(--text-dim);
}
.page-footer a:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* Daily-usage badge pinned to top-left. Rounded box bordered in the
   active theme's accent color. Tabular numerals keep the countdown
   digits from jittering. Hidden when an access code is in use. */
.usage-counter {
  position: fixed;
  top: 1rem;
  left: 1rem;
  padding: var(--space-6) var(--space-12);
  font-size: 12px;
  color: var(--text);
  background: var(--bg-elevated);
  border: 2px solid var(--accent);
  border-radius: var(--radius-sm);
  font-feature-settings: "tnum";
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  user-select: none;
  z-index: 100;
  transition: border-color 0.2s ease, color 0.2s ease;
}
.usage-counter.depleted {
  color: var(--warning-fg);
  border-color: var(--warning-fg);
}
.usage-counter[hidden] { display: none; }

/* On narrow screens, tuck the badge against the edge so it doesn't
   overlap the centered caveman illustration too aggressively. */
@media (max-width: 560px) {
  .usage-counter {
    top: 0.75rem;
    left: 0.75rem;
    font-size: 11px;
    padding: var(--space-4) var(--space-9);
  }
}

.access-code-status {
  margin-left: var(--space-6);
  font-weight: 600;
}
.access-code-status.accepted { color: var(--success-fg); }
.access-code-status.rejected { color: var(--warning-fg); }

/* Subtle "clear" link-button beside the access-code status — easy to find
   once you're looking for it, easy to overlook otherwise. Styled like the
   footer's dotted-underline links rather than a full button so it doesn't
   compete with the "accepted ✓" status for attention. */
.access-code-clear {
  margin-left: var(--space-6);
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  font-size: 11px;
  color: var(--text-dim);
  text-decoration: underline dotted;
  text-underline-offset: 2px;
  cursor: pointer;
  opacity: 0.65;
  transition: opacity 0.15s ease, color 0.15s ease;
}
.access-code-clear:hover {
  opacity: 1;
  color: var(--accent);
}
.access-code-clear[hidden] { display: none; }

.access-code-panel {
  margin-top: 0.5rem;
  display: flex;
  gap: var(--space-6);
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
}
/* The HTML `hidden` attribute is normally equivalent to `display: none`,
   but the .access-code-panel rule above declares `display: flex` and wins
   on specificity. Restore the hidden behaviour explicitly. */
.access-code-panel[hidden] { display: none; }
.access-code-panel input {
  font-size: 13px;
  padding: var(--space-6) var(--space-10);
  min-width: 200px;
}
.access-code-panel button {
  padding: var(--space-6) var(--space-12);
  font-size: 13px;
}

/* ---------- Version badge + patch notes/roadmap pill (bottom-center) ----------
   Floats centered along the bottom edge so it's easy to find but light
   enough not to fight the content. Opacity bumps on hover for a clear
   "I can be clicked" affordance. */
/* The bottom nav bar. (Class is still .version-badge for historical
   reasons — it predates the move of the version number into the footer
   credit.) At rest, it's a small circular pill containing only the home
   icon. On hover or focus-within, two .version-side groups slide out
   from behind the pill to reveal the rest of the nav:
   patch notes · about · 🏠 · roadmap · feedback */
.version-badge {
  position: fixed;
  /* Raised well clear of the viewport edge so it floats rather than hugs. */
  bottom: 1.75rem;
  left: 50%;
  transform: translateX(-50%);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12.5px;
  letter-spacing: 0.02em;
  color: var(--text-dim);
  background: var(--bg-elevated);
  border: 1px solid var(--accent-soft);
  border-radius: 999px;
  /* Uniform padding so the collapsed state (just the home icon) is a
     proper circle, not an oval. */
  padding: 7px;
  /* Resting opacity is comfortably visible without screaming; hover
     pushes it to full 1.0 for the "I can be clicked" affordance. */
  opacity: 0.85;
  user-select: none;
  z-index: 50;
  transition: opacity 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
  display: inline-flex;
  align-items: center;
  /* No parent gap — sides own their internal gap and use margin-when-
     expanded to space themselves from the home icon. With sides at
     width:0 collapsed, this lets the badge shrink to a perfect circle. */
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12);
  white-space: nowrap;
}
.version-badge:hover,
.version-badge:focus-within,
.version-badge.is-open {
  opacity: 1;
  border-color: var(--accent);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
}

.version-sep { opacity: 0.45; }

.version-link,
.version-link:visited {
  color: inherit;
  text-decoration: underline;
  text-decoration-style: dotted;
  text-decoration-color: currentColor;
  text-underline-offset: 2px;
  position: relative;
}
.version-link:hover {
  color: var(--accent);
  text-decoration-color: var(--accent);
}

/* "You are here" treatment for the link representing the current page.
   Matches the home icon's .is-current styling — accent color, no hover
   color change, default cursor — so the user can see at a glance which
   page they're on without thinking about it. */
.version-link.is-current,
.version-link.is-current:visited,
.version-link.is-current:hover {
  color: var(--accent);
  text-decoration-color: var(--accent);
  cursor: default;
}

/* ---------- Collapsing side groups ----------
   At rest, both sides collapse to width 0 / opacity 0, leaving only the
   central home icon visible — the bar reads as a small round button.
   On hover or focus-within, both sides slide out to identical widths so
   the home icon stays dead-centered. Each side's justify-content pushes
   its content inward toward the home icon, so the slack from the equal-
   width slots ends up on the outer edges where the eye doesn't notice.
   The sides also sit 3px above the centerline and slightly less opaque
   than the home icon, so they read as floating accents. */
.version-side {
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  overflow: hidden;
  white-space: nowrap;
  width: 0;
  opacity: 0;
  margin: 0;
  transform: translateY(-3px);
  transition: width   0.35s cubic-bezier(0.4, 0, 0.2, 1),
              opacity 0.22s ease,
              margin  0.3s  cubic-bezier(0.4, 0, 0.2, 1);
}
.version-side-left  { justify-content: flex-end;   }
.version-side-right { justify-content: flex-start; }

.version-badge:hover .version-side,
.version-badge:focus-within .version-side,
.version-badge.is-open .version-side {
  /* Tuned to comfortably fit each side's content at 12.5px monospace.
     Left side "patch notes · about ·" with internal 8px gaps measures
     ~170px (plus ~12px when the red dot is visible inside the link);
     right side "· roadmap · feedback" measures ~155px. 185px covers
     both with buffer — update if link labels change. */
  width: 185px;
  opacity: 0.8;
}
.version-badge:hover .version-side-left,
.version-badge:focus-within .version-side-left,
.version-badge.is-open .version-side-left  { margin-right: var(--space-8); }
.version-badge:hover .version-side-right,
.version-badge:focus-within .version-side-right,
.version-badge.is-open .version-side-right { margin-left:  var(--space-8); }

/* ---------- Home icon in the bottom nav ----------
   Always visible — the only element that survives the collapsed state.
   Renders as a link on every page EXCEPT index.html, where it's a
   <span> with .is-current (still visible, but non-interactive and
   accent-colored as a "you're here" cue). */
.version-home {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* F15: the 14px svg was the entire hit box — under the 24px AA floor
     (SC 2.5.8). padding on the link itself (not .version-badge's own
     padding, which is tuned to keep the *collapsed* pill "a proper
     circle") grows the tappable box to 24px; since it's uniform on all
     sides, the whole badge grows by the same amount everywhere and
     stays circular, just a touch bigger. */
  padding: 5px;
  /* Strip the dotted underline the text links use — the icon is its own
     affordance. */
  text-decoration: none;
  border-bottom: none;
  color: var(--text-dim);
  transition: color 0.15s ease, transform 0.15s ease;
  flex-shrink: 0;
}
.version-home svg {
  width: 14px;
  height: 14px;
  display: block;
}
a.version-home:hover {
  color: var(--accent);
  transform: translateY(-1px);
}
.version-home.is-current {
  color: var(--accent);
  cursor: default;
}

/* When the pill expands, the home icon swells and lifts clear of the bar
   so it reads as the anchor the rest of the nav fans out from, rather
   than just another item in the row. */
.version-badge:hover .version-home,
.version-badge:focus-within .version-home,
.version-badge.is-open .version-home {
  transform: scale(1.4) translateY(-2px);
  color: var(--accent);
}

/* Red-dot indicator for unseen patch notes. Lives inside the "patch
   notes" link in the bottom nav. Toggled by inline scripts on each page
   based on localStorage 'etac-seen-version' vs the footer's
   .credit-version text. A gentle pulse draws the eye without being
   obnoxious. */
.version-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #ef4444;
  margin-left: var(--space-5);
  vertical-align: middle;
  box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.55);
  animation: version-dot-pulse 1.8s ease-out infinite;
}
.version-dot[hidden] { display: none; }

/* Edge variant — pinned to the badge's top-right corner so it stays
   visible while the nav is collapsed to just the home button (when
   the in-link dot is hidden inside a width:0 side group). Two dots,
   same red pulse, toggled together by the inline script. */
.version-dot.is-edge {
  position: absolute;
  top: -2px;
  right: -2px;
  margin: 0;
  transition: opacity 0.15s ease;
}

/* Once the pill expands, the in-link dot beside "patch notes" takes over —
   hide the corner dot so the notification reads as moving into the link
   rather than staying stuck to the pill. */
.version-badge:hover .version-dot.is-edge,
.version-badge:focus-within .version-dot.is-edge,
.version-badge.is-open .version-dot.is-edge {
  opacity: 0;
}

@keyframes version-dot-pulse {
  0%   { box-shadow: 0 0 0 0   rgba(239, 68, 68, 0.55); }
  70%  { box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); }
  100% { box-shadow: 0 0 0 0   rgba(239, 68, 68, 0); }
}

@media (max-width: 560px) {
  .version-badge {
    bottom: 1.25rem;
    font-size: 11px;
    /* Slightly tighter circular padding on narrow screens */
    padding: 6px;
  }
  .version-home svg { width: 13px; height: 13px; }
  /* 13px icon + 5px padding lands at 23px — one px under the AA floor. */
  .version-home { padding: 5.5px; }
  /* Narrower expanded sides on mobile to keep the open bar from
     overflowing tight viewports. */
  .version-badge:hover .version-side,
  .version-badge:focus-within .version-side,
  .version-badge.is-open .version-side { width: 165px; }
}

/* ---------- About page content ---------- */
.about-content {
  margin-top: 0.5rem;
}
.about-content section {
  margin-bottom: 1.75rem;
}
.about-content h2 {
  font-size: 16px;
  font-weight: 700;
  color: var(--accent);
  margin: 0 0 0.5rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.about-content p,
.about-content ul,
.about-content ol {
  font-size: 15px;
  line-height: 1.65;
  color: var(--text);
  margin: 0 0 0.6rem;
}
.about-content ul,
.about-content ol {
  padding-left: 1.25rem;
}
.about-content li {
  margin-bottom: 0.4rem;
}
.about-content code {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  padding: 1px var(--space-6);
  border-radius: var(--radius-xs);
  font-size: 13px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
}
.about-content .screenshot-placeholder {
  font-size: 13px;
  color: var(--text-dim);
  font-style: italic;
}
.about-content .disclaimer-section {
  background: var(--accent-soft);
  border: 1px solid var(--accent);
  padding: 1rem 1.25rem;
  border-radius: var(--radius-sm);
}
.about-content .disclaimer-section h2 {
  margin-top: 0;
}

/* ---------- Shared small "feedback" button ----------
   Used inline on the roadmap aside and as a standalone button on the
   about page. Deliberately understated — sits inline with surrounding
   prose without screaming for attention, then warms to the accent color
   on hover. */
.feedback-link,
.feedback-link:visited {
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  padding: var(--space-2) var(--space-8);
  font-size: 11px;
  font-weight: 500;
  /* Match the muted tone of surrounding aside-foot prose so the button
     reads as part of the sentence, not a separate UI element. */
  color: var(--text-dim);
  background: var(--bg-input);
  border: 1px solid var(--border);
  border-radius: 5px;
  text-decoration: none;
  cursor: pointer;
  line-height: 1.4;
  transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.feedback-link:hover {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-soft);
}
.feedback-link:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-soft);
}

/* Standalone (about-page) variant: a small centered prompt with a touch
   more breathing room around it. */
.feedback-prompt {
  text-align: center;
  margin: 1.5rem 0 0;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
  font-size: 13px;
  color: var(--text-dim);
}
.feedback-prompt .feedback-link { margin-left: var(--space-6); }

/* ---------- Patch notes page ---------- */
.patch-notes-content {
  margin-top: 0.5rem;
}
.patch-version {
  margin-bottom: 1.75rem;
  border-left: 2px solid var(--accent);
  padding-left: 1rem;
}
.patch-version h2 {
  font-size: 18px;
  font-weight: 700;
  color: var(--accent);
  margin: 0 0 0.5rem;
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-10);
}
.patch-date {
  font-size: 12px;
  font-weight: 400;
  color: var(--text-dim);
}
.patch-version ul {
  list-style: disc;
  padding-left: 1.25rem;
  font-size: 15px;
  line-height: 1.65;
  color: var(--text);
  margin: 0;
}
.patch-version li {
  margin-bottom: 0.35rem;
}

/* ---------- Roadmap page ----------
   Mirrors the patch-notes column treatment: each status group gets its
   own left-border accent in the color that matches its urgency. */
.roadmap-content { margin-top: 0.5rem; }

.roadmap-section {
  margin-bottom: 1.75rem;
  border-left: 2px solid var(--text-dim);
  padding-left: 1rem;
}
.roadmap-section h2 {
  font-size: 18px;
  font-weight: 700;
  margin: 0 0 0.6rem;
  display: flex;
  align-items: center;
  gap: var(--space-10);
  color: var(--text);
}
.roadmap-status {
  display: inline-block;
  padding: 3px var(--space-10);
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  border: 1px solid currentColor;
  background: var(--accent-soft);
}
.roadmap-section.is-now    { border-left-color: var(--accent); }
.roadmap-section.is-now    .roadmap-status { color: var(--accent); }
.roadmap-section.is-next   .roadmap-status { color: var(--text-dim); background: transparent; }
.roadmap-section.is-maybe  { border-left-style: dashed; }
.roadmap-section.is-maybe  .roadmap-status {
  color: var(--text-dim);
  background: transparent;
  opacity: 0.85;
}
.roadmap-section ul {
  list-style: disc;
  padding-left: 1.25rem;
  font-size: 15px;
  line-height: 1.65;
  color: var(--text);
  margin: 0;
}
.roadmap-section li { margin-bottom: 0.4rem; }
.roadmap-section li strong { color: var(--accent); }

/* ---------- Mobile responsiveness ---------- */
@media (max-width: 560px) {
  body { padding: 2rem 0.75rem 5rem; }

  /* Shrink + tuck the theme switcher on small screens. (These rules used to
     target a stale `.theme-toggle` class that no longer exists in the markup,
     so they never applied — the switcher stayed full-size on mobile.) */
  .theme-switcher {
    top: 0.75rem;
    right: 0.75rem;
    height: 36px;
  }
  .theme-option { width: 36px; height: 36px; font-size: 16px; }
  /* Keep the expanded options the same diameter as the collapsed circle so
     the pill stays a tidy rounded shape on phones. */
  .theme-switcher:hover .theme-option:not(.active),
  .theme-switcher:focus-within .theme-option:not(.active) { width: 36px; }

  h1 { font-size: 26px; }
  .caveman-svg { width: 64px; }

  /* Stack the sentence-builder pieces vertically */
  .inputs {
    flex-direction: column;
    align-items: stretch;
  }
  .inputs > * + * { margin-left: 0; margin-top: var(--space-6); }
  .inputs span { text-align: center; color: var(--text-muted); font-size: 14px; }
  select,
  input[type="text"] { min-width: 0; width: 100%; }

  /* Make buttons stack at narrow widths */
  .buttons { flex-direction: column; }
  .buttons > * + * { margin-left: 0; margin-top: var(--space-10); }
  .buttons button { width: 100%; }

  /* On mobile the morph collapses vertical space instead of horizontal */
  body[data-mode="plain"] #character-btn,
  body[data-mode="plain"] #to-a-word,
  body[data-mode="plain"] #surprise-btn {
    margin-top: 0;
    max-height: 0;
  }

  .output-card-inner { padding: 1.2rem 1rem 1rem; }
  .rendered { font-size: 14px; line-height: 1.65; }
}

/* ---------- Skip-to-content link ----------
   Injected by site.js on every page (see initSkipLink). Off-screen by
   default via the same clip-rect trick as .sr-only below, but :focus
   reverses every one of those properties so a keyboard user tabbing in
   sees it land top-left, above the HUD, before anything else on the page. */
.etac-skiplink {
  position: absolute; top: -1px; left: -1px;
  width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
  z-index: 999;
}
.etac-skiplink:focus {
  position: fixed; top: var(--space-10); left: var(--space-10);
  width: auto; height: auto; padding: var(--space-9) var(--space-16);
  margin: 0; overflow: visible; clip: auto; white-space: normal;
  border-radius: var(--radius-md);
  background: var(--accent); color: var(--accent-ink, #fff);
  font: 700 13px var(--fsans, sans-serif);
  text-decoration: none;
  outline: 2px solid var(--bg); outline-offset: 2px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, .3);
}

/* ---------- Visually-hidden helper (screen-reader-only) ----------
   Keeps content in the accessibility tree but off-screen. Used for the
   live-region status announcer so streaming doesn't spam the visible UI. */
.sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ---------- Output card entrance ----------
   Results rise in gently so they feel "delivered" rather than popping. */
@keyframes card-rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
.output-card { animation: card-rise 0.34s cubic-bezier(0.2, 0.8, 0.2, 1) both; }

/* ---------- Empty state (pre-first-explanation) ----------
   A friendly nudge in the output slot so the page doesn't read as blank.
   The bone bobs to give the caveman theme a little life. */
.empty-state {
  margin-top: 0.5rem;
  padding: 1.9rem 1.25rem;
  text-align: center;
  border: 1px dashed var(--border);
  border-radius: var(--radius-lg);
  background: color-mix(in oklab, var(--bg-elevated) 55%, transparent);
  animation: card-rise 0.3s ease both;
}
.empty-state-emoji {
  font-size: 30px;
  line-height: 1;
  margin-bottom: 0.5rem;
  animation: bob 3s ease-in-out infinite;
}
.empty-state-emoji img { width: 34px; height: 34px; display: inline-block; }
.empty-state p { margin: 0; font-size: 14px; color: var(--text-muted); }
.empty-state strong { color: var(--accent); }
@keyframes bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-5px); }
}

/* ---------- Caveman charm ----------
   A slow idle "breathing" sway, and a playful tilt when you hover the
   header — leans into the mascot without being distracting. */
.caveman-svg {
  transform-origin: 50% 90%;
  animation: caveman-breathe 4.5s ease-in-out infinite;
  transition: transform 0.25s ease;
}
@keyframes caveman-breathe {
  0%, 100% { transform: translateY(0)    rotate(0deg);    }
  50%      { transform: translateY(-2px) rotate(-1.2deg); }
}
.page-header:hover .caveman-svg {
  animation-play-state: paused;
  transform: rotate(3deg) scale(1.05);
}

/* ---------- Respect reduced-motion ----------
   Honor the OS "reduce motion" preference: neutralize animations and
   transitions site-wide, and drop the sparkle particles entirely. Placed
   last so it overrides everything above. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .sparkle { display: none !important; }
}

/* Inline formatting the owner's text editor can apply (content.js). These
   live outside the edit-mode block because visitors see them too. */
.t-accent { color: var(--accent); }
.t-dim { color: var(--text-dim); }

/* ================= Owner text-edit mode (content.js) =================
   Lives in style.css (not hub.css) because every page loads this sheet —
   including roadmap.html, which skips hub.css. The dashed outlines only
   appear for the owner with edit mode on; visitors never see any of it. */
body.etac-copyedit [data-copy] {
  outline: 1.5px dashed color-mix(in oklab, var(--accent) 55%, transparent);
  outline-offset: 3px; border-radius: var(--radius-xs); cursor: text;
}
body.etac-copyedit [data-copy]:hover { outline-style: solid; background: var(--accent-soft); }
[data-copy].etac-copyedit-active {
  outline: 2px solid var(--accent); outline-offset: 3px;
  background: var(--bg-input); cursor: text;
}
.etac-copytoast {
  position: fixed; left: 50%; bottom: 64px; transform: translate(-50%, 8px); z-index: 90;
  font: 650 13px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  color: var(--text); background: var(--bg-elevated);
  border: 1px solid var(--border); border-radius: var(--radius-pill); padding: var(--space-9) var(--space-16);
  box-shadow: 0 8px 24px rgba(0, 0, 0, .2);
  opacity: 0; pointer-events: none; transition: opacity .18s, transform .18s;
}
.etac-copytoast.is-show { opacity: 1; transform: translate(-50%, 0); }
.etac-copytoast.is-error { border-color: var(--warning-bd); color: var(--warning-fg); }
.etac-copyfab {
  position: fixed; right: 18px; bottom: 18px; z-index: 89;
  width: 44px; height: 44px; border-radius: 50%; font-size: 18px; cursor: pointer;
  background: var(--bg-elevated); border: 1px solid var(--border);
  box-shadow: 0 4px 12px rgba(0, 0, 0, .2);
}
.etac-copyfab:hover { border-color: var(--accent); }

/* Deleted patch-note points ('' override): gone for visitors, so the card
   shrinks to fit; ghosted with a restore button for the owner in edit mode. */
.etac-copyhidden { display: none !important; }
body.etac-copyedit .etac-copyhidden {
  display: list-item !important;
  opacity: .45; text-decoration: line-through;
}
body.etac-copyedit .pn-card li[data-copy] { position: relative; padding-right: 38px; }
.etac-copydel {
  position: absolute; right: 4px; top: 2px;
  width: 24px; height: 24px; border-radius: 50%;
  font: 700 14px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  color: var(--text-dim); background: var(--bg-elevated);
  border: 1px solid var(--border); cursor: pointer; padding: 0;
}
.etac-copydel:hover { border-color: var(--warning-bd); color: var(--warning-fg); }
.etac-copydel.is-restore { text-decoration: none; opacity: 1; }
.etac-copydel.is-restore:hover { border-color: var(--accent); color: var(--accent); }

/* "+ Add a point" under each data-copy-list (edit mode only — the button
   itself is injected by content.js, so no body gate needed). */
.etac-copyadd {
  margin-top: var(--space-10); padding: var(--space-6) var(--space-14); cursor: pointer;
  font: 650 12px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  color: var(--accent); background: transparent;
  border: 1.5px dashed color-mix(in oklab, var(--accent) 55%, transparent);
  border-radius: var(--radius-pill);
}
.etac-copyadd:hover { border-style: solid; background: var(--accent-soft); }

/* Formatting toolbar — floats bottom-center while an editor is open. */
.etac-copybar {
  position: fixed; left: 50%; bottom: 108px; transform: translateX(-50%);
  z-index: 90; display: flex; gap: var(--space-4); padding: var(--space-5);
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  box-shadow: 0 8px 24px rgba(0, 0, 0, .2);
}
.etac-copybar__btn {
  width: 32px; height: 32px; border-radius: 50%; cursor: pointer;
  border: 1px solid transparent; background: transparent;
  font: 700 14px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  color: var(--text); padding: 0;
}
.etac-copybar__btn:hover { border-color: var(--border); background: var(--bg-input); }
.etac-copybar__btn.is-accent { color: var(--accent); }
.etac-copybar__btn.is-dim { color: var(--text-dim); }
.etac-copybar__btn.is-clear { font-weight: 500; }
@media (prefers-reduced-motion: reduce) {
  .etac-copytoast { transition: opacity .18s; transform: translate(-50%, 0); }
}
