/* ════════════════════════════════════════════════════════════════
   site.css — shared tokens, reset, typography, and site header
   All pages link this. Page-specific styles stay in inline <style>.
   ════════════════════════════════════════════════════════════════ */

/* ── Design tokens ─────────────────────────────────────────────── */
:root {
  /* ── Dark deep-purple theme ──────────────────────────────────────
     Warm gold accents on a deep violet ground. The AA helper tokens
     (--gold-ink, --ink-muted) are LIGHT here — small text sits on the
     dark surfaces, not on a light page. Playing-card faces keep their
     own light paper (--card-paper) so the dark suit pips stay legible. */
  --bg:            #100c1e;   /* deepened to the hero's darker violet (site-wide) */
  --surface:       #221a3d;
  --surface-alt:   #2b2150;
  --text:          #f1ecfa;
  --muted:         #9a90b8;   /* large / decorative only */
  --secondary:     #c8bfe0;
  --accent:        #1b1436;   /* header / footer / band chrome */
  --gold:          #c79a54;
  --gold-light:    #2e2450;   /* subtle violet highlight fill (was a cream) */
  --gold-ink:      #ddbb78;   /* light gold — small text on dark bg (AA 4.5:1) */
  --ink-muted:     #b7add6;   /* light muted — small text on dark bg (AA 4.5:1) */
  --rule:          #2c2550;
  --rule-strong:   #443a6b;
  --yang:          #1a1916;   /* card pip / yang line — used on light paper */
  --red:           #b53030;   /* hearts / diamonds — on light card paper */
  --green:         #1a1916;   /* clubs (render near-black on card paper) */
  --navy:          #1a1916;   /* spades */
  --text-muted:    #9a90b8;
  --card-dark:     #1a1916;
  --card-paper:    #f7f1e6;   /* warm paper for playing-card faces */
  --band:          #241c46;   /* full-bleed dark band (deep violet) */
  --border:        #322a55;
  --code-bg:       #241c46;
  --r-sm:          6px;
  --r-md:          12px;
  --r-lg:          20px;
  --card-radius:   4px;        /* playing-card faces — gentle real-card corners */
  --sh-sm:         0 1px 3px rgba(0,0,0,.07), 0 1px 2px rgba(0,0,0,.04);
  --sh-md:         0 4px 16px rgba(0,0,0,.08), 0 2px 6px rgba(0,0,0,.04);
  --sh-lg:         0 16px 48px rgba(0,0,0,.12), 0 4px 16px rgba(0,0,0,.06);
  --max-w:         1140px;
  --quadration-max-w: 560px;         /* shared cap for the quadration grid + finder grids that should track its size (life-script + connection mini-grids use 75% of this) */
  --section-gap:   var(--space-7);   /* vertical gap between stacked page sections (3rem) */
  --serif:         'Lora', Georgia, serif;
  --sans:          'Inter', system-ui, sans-serif;
  --mono:          'SFMono-Regular', ui-monospace, Menlo, Consolas, monospace;
  --ease:          .18s ease;
  /* ── Spacing scale ───────────────────────────────────────────────
     The intended rhythm. Adopt these for paddings/margins/gaps instead of
     typing rem values; --section-gap is derived from it. Existing off-grid
     values (.4/.45/.85/1.25/1.75rem…) are being migrated incrementally. */
  --space-1:       .25rem;
  --space-2:       .5rem;
  --space-3:       .75rem;
  --space-4:       1rem;
  --space-5:       1.5rem;
  --space-6:       2rem;
  --space-7:       3rem;
  /* ── Type scale ──────────────────────────────────────────────────
     Role-named font sizes; the heading registers + .subtitle/.lead are
     wired to these. New UI text should reference a token, not a literal. */
  --text-xs:       .8rem;     /* small caps labels, subtitle */
  --text-sm:       .85rem;    /* h3 / small text */
  --text-base:     1rem;      /* body (html root is 16px) */
  --text-md:       1.05rem;   /* lead paragraph */
  --text-lg:       1.35rem;   /* h2 */
  --text-xl:       2.2rem;    /* h1 */
}

/* ── Reset ──────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
/* Root background matches the theme so the desktop overscroll/rubber-band area
   (when you scroll past the top or bottom) shows the deep-violet ground, not a
   white bar. overscroll-behavior-y also stops the bounce where supported.
   IMPORTANT: the starfield canvas (.stars-bg) is position:fixed; z-index:-1, so
   the stars only show through when the violet ground is painted on the ROOT
   (html) BEHIND that negative layer — a background on <body> paints ABOVE the
   canvas and hides the stars. So starry pages put the gradient on the root via
   html:has(body.starry) below, and body.starry itself stays transparent. The
   solid var(--bg) here is the fallback (keeps overscroll violet; flat violet if
   :has is unsupported). */
html { font-size: 16px; scroll-behavior: smooth; scrollbar-gutter: stable; background: var(--bg); overscroll-behavior-y: none; }
/* Starry pages: paint the drifting-star ground on the root so it sits behind the
   z-index:-1 star canvas (see .stars-bg + body.starry). */
