/* ─────────────────────────────────────────────────────────────────────
   VIBE DUNGEON · shared design system
   Single source of truth for tokens and shared chrome — consumed by the
   marketing page and the legal pages. Page-specific layout (hero,
   sessions, personas, pricing, FAQ; the doc layout for legal pages)
   lives inline in each page. To re-brand or re-tone, edit this file
   only — never one-off a hex code, font-size, or radius downstream.

   The wine is restraint made visible: one accent, used surgically,
   never more than a handful of placements per viewport. The brand
   polish IS the consent signal — refinement reads as considered;
   horror-genre maximalism reads as seedy. Hold restraint as a hard
   limit.
   ───────────────────────────────────────────────────────────────────── */

/* [LAW:one-source-of-truth] Every visual decision (color, type, spacing,
   radii, shadow, texture, dot-grid) is a custom property declared here.
   Other stylesheets read from these tokens; they do not redeclare them. */
:root {
  --bg:           #0d0a09;
  --bg-2:         #120e0c;
  --bg-vignette:  radial-gradient(1200px 700px at 50% -10%, rgba(160,38,56,0.10), transparent 60%),
                  radial-gradient(900px 600px at 80% 110%, rgba(160,38,56,0.05), transparent 60%);

  --ink:          #ece4d4;
  --ink-bright:   #fff;
  --ink-muted:    #a39787;
  /* [LAW:single-enforcer] Tertiary text token. Lifted from #6f655a so it
     meets WCAG 2.2 AA normal-text contrast (≥4.5:1) on every surface
     token, including --surface-3 (4.89:1). The previous value was below
     spec on all four surfaces (2.85–3.46:1), which made every text
     consumer of this token a contrast failure. Fixed at the token rather
     than at each callsite so the "tertiary" level is the readability
     floor by construction. */
  --ink-dim:      #988b7c;
  --hairline:     rgba(236,228,212,0.08);

  --surface:      #15110e;
  --surface-top:  #1f1a14;
  --surface-3:    #261f18;
  --on-surface:   #ece4d4;
  --on-surface-muted: #a39787;
  /* Mirrors --ink-dim. Same readability-floor reasoning applies; kept
     as a separate token so the surface family can drift if needed. */
  --on-surface-dim:   #988b7c;
  --ring:         rgba(236,228,212,0.10);
  --ring-strong:  rgba(236,228,212,0.18);

  --accent:       #a02638;
  --accent-soft:  #c64356;
  --accent-tint:  rgba(160,38,56,0.16);
  --accent-edge:  rgba(160,38,56,0.40);

  --dot:          rgba(236,228,212,0.035);
  --dot-size:     28px;

  --font-display: "Instrument Serif", "Tiempos Headline", Georgia, serif;
  --font-sans:    "Inter", system-ui, -apple-system, sans-serif;
  --font-mono:    "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;
  --tracking-tag: 0.18em;
  --tracking-kick: 0.22em;

  --t-hero:       clamp(3.25rem, 8vw, 6.5rem);
  --t-h2:         clamp(2.25rem, 4.5vw, 3.5rem);
  --t-h3:         clamp(1.5rem, 2.4vw, 2rem);
  --t-price:      clamp(2.75rem, 4.5vw, 3.5rem);
  --t-lead:       clamp(1.1rem, 1.5vw, 1.3rem);
  --t-body:       1rem;
  --t-body-small: 0.9375rem;
  --t-small:      0.875rem;
  --t-tiny:       0.75rem;

  --s-1: 0.5rem;  --s-2: 0.75rem; --s-3: 1rem;   --s-4: 1.5rem;
  --s-5: 2rem;    --s-6: 3rem;    --s-7: 4.5rem; --s-8: 7rem;  --s-9: 10rem;

  --r-card: 18px;  --r-card-lg: 24px;  --r-btn: 10px;  --r-pill: 999px;

  --shadow-card:  0 28px 56px -28px rgba(0,0,0,0.7),
                  0 2px 8px rgba(0,0,0,0.4),
                  0 0 0 1px var(--ring);
  --shadow-lift:  0 36px 72px -32px rgba(160,38,56,0.18),
                  0 6px 16px rgba(0,0,0,0.5),
                  0 0 0 1px var(--ring-strong);
}