html:has(body.starry) {
  background: radial-gradient(125% 85% at 50% -8%, #1a1433 0%, #110c21 46%, #0e091a 100%) fixed;
  /* the shorthand above resets background-color to transparent; restore the solid
     violet base so overscroll / svh-dvh gaps never flash white behind the fixed
     gradient (the gradient still paints the viewport on top). */
  background-color: var(--bg);
}
/* Lock horizontal scroll site-wide. Needs to be on BOTH html and body for
   iOS Safari (body-only is ignored), plus overscroll-behavior-x to kill
   the rubber-band elastic pan. position: relative gives layout a fresh
   containing block (another iOS-Safari requirement). */
html { overflow-x: hidden; overscroll-behavior-x: none; }
body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  overflow-x: hidden;
  overscroll-behavior-x: none;
  position: relative;
  max-width: 100vw;
}

/* ── Starfield background (shared) ─────────────────────────────────
   A fixed, full-viewport canvas of drifting, twinkling stars sits behind
   all content on a deep-violet ground. Opt in with `body.starry` + a
   `<canvas class="stars-bg">`; the animation lives in js/stars.js and
   honours prefers-reduced-motion. The violet gradient (the homepage's
   established starfield ground, kept verbatim) is painted on the ROOT via
   html:has(body.starry) above, so it sits behind the z-index:-1 canvas;
   body.starry stays transparent so it never paints over the stars. */
body.starry {
  background: transparent;
}
.stars-bg {
  position: fixed; inset: 0;
  width: 100%; height: 100%;
  z-index: -1; pointer-events: none;
}
a { color: inherit; text-decoration: none; }

/* Visible keyboard focus, site-wide (a11y 2.4.7). Uses :focus-visible so it
   only shows for keyboard/AT users, never on mouse click. */
:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; border-radius: 3px; }

/* Skip-to-content link (a11y 2.4.1) — off-screen until focused, then slides in
   over the sticky header. Injected with the header by site.js. */
.skip-link {
  position: fixed; left: 12px; top: -56px; z-index: 300;
  background: var(--accent); color: #fff;
  padding: .6rem 1rem; border-radius: 0 0 10px 10px;
  font-size: .8rem; font-weight: 600; letter-spacing: .04em;
  text-decoration: none; transition: top .16s ease;
}
.skip-link:focus { top: 0; }
.main-anchor { position: absolute; width: 0; height: 0; overflow: hidden; }

/* Visually-hidden utility — content stays in the DOM (and the accessibility/SEO
   document outline) but is clipped from view. Used for the per-page <h1 class="vh">
   that gives each tool page a proper top-level heading without a visible title.
   Promoted here from index.html now that several pages use it. */