*,*::before,*::after { box-sizing: border-box; margin: 0; }
/* [LAW:single-enforcer] scroll-padding-top reserves headroom so the
   sticky `.nav` (top: var(--s-4); ~80px tall with padding) cannot occlude
   a focused element after anchor-jump or tab-into-section. Satisfies
   WCAG 2.2 §2.4.11 (Focus Not Obscured — Minimum). */
html { scroll-behavior: smooth; scroll-padding-top: var(--s-7); }
body {
  font-family: var(--font-sans);
  color: var(--ink);
  background-color: var(--bg);
  background-image:
    var(--bg-vignette),
    radial-gradient(var(--dot) 1px, transparent 1px);
  background-size: auto, var(--dot-size) var(--dot-size);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  line-height: 1.55;
  font-size: 16px;
}
::selection { background: var(--accent); color: var(--ink); }
img,svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
/* [LAW:single-enforcer] One focus-ring rule for every interactive type.
   WCAG 2.2 §2.4.11 (Focus Appearance, AA-new) requires the indicator to
   have ≥3:1 contrast against adjacent colors. The previous --accent
   (#a02638) hit only 2.66:1 on the page bg and 2.33:1 on surface-top —
   below spec on every surface. --ink-bright lands at ~19:1 on all
   surfaces and is the conventional cream-white indicator for dark UIs;
   it does not break the wine-restraint rule because focus rings are
   transient indicators, not part of the "8 surgical wine placements".
   The accent-edge halo retains brand glow without leaning on accent for
   the load-bearing contrast. */
a:focus-visible, button:focus-visible, summary:focus-visible,
input:focus-visible, [role="button"]:focus-visible {
  outline: 2px solid var(--ink-bright);
  outline-offset: 3px;
  border-radius: 4px;
  box-shadow: 0 0 0 5px var(--accent-edge);
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

.wrap { max-width: 1200px; margin-inline: auto; padding-inline: var(--s-4); }
.serif { font-family: var(--font-display); font-weight: 400; line-height: 1.02; letter-spacing: -0.01em; }
.serif em { font-style: italic; }
.mono  { font-family: var(--font-mono); font-size: var(--t-small); letter-spacing: -0.01em; }
.kicker {
  display: inline-block;
  font-size: var(--t-tiny);
  text-transform: uppercase;
  letter-spacing: var(--tracking-kick);
  color: var(--ink-muted);
}
.kicker .dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent); margin-right: 0.6em; vertical-align: middle; }
.muted { color: var(--ink-muted); }
.dim { color: var(--ink-dim); }
section { padding-block: var(--s-8); }
.rule { height: 1px; background: var(--hairline); border: 0; margin: 0; }
.skip { position: absolute; left: -9999px; top: 0; }
.skip:focus { position: fixed; left: 1rem; top: 1rem; padding: .5rem 1rem; background: var(--accent); color: var(--ink); border-radius: var(--r-btn); z-index: 999; }
/* [LAW:one-source-of-truth] Visually-hidden utility for screen-reader-
   only text and polite live regions. Consumed by every page that has
   AT-only announcements (homepage waitlist, /waitlist-thanks/). */
.visually-hidden { position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0);
  white-space: nowrap; border: 0; }

/* [LAW:one-source-of-truth] Brand mark — one rule consumed by both the
   marketing nav, the footer brand block, and every legal-page header.
   Previously this was scoped `.nav .brand .glyph` which forced the
   footer to inline-copy the styles; the tight scope was the rough bit. */
.brand { font-family: var(--font-display); font-size: 1.5rem;
  display: inline-flex; align-items: center; gap: .55rem; letter-spacing: -0.005em; }