.vh {
  position: absolute !important; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ── Typography ─────────────────────────────────────────────────── */
h1 {
  font-family: var(--serif);
  font-size: var(--text-xl);
  font-weight: normal;
  letter-spacing: -0.01em;
  line-height: 1.2;
  margin-bottom: 0.4rem;
}
h2 {
  font-family: var(--serif);
  font-size: var(--text-lg);
  font-weight: normal;
  margin: 3.5rem 0 1rem;
  padding-bottom: 0.4rem;
  border-bottom: 1px solid var(--rule);
}
h3 {
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 2rem 0 0.75rem;
}
p { margin-bottom: 1rem; }
p:last-child { margin-bottom: 0; }

.subtitle {
  font-family: var(--mono);
  font-size: var(--text-xs);
  color: var(--muted);
  margin-bottom: 2.5rem;
  letter-spacing: 0.04em;
}
.lead {
  font-size: var(--text-md);
  color: var(--secondary);
  line-height: 1.75;
  margin-bottom: 2rem;
}
hr { border: none; border-top: 1px solid var(--rule); margin: 2rem 0; }
code {
  font-family: var(--mono);
  font-size: 0.82em;
  background: var(--code-bg);
  padding: 0.12em 0.4em;
  border-radius: 3px;
}
pre {
  font-family: var(--mono);
  font-size: 0.8rem;
  background: var(--code-bg);
  padding: 1.25rem 1.5rem;
  border-radius: 6px;
  overflow-x: auto;
  line-height: 1.6;
  margin: 1.25rem 0;
  border-left: 3px solid var(--accent);
}

/* ── Layout ─────────────────────────────────────────────────────── */
.page-content {
  width: 100%;
  max-width: 780px;
  margin: 0 auto;
  padding: 1.25rem 1.5rem 6rem;
  align-self: center;
}
.page-header {
  margin-bottom: 2rem;
  padding-bottom: 1.5rem;
  border-bottom: 1px solid var(--rule);
}

/* Uniform vertical rhythm between stacked top-level sections. Mark each section
   `.page-section`; the gap between any two adjacent ones is a single margin
   driven by --section-gap (change it in :root to re-space every such page at
   once). A section's OWN padding is then just internal framing, not the gap —
   keep section vertical padding at 0 and let this rule own the spacing. The
   first .page-section gets no top margin, so a page's top inset is set wherever
   that first section's padding/header sits. Pages whose sections live in
   different wrappers (e.g. iching, astrology) can't use this sibling rule, so
   they reference var(--section-gap) directly on their own gap. */
.page-section + .page-section { margin-block-start: var(--section-gap); }

/* ── Section minimise/maximise (shared) ────────────────────────────
   Opt-in pattern: wrap a .page-section's body in .section-bodywrap >
   .section-bodymin, replace the .section-label span with a button.section-toggle
   (containing the label + a .section-chev SVG), and toggleSection(id) (in
   site.js) flips .section-open on the parent .page-section. CSS handles the
   grid-rows 0fr → 1fr unfurl, the chevron rotation, and prefers-reduced-motion. */
.page-section .section-toggle {
  background: none; border: none; padding: 0; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  gap: .55rem;
  margin: 0 auto;
  font: inherit;
}
.page-section .section-toggle:focus-visible {
  outline: 2px solid var(--gold); outline-offset: 4px;
  border-radius: 4px;
}
.page-section .section-toggle .section-label,
.page-section .section-toggle .hex-ref-label { margin: 0; }
.section-chev {
  width: 11px; height: 11px;
  color: var(--gold); opacity: .55;
  flex-shrink: 0;
  transition: transform .25s ease, opacity .18s;
}
.section-toggle:hover .section-chev,
.section-toggle:focus-visible .section-chev { opacity: 1; }
.page-section.section-open .section-chev { transform: rotate(180deg); }
.section-bodywrap {
  display: grid; grid-template-rows: 0fr;
  margin-top: 0;
  transition: grid-template-rows .4s ease, margin-top .4s ease;
}
.page-section.section-open .section-bodywrap {
  grid-template-rows: 1fr;
  margin-top: 1rem;
}
/* A little top room inside the (overflow-hidden) collapse wrapper: leaves a slight
   gap below each section subtitle and stops top-poking decorations — a selected
   button's gold outline, the saved-birthday badge — from being clipped, while open.
   Kept at 0 while collapsed (below) — otherwise this padding is the one part of
   the wrapper that DOESN'T shrink with grid-template-rows: 0fr (padding isn't
   compressed by the 0fr track the way content height is), so a sliver of exactly
   that padding's height stays visible — the very decorations it was added to
   protect (a selected tool's gold outline, a saved-birthday badge) go on peeking
   out the top of an otherwise-collapsed section. */
.section-bodymin { overflow: hidden; min-height: 0; padding-top: 0; transition: padding-top .4s ease; }
.page-section.section-open .section-bodymin { padding-top: 8px; }
@media (prefers-reduced-motion: reduce) {
  .section-bodywrap { transition: none; }
  .section-chev { transition: none; }
  .section-bodymin { transition: none; }
}

/* ── Site header and nav ────────────────────────────────────────── */
.site-header {
  position: sticky; top: 0; z-index: 200;
  background: var(--accent);
  border-bottom: 1px solid rgba(255,255,255,.08);
}
.site-header.scrolled { box-shadow: 0 2px 12px rgba(0,0,0,.25); }
/* Blended header on starry pages — the sticky bar melts into the starfield:
   transparent at rest, easing to a faint deep-violet veil once the page scrolls
   (site.js toggles .scrolled). The logo/burger/nav are already light, so they
   read on the dark ground; the open nav dropdown keeps its own --accent bg. */
body.starry .site-header { background: transparent; border-bottom-color: transparent; }
body.starry .site-header.scrolled {
  background: rgba(22, 16, 41, .82);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  border-bottom-color: rgba(255, 255, 255, .06);
}
.sh-inner {
  display: flex; align-items: center; height: 64px;
  max-width: var(--max-w); margin: 0 auto;
  padding: 0 2rem; position: relative;
}
.sh-logo {
  font-family: var(--serif); font-size: 1.45rem; font-weight: 400;
  letter-spacing: .01em; color: rgba(255,255,255,.92);
  text-decoration: none; position: absolute;
  left: 50%; transform: translateX(-50%); white-space: nowrap;
}
.sh-logo .suit { color: var(--gold); }
/* Menu trigger: a labelled, >=44px touch target (a11y 2.5.5) so it reads as
   navigation, not a bare glyph. */
.burger {
  background: none; border: none; cursor: pointer;
  margin-left: auto; min-height: 44px; padding: 0 4px;
  display: flex; align-items: center; gap: 9px; z-index: 1;
  color: rgba(255,255,255,.85);
}
/* Burger bars sit in an absolutely-positioned stack so they can rotate into
   an X when the menu is open. site.js toggles .is-open on the burger. */
.burger-bars { position: relative; width: 22px; height: 16px; }
.burger-bars span {
  display: block; position: absolute; left: 0;
  width: 22px; height: 2px;
  background: currentColor; border-radius: 2px;
  transition: transform .25s ease, opacity .2s ease;
}
.burger-bars span:nth-child(1) { top: 0; }
.burger-bars span:nth-child(2) { top: 7px; }
.burger-bars span:nth-child(3) { top: 14px; }
.burger.is-open .burger-bars span:nth-child(1) { transform: translateY(7px)  rotate(45deg); }
.burger.is-open .burger-bars span:nth-child(2) { opacity: 0; }
.burger.is-open .burger-bars span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* Frosted-glass menu — semi-transparent --accent (rgba 27,20,54 ≈ --accent
   #1b1436 at ~72%) + 14px backdrop blur, with a hairline gold top edge so
   it reads as the header unfurling rather than a panel floating beneath
   it. Always in flow (display:flex) so it can fade + slide; visibility
   gates focusability while closed. */
.sh-nav {
  display: flex; flex-direction: column;
  position: absolute; top: 64px; right: 0;
  /* Size to the widest item — currently CELESTIAL KEYS sets the width.
     max-content + .sh-nav a { white-space: nowrap } keeps every row on
     one line without leaving a desktop-sized gap on the right. */
  width: max-content;
  max-width: calc(100vw - 1rem);
  padding: 6px;
  background: rgba(27, 20, 54, .72);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
          backdrop-filter: blur(14px) saturate(140%);
  /* No outer hairline border — relies on the gold inset top edge + the
     atmospheric shadow to define the panel. Reads cleaner on the dark
     ground than the previous 1px white-alpha rim. */
  border: none;
  border-radius: 0 0 16px 16px;
  box-shadow:
    inset 0 1px 0 rgba(199,154,84,.32),
    inset 1px 0 0 rgba(199,154,84,.10),
    inset -1px 0 0 rgba(199,154,84,.10),
    0 18px 44px rgba(0,0,0,.55);
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateY(-6px);
  transition: opacity .2s ease, transform .25s ease, visibility 0s linear .25s;
}
.sh-nav.open {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: none;
  transition: opacity .2s ease, transform .25s ease, visibility 0s linear 0s;
}
.sh-nav a {
  display: flex; align-items: center; min-height: 44px;
  font-size: .72rem; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; text-decoration: none;
  padding: 0 1rem 0 calc(1rem - 3px);
  border-left: 3px solid transparent;
  border-radius: 0 6px 6px 0;
  color: rgba(255,255,255,.82); white-space: nowrap;
  transition: background .18s, color .18s, border-color .18s;
}
.sh-nav a:hover,
.sh-nav a:focus-visible {
  background: rgba(199,154,84,.12);
  color: var(--gold-ink);
  border-left-color: rgba(199,154,84,.45);
  outline: none;   /* the gold rail + soft tint IS the focus indicator */
}
/* Deliberately no auto-highlight for the current page — the menu reads
   uniformly quiet until the user hovers / focuses. The page title in
   the header top-left already names where you are. .active stays in
   the DOM (set by site.js) for any future semantic use. */
.sh-nav a.active { /* intentionally empty */ }

@media (prefers-reduced-motion: reduce) {
  .sh-nav { transition: visibility 0s; transform: none; }
  .burger-bars span { transition: none; }
}
.sh-nav hr { border: none; border-top: 1px solid rgba(255,255,255,.12); margin: .25rem 0; }
.sh-nav .nav-heading {
  font-size: .6rem; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: rgba(255,255,255,.5);
  padding: .6rem 1rem .2rem;
  border-top: 1px solid rgba(255,255,255,.1);
  margin-top: .25rem;
}
/* First group label sits flush at the top of the menu (no divider above it). */
.sh-nav .nav-heading:first-child { border-top: none; margin-top: 0; padding-top: .2rem; }
.sh-nav .tool-btn {
  background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.14);
  color: rgba(255,255,255,.82); border-radius: 8px;
  min-height: 44px; padding: 0 1rem; font-size: .72rem; font-weight: 600;
  letter-spacing: .04em; cursor: pointer; text-align: left;
  transition: background .18s, color .18s; white-space: nowrap; width: 100%;
}
.sh-nav .tool-btn:hover { background: var(--gold); color: var(--accent); }
@media (max-width: 560px) {
  .sh-inner { padding: 0 1rem; }
  .sh-logo { font-size: 1.15rem; }
}

/* ── Buttons (shared) ───────────────────────────────────────────────
   The canonical general-purpose button. Base = a neutral utility button
   (surface-alt fill, hairline border, warming gold hover); modifiers cover
   the common variants. Domain controls keep their own look on purpose — the
   calculator keypad, the I Ching cast-icon buttons, and the in-game buttons
   are deliberate sub-languages, not this button. New ordinary buttons use
   `.btn` (+ a modifier) so they inherit the system for free. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .35rem;
  background: var(--surface-alt); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: .4rem .85rem;
  font-family: var(--sans); font-size: .75rem; font-weight: 500; letter-spacing: .03em;
  color: var(--secondary); cursor: pointer; white-space: nowrap;
  transition: background var(--ease), border-color var(--ease), color var(--ease);
}
.btn:hover { border-color: var(--gold); color: var(--gold); background: var(--gold-light); }
.btn:disabled { opacity: .4; cursor: default; }
/* Primary call-to-action: gold fill, dark ink. */
.btn--gold { background: var(--gold); border-color: transparent; color: var(--accent); }
.btn--gold:hover { background: var(--gold-ink); color: var(--accent); }
/* Secondary: transparent until hover. */
.btn--ghost { background: transparent; }
/* Square icon button (pairs with an inline SVG). */
.btn--icon { padding: .4rem; }

/* ── Shared playing-card faces ──────────────────────────────────────
   Canonical copy used by cardsoflife, quadrations and astrology.
   iching.html keeps its own px-tuned variant (small cast cards) which
   overrides these by cascade order. Pages add their own sizing /
   interactivity (cursor, hover, width) on top of this base. */
.spread-card {
  position: relative;
  overflow: hidden;
  background: var(--card-paper);          /* cards stay on light paper in the dark theme */
  border: 1px solid #e3d8c4;
  border-radius: var(--card-radius);
  container-type: inline-size;
  aspect-ratio: 5 / 7;
}
.spread-card.hearts,  .spread-card.diamonds { color: var(--red); }
.spread-card.clubs  { color: var(--green); }
.spread-card.spades { color: var(--navy); }
.spread-card.joker  { color: var(--gold); }
/* Court figures — and now the Joker's original "Quinta Essentia" art — have a
   transparent body, so they sit on the parchment card face like the number cards.
   The Joker art is full-bleed (fills the card) so its four suits reach the corners. */
.spread-card.joker .court-art { inset: 0; width: 100%; height: 100%; }
.card-corner {
  position: absolute; z-index: 1;
  display: flex; flex-direction: column; align-items: center; gap: 1px; line-height: 1;
}
.card-tl { top: 2.5%; left: 4%; }
.card-br { bottom: 2.5%; right: 4%; transform: rotate(180deg); }
.cc-rank { font-family: var(--serif); font-size: 22cqw; font-weight: 500; line-height: 1; }
.cc-suit { font-size: 14cqw; line-height: 1; }
/* Joker only: "oker" descends vertically beneath the Jack-style "J" so the corner
   reads "Joker", using the same serif + corner position as the court indices. */
.cc-rest { font-family: var(--serif); font-size: 9cqw; font-weight: 500; line-height: 1.04;
  writing-mode: vertical-rl; text-orientation: upright; letter-spacing: .2cqw; margin-top: .4cqw; }
.card-pips { position: absolute; top: 18%; bottom: 18%; left: 12%; right: 12%; }
.pip {
  position: absolute; transform: translate(-50%, -50%);
  font-size: 20cqw; line-height: 1; user-select: none; pointer-events: none;
}
.pip.inv { transform: translate(-50%, -50%) rotate(180deg); }
.pip.ace { font-size: 44cqw; }
.pip-svg { width: 1em; height: 1em; fill: currentColor; display: block; }
.court-art {
  position: absolute; inset: 8% 5%;
  width: 90%; height: 84%;
  object-fit: contain; object-position: center;
}
/* Alternate "pips-only" courts: each court renders BOTH the figure (.court-art +
   .court-pip) and a pip layout (.card-pips.court-only); body.pips-only swaps them
   with pure CSS — no re-render. Toggled from the Quadrations controls. The Joker
   keeps its emblem (it has no pip form), so it's excluded. */
.court-only { display: none; }
body.pips-only .spread-card:not(.joker) .court-art,
body.pips-only .spread-card:not(.joker) .court-pip { display: none; }
body.pips-only .spread-card:not(.joker) .court-only { display: block; }
/* Suit pip beside the court figure's head (position set inline per card), using
   the same glyph + suit colour as the number cards and matched to their size. */
.court-pip {
  position: absolute; z-index: 1;
  transform: translate(-50%, -50%);
  font-size: 20cqw; line-height: 1; pointer-events: none;
}
.card-face {
  position: absolute; inset: 5%;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
}
.card-face-letter { font-family: var(--serif); font-size: 42cqw; font-weight: 400; line-height: 1; }
.card-face-sym    { font-size: 24cqw; opacity: .75; }

/* ── Shared card back (engine-turned gold rosette + I Ching cash coin) ──
   The single source of truth for the card-back design, reused by the homepage
   casting field (.mini), the Cards of Life life-script riffle (.ls-back), and
   the I Ching cast deal. Purely visual + self-contained (fills its parent, sets
   its own --cash-hole) — each context adds its own positioning / flip transform.
   Markup: <div class="card-back"><span class="cb-coin"></span></div>. */
.card-back {
  position: absolute; inset: 0;
  border-radius: inherit; overflow: hidden;
  --cash-hole: 30%;   /* cash-coin square hole, as a % of the coin */
  background: linear-gradient(155deg, #2a2154 0%, #1c1440 60%, #160e2e 100%);
  border: 1px solid rgba(221,187,120,.42);
}
/* inner frame line */
.card-back::before {
  content: ''; position: absolute; inset: 8%; border-radius: 2px;
  border: 1px solid rgba(221,187,120,.45);
}
/* engine-turned guilloché rosette */
.card-back::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 78%; aspect-ratio: 1; transform: translate(-50%, -50%);
  border-radius: 50%;
  background:
    repeating-radial-gradient(circle at 50% 50%, rgba(221,187,120,.3) 0 1px, transparent 1px 4px),
    repeating-conic-gradient(from 0deg, rgba(221,187,120,.18) 0deg 2deg, transparent 2deg 9deg);
  -webkit-mask: radial-gradient(circle, #000 56%, transparent 60%);
          mask: radial-gradient(circle, #000 56%, transparent 60%);
}
/* the cash coin at the hub */
.card-back .cb-coin {
  position: absolute; left: 50%; top: 50%; z-index: 2;
  container-type: inline-size;
  width: 42%; aspect-ratio: 1; transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(circle at 38% 30%, #efd79e 0%, #d3aa61 42%, #a9813f 72%, #75582a 100%);
  border: 1px solid #5a4220;
  box-shadow: inset 0 1px 2px rgba(255,246,222,.5), inset 0 -2px 4px rgba(58,38,14,.55), 0 2px 5px rgba(0,0,0,.5);
  display: flex; align-items: center; justify-content: center;
}
.card-back .cb-coin::after {
  content: ''; width: var(--cash-hole); height: var(--cash-hole); border-radius: 1.5px;
  background: radial-gradient(circle at 50% 38%, #2a1f12, #120c06);
  box-shadow: inset 0 1px 2px rgba(0,0,0,.85);
  border: 1px solid #5a4220;
}

/* ── Shared spread grid (7-column card layout) ──────────────────── */
.spread-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: .45rem .35rem;
  align-items: center;
  width: 100%;
  max-width: var(--quadration-max-w);
  margin: 0 auto;
}
.planet-col-label {
  text-align: center; font-size: 1.5rem;
  color: var(--text-muted); padding-top: .4rem; line-height: 1;
}
.crown-row { grid-column: 1 / -1; display: grid; grid-template-columns: repeat(7, 1fr); gap: .45rem .35rem; }

/* Seats + displacement ghost chips. Each seat carries two chips read against
   its LIFE occupant: .sl-ghost-d names the card it displaces (the seat's
   Spirit owner), .sl-ghost-b the card it is displaced by. The grid classes
   .sl-ghosts / .sl-ghosts-by (buildSpreadGrid's showGhosts — the Quadration
   Life view's Displaces / Displaced-by pills) pick which set renders. Cards
   are re-seated after the chips in DOM order, so chips keep order:1 to stay
   under the card. */
.sl-seat { min-width: 0; display: flex; flex-direction: column; }
.sl-seat .spread-card { order: 0; }
/* The two chips share one row under the card, side by side. */
.sl-ghost-row { order: 1; display: flex; justify-content: center; gap: .22rem; margin-top: .22rem; }
.sl-ghost {
  display: none;
  padding: .08rem .3rem;
  font-family: var(--sans); font-size: .55rem; font-weight: 600;
  letter-spacing: .03em; line-height: 1.2; border-radius: 4px;
  background: rgba(253,250,245,.82); /* --surface at 82% for the navy band */
  white-space: nowrap; user-select: none; pointer-events: none;
}
.spread-grid.sl-ghosts .sl-ghost-d,
.spread-grid.sl-ghosts-by .sl-ghost-b { display: block; }
.sl-ghost-self { visibility: hidden; }
.sl-ghost.hearts, .sl-ghost.diamonds { color: var(--red); }
.sl-ghost.clubs  { color: var(--green); }
.sl-ghost.spades { color: var(--navy); }

/* ── Compare-card popup (shared chrome) ─────────────────────────────
   The card-detail overlay used by cardsoflife, iching, quadrations and
   astrology. The shared visual chrome lives here; each page keeps only its
   own overrides in its inline <style> (overlay z-index, cardFlip flip
   direction, #ccFace reset, footer variant, displacement-link styling).
   Canonical tokens; personality text uses Lora (see add-site-page notes). */
.ccard-overlay.open { display: flex; }
/* Lock background scroll while a fullscreen popup overlay is open (the card
   popup or the hexagram popup, on any page). scrollbar-gutter:stable on <html>
   (see the reset) keeps the layout from shifting when the scrollbar is hidden. */
html:has(.ccard-overlay.open),
html:has(.hx-overlay.open) { overflow: hidden; }
.ccard-popup {
  background: var(--surface); border-radius: var(--r-lg);
  /* Permanent gold ring to match the hexagram popup. */
  box-shadow: 0 0 0 1.5px var(--gold), var(--sh-lg); max-width: 420px; width: 100%;
  position: relative;
  animation: cardFlip .25s cubic-bezier(.23,1,.32,1);
  display: flex; flex-direction: column;
  height: 460px; max-height: 88vh; overflow: hidden;
}
.ccard-close {
  position: absolute; top: .4rem; right: .9rem; z-index: 2;
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--surface-alt); border: 1px solid var(--border);
  font-size: .78rem; color: var(--muted);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; transition: all var(--ease);
}
.ccard-close:hover { background: var(--border); color: var(--text); }
/* Solar Value banner — pinned at the very top of the popup */
.ccard-solar {
  display: flex; align-items: center; justify-content: center;
  padding: .45rem 1rem;
  background: rgba(217,184,122,.08);
  border-bottom: 1px solid var(--border);
}
.ccard-solar-value {
  font-family: var(--serif); font-size: 1.5rem; font-weight: 700;
  color: var(--gold); line-height: 1;
}
/* Period reading: hide solar banner */
/* (Previously hid .ccard-solar in period popups — restored so the Your-Cards
   popups carry the same SV badge as the rest of the compare-card popups.) */
.ccard-header {
  display: flex; gap: 1.35rem; align-items: flex-start;
  padding: 1.35rem 1.75rem 1.2rem;
  border-bottom: 1px solid var(--border);
}
.ccard-info { flex: 1; min-width: 0; padding-top: .1rem; }
.ccard-name {
  font-family: var(--serif); font-size: 1.45rem; font-weight: 400;
  line-height: 1.15; margin-bottom: .18rem;
}
.ccard-subtitle {
  font-size: .62rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--muted);
}
.ccard-keywords {
  padding: .55rem 1.75rem;
  border-top: 1px solid var(--border);
  font-size: .72rem; color: var(--gold);
  letter-spacing: .03em; line-height: 1.55;
  text-align: center;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden;
}
.ccard-planets {
  font-size: .62rem; letter-spacing: .08em;
  color: var(--muted); margin-top: .25rem;
}
.ccard-body {
  flex: 1; overflow-y: auto; min-height: 0;
  padding: 1.2rem 1.75rem;
}
.ccard-personality {
  font-family: 'Lora', Georgia, serif;
  font-size: .875rem; color: var(--secondary); line-height: 1.82;
  text-align: center;
}
/* Displacement card tokens (rank+suit) — white tiles like the calendar chips,
   so the black clubs/spades read on the dark popup (--surface) and finder ground.
   Shared by the compare popup and the finder/relationship displacement lines. */
.cc-dcard {
  display: inline-block; background: rgba(255,255,255,.94); color: var(--card-dark);
  border: 0; border-radius: var(--r-sm);
  padding: .06em .42em; margin: 0 .08em;
  font-family: var(--serif); font-weight: 600; font-size: .95em; line-height: 1.25;
  vertical-align: baseline;
}
.cc-dcard.hearts, .cc-dcard.diamonds { color: var(--red); }
.cc-dcard.clubs,  .cc-dcard.spades   { color: var(--card-dark); }
button.cc-dcard { cursor: pointer; transition: box-shadow var(--ease), transform var(--ease); }
button.cc-dcard:hover { box-shadow: var(--sh-sm); transform: translateY(-1px); }
@media (prefers-reduced-motion: reduce) { button.cc-dcard:hover { transform: none; } }

/* ── Single-gate hexagram popup (shared chrome) ─────────────────────
   Used by iching, hexagrams and astrology. Shared chrome here; each page
   keeps its own overrides inline: .hx-overlay z-index, .hx-header /
   .hx-body / .hx-keynote padding, .hx-shadow margin, plus astrology's
   .hx-line/.hx-line-tag. Readable oracle text (kw/image/keynote) uses
   --gold-ink for AA contrast. */
.hx-overlay.open { display: flex; }
.hx-popup {
  background: var(--surface); border-radius: 14px;
  /* Permanent gold ring (was the keyboard-focus outline only) + depth shadow. */
  box-shadow: 0 0 0 1.5px var(--gold), 0 20px 60px rgba(0,0,0,.3);
  max-width: 420px; width: 100%; position: relative;
  display: flex; flex-direction: column;
  height: 480px; max-height: 88vh; overflow: hidden;
  animation: hxIn .25s cubic-bezier(.23,1,.32,1);
}
@keyframes hxIn { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: none; } }
.hx-close {
  position: absolute; top: .9rem; right: .9rem; z-index: 2;
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--surface-alt); border: 1px solid var(--rule);
  font-size: .78rem; color: var(--muted);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; transition: all var(--ease);
}
.hx-close:hover { background: var(--rule); color: var(--text); }
.hx-nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
  width: 2rem; height: 3.5rem; display: flex; align-items: center; justify-content: center;
  color: var(--muted); font-size: 1.5rem; opacity: .45; cursor: pointer;
  background: none; border: none; transition: opacity var(--ease), color var(--ease);
}
.hx-nav:hover { opacity: 1; color: var(--text); }
.hx-prev { left: .15rem; } .hx-next { right: .15rem; }
.hx-fig { flex-shrink: 0; display: flex; flex-direction: column; align-items: center; gap: .4rem; }
.hx-fig-svg { background: var(--band); border-radius: 8px; padding: .65rem .75rem; --yang: rgba(255,255,255,.9); }
.hx-fig-num { font-family: var(--mono); font-size: .58rem; color: var(--muted); letter-spacing: .05em; }
.hx-id { flex: 1; min-width: 0; padding-top: .15rem; }
.hx-kw { font-family: var(--mono); font-size: .62rem; letter-spacing: .14em; text-transform: uppercase; color: var(--gold-ink); margin-bottom: .35rem; }
.hx-name { font-family: var(--serif); font-size: 1.85rem; font-weight: 400; line-height: 1.1; margin-bottom: .35rem; }
.hx-trig { font-size: .6rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
.hx-image { font-family: var(--serif); font-style: italic; font-size: 1.05rem; line-height: 1.45; color: var(--gold-ink); padding: 1px 28px 0; text-align: center; }
.hx-body p { font-family: var(--serif); font-size: 16px; line-height: 1.8; color: var(--text); margin-bottom: 10px; text-align: center; }

/* ── Site footer ────────────────────────────────────────────────── */
.site-footer {
  background: var(--accent);
  border-top: 1px solid rgba(255,255,255,.08);
  margin-top: auto;
}
/* On starry pages the footer melts into the starfield too — transparent with no
   top rule, so the fan mark sits straight on the night sky (matches the header). */
body.starry .site-footer { background: transparent; border-top-color: transparent; }
.sf-inner {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 64px;
  max-width: var(--max-w);
  margin: 0 auto;
  padding: 0 2rem;
  color: rgba(255,255,255,.62);   /* AA on the starfield ground (was .4 = 3.79:1) */
  font-size: .72rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  font-weight: 500;
}
/* Rich footer — injected by site.js when .sf-inner is empty (pages with a
   custom footer keep theirs untouched). Minimal: just the fan mark, thin. */
.sf-inner.sf-rich {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  height: auto; padding: .6rem 2rem;
  text-transform: none; letter-spacing: normal; font-weight: 400;
}
.sf-mark { display: inline-flex; line-height: 0; }
.sf-mark svg, .sf-mark img { width: 48px; height: 48px; display: block; }
.sf-spacer { display: block; }
/* Discreet hexagram row — left of the footer on divination pages (iching +
   cardsoflife). Each mark is subtle on the night sky and brightens on
   hover/focus; keyboard-reachable via the global focus ring. */
.sf-seeds { justify-self: start; display: inline-flex; align-items: center; gap: .6rem; }
.sf-seed {
  display: inline-flex; line-height: 0;
  opacity: .2; transition: opacity var(--ease);
}
/* On pages with a single Seed link (legacy callers), keep the standalone mark
   pinned to the start of the grid track the way it always was. */
.sf-inner.sf-rich > .sf-seed { justify-self: start; }
.sf-seed svg { width: 15px; height: 17px; display: block; }
.sf-seed:hover, .sf-seed:focus-visible { opacity: .85; }
.sf-email {
  justify-self: end;
  font-family: var(--sans); font-size: clamp(.52rem, 1.7vw, .7rem); letter-spacing: .03em;
  color: var(--gold); text-decoration: none; white-space: nowrap; min-width: 0;
  transition: opacity var(--ease);
}
.sf-email:hover { opacity: .7; }
.sf-email svg { display: block; }
@media (max-width: 560px) {
  .sf-inner.sf-rich { padding: .55rem 1rem; }
}

/* ════════════════════════════════════════════════════════════════
   Page transitions (shared)
   Smooth same-origin navigations. Where the browser supports cross-
   document View Transitions (current Chrome/Safari) it cross-fades the
   old page into the new one natively — the shared starfield stays put,
   so only the content dissolves. Browsers without them fall back to a
   deep-violet veil wired in js/site.js. All off under reduced-motion.
   ════════════════════════════════════════════════════════════════ */
@view-transition { navigation: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation-duration: .42s; }

/* Veil fallback (non-VT browsers; toggled by js/site.js). A fixed wash in
   the site's own violet ground, so a leave dissolves to the same colour the
   next page opens on — no white flash, no hard cut. */
.pg-veil {
  position: fixed; inset: 0; z-index: 9000; pointer-events: none;
  background: radial-gradient(125% 85% at 50% -8%, #1a1433 0%, #110c21 46%, #0e091a 100%);
  opacity: 0; transition: opacity .34s ease;
}
.pg-veil.show { opacity: 1; }

/* Content cross-fade for popup steppers (the ‹ › arrows). The popup shell
   stays put — only its content region blinks over to the next entry, so
   paging no longer reads as a flat jump-cut. Retriggered from JS via the
   codebase idiom (remove → reflow → add). Opacity-only + short, matching
   the page's existing short-eased motion language. */
@keyframes stepFade { from { opacity: 0; } to { opacity: 1; } }
.step-fade { animation: stepFade .16s ease; }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
  .pg-veil { transition: none; }
  .ccard-popup, .hx-popup { animation: none; }
  /* The closing flip is applied by .ccard-popup.closing (0,2,0), which out-ranks
     the bare .ccard-popup guard above (0,1,0) — restate it here so reduced-motion
     users don't get the 3D flip-out. closeCompareCard() closes immediately in
     that case (it can't wait on an animationend that never fires). */
  .ccard-popup.closing { animation: none; }
  .step-fade { animation: none; }
}