.brand .glyph { width: 26px; height: 26px; display: inline-grid; place-items: center;
  background: var(--bg-2); border-radius: 6px; position: relative;
  font-size: 1rem; font-style: italic; }
.brand .glyph::after { content:""; position:absolute; top:4px; right:4px;
  width:5px; height:5px; border-radius:50%; background: var(--accent);
  box-shadow: 0 0 6px var(--accent-edge); }

/* Hero voice teaser button. Custom-controlled play/pause over an <audio>
   element; sits alongside .btn-primary / .btn-ghost in the hero CTAs.
   The pause icon swaps in via data-state attribute. */
.btn-voice {
  display: inline-flex; align-items: center; gap: 10px;
  background: transparent; border: 1px solid var(--hairline);
  color: var(--ink); padding: .65rem 1rem; border-radius: 999px;
  font-family: var(--font-sans); font-weight: 500; font-size: 14px;
  cursor: pointer; transition: border-color .15s ease, color .15s ease, background .15s ease;
}
.btn-voice:hover, .btn-voice:focus-visible { border-color: var(--accent); color: var(--ink); }
.btn-voice .voice-glyph {
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--accent); position: relative; flex-shrink: 0;
  display: inline-grid; place-items: center;
  transition: background .15s ease;
}
/* Play triangle — white, centered, slight visual offset right to look balanced */
.btn-voice .voice-glyph::before {
  content: ""; display: block;
  border-style: solid; border-color: transparent transparent transparent #ece4d4;
  border-width: 5px 0 5px 8px; margin-left: 2px;
}
.btn-voice[data-state="playing"] .voice-glyph::before {
  border: none; width: 8px; height: 9px; background: linear-gradient(90deg, #ece4d4 0 3px, transparent 3px 5px, #ece4d4 5px 8px);
  margin-left: 0;
}
.btn-voice .voice-meta {
  font-family: var(--font-mono); font-size: 11px; color: var(--ink-muted);
  letter-spacing: .06em; padding-left: 8px; border-left: 1px solid var(--hairline);
}
@media (max-width: 540px) { .btn-voice .voice-meta { display: none; } }

/* Subtle reveal animation — wine dot pulses once on first paint, no
   looping. The V glyph fades in over the same window. Honors
   prefers-reduced-motion: animation is skipped entirely if the user
   has reduced-motion set. [LAW:dataflow-not-control-flow] same shape
   either way, the media query just zeros the durations. */
@keyframes vd-glyph-fade { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: none; } }
@keyframes vd-dot-pulse {
  0%   { transform: scale(1);   box-shadow: 0 0 6px var(--accent-edge), 0 0 0 0   rgba(160,38,56,0.5); }
  35%  { transform: scale(1.2); box-shadow: 0 0 10px var(--accent),     0 0 0 8px rgba(160,38,56,0); }
  100% { transform: scale(1);   box-shadow: 0 0 6px var(--accent-edge), 0 0 0 0   rgba(160,38,56,0); }
}
.brand .glyph { animation: vd-glyph-fade 0.6s ease-out 0.05s both; }
.brand .glyph::after { animation: vd-dot-pulse 1.2s ease-out 0.4s both; }
@media (prefers-reduced-motion: reduce) {
  .brand .glyph,
  .brand .glyph::after { animation: none; }
}

/* ─────────────────────────────────────────────────────────────────────
   Persona portrait — gradient surface + dark overlay + corner badge

   [LAW:one-source-of-truth] One portrait identity, consumed by the
   homepage persona card AND the /personas/<slug>/ detail-page hero.
   Aspect ratio is set by the *consumer* (`.persona .portrait` = 16/9
   on the homepage card; `.persona-detail .portrait` = 4/5 on detail
   pages), since the right aspect depends on container, not identity.

   [LAW:locality-or-seam] Palette assignment lives on the .portrait
   element itself (`.portrait.pN`), not on a parent. A portrait knows
   its own colors; no surrounding markup is required to set them.

   IMPORTANT a11y: .portrait MUST NOT be aria-hidden. The visual
   gradient lives in ::before/::after pseudo-elements (empty content,
   correctly inert to AT); the .badge inside the portrait is real
   text and must remain in the accessibility tree.
   ───────────────────────────────────────────────────────────────────── */
.portrait { position: relative; background: var(--bg-2); overflow: hidden; }
.portrait::before { content: ""; position: absolute; inset: 0;
  background-image: radial-gradient(circle at 30% 40%, var(--p1, #3a2620), transparent 55%),
                    radial-gradient(circle at 75% 70%, var(--p2, #1a1612), transparent 60%),
                    linear-gradient(135deg, var(--p3, #261814), var(--p4, #0d0a09)); }
/* Signature-object image fills the slot. Stacks between ::before (gradient
   base) and ::after (vignette overlay) — both pseudo-elements stay in place,
   so the image is anchored to the brand's dark warm chrome rather than
   sitting raw. Per CLAUDE.md persona visual-identity rule. */
.portrait img { position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; }
.portrait::after { content: ""; position: absolute; inset: 0;
  background: radial-gradient(circle at 50% 50%, transparent 0%, rgba(13,10,9,0.5) 100%); }
.portrait .badge { position: absolute; top: var(--s-3); left: var(--s-3);
  font-size: var(--t-tiny); letter-spacing: var(--tracking-tag); text-transform: uppercase;
  color: var(--ink); background: rgba(13,10,9,0.6); backdrop-filter: blur(6px);
  padding: .25em .7em; border-radius: var(--r-pill);
  box-shadow: 0 0 0 1px var(--ring); z-index: 1; }

.portrait.rust::before { --p1:#3a2620; --p2:#1a1612; --p3:#2a1c18; --p4:#0d0a09; }
.portrait.teal::before { --p1:#1c2a2a; --p2:#15110e; --p3:#1a2024; --p4:#0d0a09; }
.portrait.amber::before { --p1:#3a2a18; --p2:#1f1812; --p3:#241a12; --p4:#0d0a09; }
.portrait.violet::before { --p1:#1a1822; --p2:#15110e; --p3:#181520; --p4:#0d0a09; }
.portrait.brown::before { --p1:#2a2418; --p2:#15110e; --p3:#1c1812; --p4:#0d0a09; }
.portrait.claret::before { --p1:#4a1a22; --p2:#1c1210; --p3:#2a1418; --p4:#0d0a09; }
.portrait.goldenrod::before { --p1:#3a2e18; --p2:#1c1612; --p3:#26200d; --p4:#0d0a09; }
.portrait.slate::before { --p1:#2c323a; --p2:#15171a; --p3:#1c2128; --p4:#0d0a09; }
.portrait.moss::before { --p1:#2d4028; --p2:#161b14; --p3:#1c2a1d; --p4:#0d0a09; }
.portrait.taupe::before { --p1:#34322c; --p2:#15140f; --p3:#1e1c15; --p4:#0d0a09; }
.portrait.plum::before { --p1:#3a1c2a; --p2:#1a1015; --p3:#2a1620; --p4:#0d0a09; }
.portrait.limewash::before { --p1:#2c2e30; --p2:#14161a; --p3:#1c1f22; --p4:#0d0a09; }

.btn { display: inline-flex; align-items: center; justify-content: center; gap: .5em;
  padding: .8em 1.35em; border-radius: var(--r-btn);
  font: inherit; font-size: var(--t-small); font-weight: 500;
  cursor: pointer; border: 0;
  transition: background .15s ease, transform .15s ease, box-shadow .15s ease, color .15s ease;
  white-space: nowrap; }
.btn-primary { background: var(--ink); color: var(--bg);
  box-shadow: 0 0 0 1px var(--ring-strong), 0 12px 24px -12px rgba(236,228,212,0.18); }
.btn-primary:hover { background: var(--ink-bright); transform: translateY(-1px); }
.btn-ghost { background: transparent; color: var(--on-surface);
  box-shadow: inset 0 0 0 1px var(--ring); }
.btn-ghost:hover { box-shadow: inset 0 0 0 1px var(--ring-strong); background: var(--surface); }
.btn-accent { background: var(--accent); color: var(--ink);
  box-shadow: 0 0 0 1px var(--accent-edge), 0 12px 28px -12px rgba(160,38,56,0.55); }
.btn-accent:hover { background: var(--accent-soft); transform: translateY(-1px); }
.btn-block { width: 100%; }
.btn .arrow { transition: transform .2s ease; }
.btn:hover .arrow { transform: translateX(3px); }

footer { padding-block: var(--s-7) var(--s-6); border-top: 1px solid var(--hairline);
  margin-top: var(--s-7); }
.foot-grid { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; gap: var(--s-6); }
.foot-grid h4 { font-size: var(--t-tiny); text-transform: uppercase; letter-spacing: var(--tracking-tag);
  color: var(--ink-muted); margin-bottom: var(--s-3); font-weight: 600; }
.foot-grid ul { list-style: none; padding: 0; display: grid; gap: var(--s-2); }
.foot-grid a { color: var(--ink-muted); font-size: var(--t-small); transition: color .15s ease; }
.foot-grid a:hover { color: var(--ink); }
.foot-brand .brand { font-size: 1.6rem; }
.foot-brand .blurb { color: var(--ink-muted); font-size: var(--t-small);
  max-width: 38ch; margin-top: var(--s-3); }
.foot-bottom { margin-top: var(--s-6); padding-top: var(--s-5);
  border-top: 1px solid var(--hairline);
  display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap;
  gap: var(--s-3); font-size: var(--t-tiny); color: var(--ink-dim); }
.foot-bottom .legal a { color: var(--ink-dim); margin-left: var(--s-4); }
.foot-bottom .legal a:hover { color: var(--ink-muted); }

@media (max-width: 940px) {
  section { padding-block: var(--s-7); }
  .foot-grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 560px) {
  .foot-grid { grid-template-columns: 1fr; }
}

/* ─────────────────────────────────────────────────────────────────────
   Cookie consent surface

   Two pieces: a slim banner pinned to the bottom of the viewport on
   first visit, and a centered <dialog> opened either from the banner's
   "View preferences" button or from the footer "Cookie preferences"
   link on subsequent visits. Both consume the same tokens as the rest
   of the site — surface + ring + radii + shadow — so they read as
   chrome of the same product, not bolted-on third-party UI. Buttons
   reuse .btn-primary / .btn-ghost; no new wine placement.
   ───────────────────────────────────────────────────────────────────── */
/* Cookie banner — slim corner toast. Pinned bottom-right, single line of
   copy, compact buttons. The banner is the consent ceremony, not a
   marketing surface; visual weight kept proportional. The preferences
   dialog (.cookie-dialog) carries the longer disclosure for users who
   open it; the banner stays terse.
   The .kicker is intentionally hidden here — a section header inside a
   480px toast reads as bureaucratic chrome. The buttons and one-sentence
   copy carry the context. */
.cookie-banner {
  position: fixed; right: var(--s-4); bottom: var(--s-4);
  left: auto;
  max-width: 460px; z-index: 100;
  background: var(--surface-top);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-card);
  color: var(--on-surface);
  opacity: 0; transform: translateY(12px);
  transition: opacity .25s ease, transform .25s ease;
}
.cookie-banner.is-visible { opacity: 1; transform: translateY(0); }
.cookie-banner-inner {
  display: grid; grid-template-columns: 1fr;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-4);
}
.cookie-banner-copy .kicker { display: none; }
.cookie-banner-copy p {
  font-size: var(--t-small); line-height: 1.5;
  color: var(--on-surface-muted); margin: 0;
}
.cookie-banner-actions {
  display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap;
  justify-content: flex-end;
}
/* [LAW:locality-or-seam] Banner-scoped button shrinkage — the global
   .btn dimensions are correct for marketing CTAs; the banner needs a
   smaller hit target because it sits in the user's peripheral vision,
   not in the page's primary reading flow. Override is local to this
   surface, not a global token edit. */
.cookie-banner .btn {
  padding: .45em .9em; font-size: var(--t-tiny);
  letter-spacing: 0.02em;
}
.cookie-banner-prefs {
  appearance: none; background: transparent; border: 0; padding: 0; cursor: pointer;
  font: inherit; font-size: var(--t-tiny); color: var(--ink-dim);
  text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: var(--hairline);
  margin-left: auto;
}
.cookie-banner-prefs:hover { color: var(--ink-muted); text-decoration-color: var(--ring-strong); }

.cookie-dialog {
  /* The outer <dialog> is a positioning container only — visual chrome
     lives on .cookie-dialog-inner. Explicit margin/inset replace the UA
     defaults that our background/border/padding resets sometimes drop. */
  border: 0; padding: 0; background: transparent; color: var(--on-surface);
  max-width: 560px; width: calc(100% - var(--s-4) * 2);
  margin: auto; inset: 0;
}
.cookie-dialog::backdrop { background: rgba(0,0,0,0.6); backdrop-filter: blur(2px); }
.cookie-dialog-inner {
  background: var(--surface-top);
  border-radius: var(--r-card-lg);
  box-shadow: var(--shadow-lift);
  padding: var(--s-5);
}
.cookie-dialog-head .kicker { margin-bottom: var(--s-2); }
.cookie-dialog-head h2 { font-size: var(--t-h3); margin-bottom: var(--s-3); }
.cookie-dialog-head h2 em { color: var(--accent-soft); font-style: italic; }
.cookie-dialog-lede {
  font-size: var(--t-body-small); color: var(--ink-muted); margin-bottom: var(--s-4);
}
.cookie-dialog-body { display: grid; gap: var(--s-3); margin-bottom: var(--s-4); }
.cookie-cat {
  background: var(--surface); border-radius: var(--r-btn);
  padding: var(--s-3); box-shadow: inset 0 0 0 1px var(--ring);
}
.cookie-cat-head {
  display: flex; align-items: center; gap: var(--s-2);
  font-size: var(--t-small); font-weight: 600; cursor: pointer;
}
.cookie-cat-head input[disabled] { cursor: not-allowed; opacity: 0.6; }
.cookie-cat-label { color: var(--on-surface); }
.cookie-cat-note {
  font-size: var(--t-tiny); color: var(--ink-dim); margin-top: var(--s-1);
}
.cookie-cat-items {
  list-style: none; padding: 0; margin-top: var(--s-2);
  display: grid; gap: var(--s-1);
  font-size: var(--t-tiny); color: var(--ink-muted);
}
.cookie-cat-items code {
  font-family: var(--font-mono); color: var(--ink); font-size: 0.85em;
}
.cookie-cat-empty {
  font-size: var(--t-tiny); color: var(--ink-dim); margin-top: var(--s-2);
}
.cookie-cat-empty em { font-style: italic; font-family: var(--font-display); }
.cookie-dialog-foot {
  display: flex; justify-content: flex-end; gap: var(--s-2);
  padding-top: var(--s-3); border-top: 1px solid var(--hairline);
}

/* Fallback for browsers without native <dialog>: position as overlay. */
.cookie-dialog--fallback {
  position: fixed; inset: 0; display: grid; place-items: center;
  background: rgba(0,0,0,0.6); z-index: 200;
}

@media (max-width: 560px) {
  /* On narrow viewports the toast expands to a bottom edge-to-edge bar so
     buttons remain reachable with one thumb. Still slim — same padding
     and font sizes as the desktop toast, just no max-width cap. */
  .cookie-banner { left: var(--s-3); right: var(--s-3); bottom: var(--s-3);
    max-width: none; }
  .cookie-banner-actions { justify-content: flex-end; }
  .cookie-dialog-inner { padding: var(--s-4); }
}
@media (prefers-reduced-motion: reduce) {
  .cookie-banner { transition: opacity .01s linear; transform: none; }
  .cookie-banner.is-visible { transform: none; }
}
