/* ── Tokens ──────────────────────────────────────────────── */
    :root {
      /* Page aliases only — all base tokens (--bg, --surface, --gold, suits,
         radii, shadows, fonts…) inherit canonically from css/site.css. These
         three are this page's local names for global values. */
      --text-primary:   var(--text);
      --text-secondary: var(--secondary);
      --border-strong:  var(--rule-strong);
    }

    /* ── Reset ───────────────────────────────────────────────── */
    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
    html { scroll-behavior: smooth; scroll-padding-top: 64px; }
    /* overflow-x:hidden stays on <body> only — putting it on <html> makes html
       the scroll container and breaks the sticky .site-header (other pages keep
       it on body, where the header stays pinned). */
    body {
      font-family: var(--sans);
      background: var(--bg);
      color: var(--text-primary);
      line-height: 1.6;
      -webkit-font-smoothing: antialiased;
      overflow-x: hidden;
    }
    a { color: inherit; text-decoration: none; }
    button { cursor: pointer; border: none; background: none; font: inherit; }
    input, select { font: inherit; }
    /* Visible keyboard focus across the page (a11y 2.4.7) */
    :focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; border-radius: 3px; }
    /* Finder label sits on a light band — darker gold for AA contrast.
       The Quadrations / Your Cards / Tools labels stay bright gold on navy. */
    .finder .section-label { color: var(--gold-ink); }
    /* a11y: gold used as reading text on light → darker gold for AA (the
       relationship note in the light popups). */
    .rel-note { color: var(--gold-ink); }

    /* ── Layout ──────────────────────────────────────────────── */
    .container { max-width: var(--max-w); margin: 0 auto; padding: 0 2rem; }
    .section { padding: 5rem 0; }
    .section-label {
      display: block; text-align: center;
      font-size: .68rem; letter-spacing: .18em; text-transform: uppercase;
      font-weight: 600; color: var(--gold); margin-bottom: 0;
    }
    /* In flex containers margins don't collapse — zero out the label margin
       so the gap/margin-bottom on the parent sets the spacing uniformly */
    .finder-inner > .section-label { margin-bottom: 0; }
    .divider { width: 40px; height: 1px; background: var(--gold); margin: 1.25rem 0; }

    /* ── Birth Card Finder ───────────────────────────────────── */
    .finder { background: transparent; padding: 0; }  /* first section since the Card Elements row moved to the page foot (2026-07-06); top gap from main > h1 + .page-section */
    .finder-top {
      max-width: 575px; margin: 0 auto; padding: 0 1.25rem;
      display: flex; flex-direction: column; gap: 0.6rem;
      position: relative;
    }
    .finder-inner {
      max-width: 575px; margin: 0 auto; padding: 0;
      display: flex; flex-direction: column; align-items: flex-start; gap: .75rem;
      /* width:100% (+ min-width:0) makes this a DEFINITE width — its old
         shrink-to-fit width (margin:0 auto suppresses flex-item stretch) was
         sized by the fixed-width finder-form's min-content, so in duo mode on
         narrow phones the box's max-width:100% resolved against an already-
         overflowing 424px parent and never clamped. Definite width → the form
         clamps to the phone width and the wheels compress instead. */
      width: 100%; min-width: 0;
    }
    /* Single-birthday result card — sized to match the relationship
       triptych's centre card so swapping between solo and relationship
       readings keeps the focal card at one consistent size. */
    .result-pip-card { width: 78px; height: 108px; flex-shrink: 0; }
    .rel-result-pip  { border-color: var(--gold) !important; background: var(--card-paper) !important; }
    .finder-form {
      /* STANDARDISED widths (2026-07-06; replaced fit-content, whose width
         drifted with content at every breakpoint): one fixed target per mode,
         the same at every screen size, clamped only by max-width:100% on
         narrow phones. Solo → duo animates the box width itself, in step with
         the partner slot's own .4s slide (same duration + easing), so the two
         reveals share one motion. Numbers: solo fits two tab columns at their
         minimum (~108px each) + padding; duo adds room for the second person
         slot; the wheels are flex and absorb the slack. */
      --fw-solo: 272px;
      --fw-duo: 424px;
      width: var(--fw-solo);
      max-width: 100%;
      transition: width .4s cubic-bezier(.4, 0, .2, 1);
      align-self: center;
      background: var(--surface);
      border: 1px solid var(--border);
      border-radius: var(--r-lg);
      padding: 1.25rem 1.5rem;
      box-shadow: var(--sh-sm);
    }
    .finder-form:has(.person-partner:not(.is-ghost)) { width: var(--fw-duo); }
    @media (prefers-reduced-motion: reduce) { .finder-form { transition: none; } }
    /* With the form width fixed per mode, the person slots flex to fill it —
       person 1 relaxes a little in solo and both people share evenly in duo. */
    .person-slot { flex: 1; }
    .finder-inputs { display: flex; align-items: center; gap: .6rem; }
    /* Source-of-truth selects — the wheel widgets write to them and fire
       change events; the rest of the page (maybeFindUnified, share link,
       populateDays, etc.) keeps reading from these. They live in a
       sibling .finder-dd-row beneath the wheels; that row is hidden by
       default and shown when the user enables dropdown mode via the
       chevron toggle in the .person-divider column. */
    /* Rank/suit card picker — the chevron reveals these two dropdowns in place
       of the (retired) DD/MM selects, styled identically to .finder-dd-row.
       Selecting a rank AND a suit loads that card's reading. */
    .finder-cardpick-row { display: none; }
    .finder-form.cp-on .finder-cardpick-row {
      display: flex; flex-direction: column; align-items: stretch; gap: .4rem;
      margin-top: .55rem; padding-top: .55rem;
      border-top: 1px dashed rgba(199,154,84,.18);
      /* width:0 + min-width:100% keeps this row from driving the fit-content
         form's width (the "♦ Diamonds" selects are wider than the wheels, so
         without this the box stretches every time the picker opens). It
         contributes 0 to the intrinsic pass, then fills the wheel-defined slot
         width in layout. Stacking the two selects vertically then gives EACH
         the full wheel width, so "Queen" / "♦ Diamonds" sit tidily inside the
         box instead of being squeezed into half-width side-by-side selects. */
      width: 0; min-width: 100%;
    }
    .finder-cardpick-row select {
      display: block; width: 100%; flex: none;
      height: 32px; padding: 0 .5rem;
      background: var(--surface-alt); border: 1px solid var(--border);
      border-radius: var(--r-sm); color: var(--gold-ink);
      font-family: var(--sans); font-size: .8rem; cursor: pointer; appearance: auto;
    }
    /* Inset focus ring (offset INWARD): the selects are full-slot-width, and
       the partner slot clips overflow for its slide-in — an outward outline
       would be cut off on the left/right there. Inset draws the ring just
       inside each select, fully visible and identical for BOTH people. */
    .finder-cardpick-row select:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }

    .finder-dd-row { display: none; }
    .finder-form.dd-on .finder-dd-row {
      display: flex; align-items: center; gap: .6rem;
      margin-top: .55rem;
      /* Hairline rule above to signal "shortcut / alternate" rather than
         a second primary control. */
      padding-top: .55rem;
      border-top: 1px dashed rgba(199,154,84,.18);
    }
    /* Native selects styled to sit QUIETER than the wheels — wheels stay
       the headline, dropdowns are the express lane. */
    .finder-dd-row select.fw-bound {
      display: block;
      flex: 1; min-width: 0;
      height: 32px; padding: 0 .5rem;
      background: var(--surface-alt);
      border: 1px solid var(--border);
      border-radius: var(--r-sm);
      color: var(--gold-ink);
      font-family: var(--sans); font-size: .8rem;
      cursor: pointer;
      appearance: auto;
    }
    .finder-dd-row select.fw-bound:focus-visible {
      outline: 2px solid var(--gold); outline-offset: 2px;
    }
    .finder-dd-row select.fw-bound:hover { border-color: rgba(199,154,84,.55); }
    /* ── Scroll-wheel picker ─────────────────────────────────────
       iOS / Co-Star style. Each frame is a fixed-height column that
       scroll-snaps its items into the centre cell (the gold-edged
       horizontal band). The currently-snapped item gets .is-current
       (gold ink + slight scale). The wheel writes the picked value
       to its hidden <select> + dispatches change; an external change
       on the select snaps the wheel back via .fw-snap flag. */
    .fw-frame {
      flex: 1; min-width: 64px;
      height: 128px;
      position: relative;
    }
    .fw-wheel {
      width: 100%; height: 100%;
      background: var(--surface-alt);
      border: 1.5px solid var(--border);
      border-radius: var(--r-sm);
      overflow-y: scroll;
      scroll-snap-type: y mandatory;
      -webkit-overflow-scrolling: touch;
      scrollbar-width: none;
      text-align: center;
      -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 28%, #000 72%, transparent 100%);
              mask-image: linear-gradient(to bottom, transparent 0%, #000 28%, #000 72%, transparent 100%);
      transition: border-color var(--ease);
    }
    .fw-wheel::-webkit-scrollbar { display: none; }
    .fw-wheel:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
    .fw-frame:focus-within .fw-wheel { border-color: var(--gold); }
    .fw-wheel-item {
      scroll-snap-align: center;
      height: 32px; line-height: 32px;
      font-family: var(--sans); font-size: .95rem;
      color: var(--text-muted);
      cursor: pointer;
      transition: color .15s ease, font-weight .15s ease, transform .15s ease;
    }
    .fw-wheel-item:hover { color: var(--gold-ink); }
    .fw-wheel-item.is-current {
      color: var(--gold-ink); font-weight: 600;
      transform: scale(1.05);
    }
    /* Em-dash placeholder at the top of each wheel — the wheel starts
       on this so no value is committed until the user scrolls past it.
       Stays muted even when centred so it never reads as "selected". */
    .fw-wheel-item.placeholder {
      color: var(--text-muted); opacity: .55;
      letter-spacing: .14em; cursor: default;
    }
    .fw-wheel-item.placeholder.is-current {
      color: var(--text-muted); opacity: .7;
      font-weight: 400; transform: none;
    }
    /* Top/bottom pads — let the first and last items scroll into the
       centre band. Pad height = (wheel-height − item-height) / 2. */
    .fw-wheel-pad { height: 48px; pointer-events: none; }
    /* The gold-edged centre band */
    .fw-wheel-cell {
      position: absolute; left: 0; right: 0;
      top: 50%; transform: translateY(-50%);
      height: 32px;
      border-top: 1px solid rgba(199,154,84,.28);
      border-bottom: 1px solid rgba(199,154,84,.28);
      pointer-events: none;
    }
    /* ── Side-by-side person slots ──────────────────────────────
       Inline flex row: [person 1] [chip] [person 2-or-empty]. The
       chip lives between the two slots; in solo mode the partner
       slot's max-width + margin-left collapse to 0 so the form
       fits snug around person 1 + chip. Click + and they both
       transition smoothly, widening the form to make room. The
       transition runs concurrently on the slot (max-width grows)
       and the gap (margin-left grows) so the form's fit-content
       width recalculates each frame in a smooth slide. Stacks
       vertically at <480px. */
    .person-slots {
      display: flex;
      /* Anchor every slot + the divider to the TOP of the row so the
         wheels (and the buttons aligned to them) keep a constant y
         position regardless of whether the dd-row beneath the wheels
         is open or closed. With center-alignment, the partner chip
         would drift downward each time the slot grew taller. */
      align-items: flex-start;
    }
    .person-slot { min-width: 0; }
    .person-divider { margin-left: .65rem; }
    .person-divider {
      display: flex; flex-direction: column;
      align-items: center;
      gap: .45rem;
      /* Pin the partner toggle's CENTRE to the wheel's gold centre-band:
         wheel = 128px tall, centre band at 64px from top; partner-toggle
         is 38px (half = 19px), so its top must sit at 45px. The saved-
         birthday picker (.person-pick, 28px) + the column's .45rem gap now
         occupy that space above the chip, so top-pad = 45 − 28 − 7 = 10px
         keeps the chip's centre on the band. dd-toggle stays .45rem below. */
      padding-top: 10px;
    }
    /* Saved-birthday picker — sits at the TOP of the divider column, above the
       +partner chip. Same quiet chip language as .dd-toggle. */
    .person-pick {
      display: inline-flex; align-items: center; justify-content: center;
      width: 30px; height: 30px; padding: 0; margin: 0;
      background: transparent; color: var(--gold-ink);
      border: 1px solid var(--rule-strong); border-radius: 7px;
      cursor: pointer; opacity: .75;
      transition: opacity var(--ease), border-color var(--ease), color var(--ease);
    }
    /* Bookmark + chevron are DIFFERENT shapes (the bookmark fills ~0.85 of its
       box tall, the chevron only ~0.35), so equal pixel sizes read unequal.
       These sizes (13 / 18) make their optical footprints match — measured. */
    .person-pick svg { width: 13px; height: 13px; display: block; }
    .person-pick:hover { opacity: 1; border-color: var(--gold); color: var(--gold); }
    .person-pick:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
    /* Dropdown-mode toggle — same chip language as the result-panel's
       minimise chevron (.result-toggle), kept smaller and dimmer so it
       reads as a secondary mode control, not a primary action. Sits
       below the +partner chip in the divider column. */
    .dd-toggle {
      display: inline-flex; align-items: center; justify-content: center;
      width: 30px; height: 30px; padding: 0; margin: 0;
      border-radius: 7px;
      background: transparent;
      border: 1px solid var(--rule-strong);
      color: rgba(199,154,84,.75);
      cursor: pointer;
      transition: border-color .2s var(--ease),
                  background .2s var(--ease),
                  color .2s var(--ease),
                  opacity .2s var(--ease),
                  transform .25s var(--ease);
      opacity: .75;
    }
    .dd-toggle svg {
      width: 18px; height: 18px; display: block;   /* optically matches the 13px bookmark (shallower V) */
      transition: transform .25s ease;
    }
    .dd-toggle:hover {
      opacity: 1;
      border-color: var(--gold);
      background: rgba(199,154,84,.08);
      color: var(--gold);
    }
    .dd-toggle:focus-visible {
      outline: 2px solid var(--gold); outline-offset: 2px;
    }
    /* Pickers row — revealed under the wheels by the card-picker chevron
       (.finder-form.cp-on). Two labelled launchers for the deck + calendar
       popups; replaced the old Rank/Suit dropdowns. */
    /* 2×2 grid of compact, text-only tabs (icons dropped, smaller than the old
       flex row so they no longer force the fit-content finder box wider). */
    /* Collapse wrapper — the shared grid-rows 0fr→1fr idiom (same as the
       trays below), so opening the expander UNFURLS instead of display-
       snapping. visibility flips after the collapse so the hidden tabs
       leave the tab order. */
    .finder-pickers-wrap {
      display: grid; grid-template-rows: 0fr; width: 100%;
      visibility: hidden;
      transition: grid-template-rows .3s var(--ease), visibility 0s .3s;
    }
    .finder-form.cp-on .finder-pickers-wrap {
      grid-template-rows: 1fr; visibility: visible;
      transition: grid-template-rows .3s var(--ease);
    }
    .finder-pickers-min { overflow: hidden; min-height: 0; }
    @media (prefers-reduced-motion: reduce) { .finder-pickers-wrap { transition: none; } }
    /* ALWAYS a centred 2×2 — the old 1-col (solo) → 2-col (duo) reflow snapped
       in a single frame. Tracks are minmax(0,160px): at the duo width they sit
       at their full 160px; at the solo width they compress to ~108px (the
       buttons still fit); while the box width animates between modes the
       tracks grow/shrink with it every frame — no snap. */
    .finder-pickers {
      display: grid; grid-template-columns: repeat(2, minmax(0, 160px));
      gap: .4rem; justify-content: center; margin: .9rem auto 0;
    }
    .finder-picker-btn {
      display: inline-flex; align-items: center; justify-content: center; text-align: center;
      font-family: var(--sans); font-size: .6rem; letter-spacing: .05em;
      text-transform: uppercase; font-weight: 600; line-height: 1.1;
      color: var(--gold-ink); background: transparent;
      border: 1px solid var(--rule-strong); border-radius: 7px;
      padding: .4rem .3rem; cursor: pointer;
      transition: border-color var(--ease), color var(--ease), background var(--ease);
    }
    .finder-picker-btn:hover { border-color: var(--gold); color: var(--gold); background: rgba(199,154,84,.08); }
    .finder-picker-btn:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
    /* Active launcher — the tray/popup it controls is open. */
    .finder-picker-btn.is-active { border-color: var(--gold); color: var(--gold); background: rgba(199,154,84,.1); }
    /* Inline calendar tray (prototype) — a collapsible panel under the finder
       box (grid-rows 0fr→1fr, the shared collapse idiom). Reuses .cal-modal for
       the grid's chip colours + panel chrome. */
    .finder-tray { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .3s var(--ease); }
    .finder-tray.open { grid-template-rows: 1fr; }
    .finder-tray-min { overflow: hidden; min-height: 0; }
    /* Trays sit directly on the starfield — no purple panel. An open tray runs
       to its NATURAL height (2026-07-06; the old max-height:62vh + overflow:auto
       made tall trays like Calendar a scrollbox-within-the-page — you had to
       scroll inside the tray to reach its bottom). The page just gets longer;
       the result section sits below wherever the tray ends. */
    .finder-tray .cal-modal {
      width: 100%; max-width: 600px; margin: .9rem auto 0;
      /* max-height:none guards against the ≤500px `.cal-modal{max-height:90vh}`
         rule further down (meant for the OVERLAY calendar) — with no
         overflow:auto here a cap would clip, not scroll. */
      max-height: none;
      background: transparent; box-shadow: none; border-radius: 0; padding: 0;
    }
    @media (prefers-reduced-motion: reduce) { .finder-tray { transition: none; } }
    /* When dropdown mode is on, rotate the chevron 180° — same gesture
       grammar as #rCollapse / the Tools popout. */
    .finder-form.dd-on .dd-toggle svg { transform: rotate(180deg); }
    .finder-form.dd-on .dd-toggle {
      opacity: 1;
      border-color: rgba(199,154,84,.55);
      color: var(--gold);
    }
    .person-toggle {
      appearance: none; padding: 0;
      width: 38px; height: 38px;
      border-radius: 7px;
      background: transparent;
      border: 1px solid var(--rule-strong);
      display: flex; align-items: center; justify-content: center;
      cursor: pointer;
      color: rgba(199,154,84,.85);
      transition: border-color .25s var(--ease),
                  background   .25s var(--ease),
                  color        .25s var(--ease),
                  transform    .25s var(--ease);
    }
    .person-toggle:hover {
      border-color: var(--gold);
      background: rgba(199,154,84,.1);
      color: var(--gold);
      transform: scale(1.08);
    }
    .person-toggle:focus-visible {
      outline: 2px solid var(--gold); outline-offset: 2px;
    }
    .person-toggle-icon {
      display: inline-flex; pointer-events: none;
    }
    /* +/− drawn as a centred SVG (a plus whose vertical stroke is hidden in
       relationship mode → a minus), so it sits dead-centre unlike the old text
       glyph. Sized to optically match the picker + chevron. */
    .person-toggle-icon svg { width: 18px; height: 18px; display: block; }
    .person-toggle .pt-v { transition: opacity .2s var(--ease); }
    .person-slots:has(.person-partner:not(.is-ghost)) .person-toggle .pt-v { opacity: 0; }
    /* Active state — chip becomes the remove control; warm the border to a
       gold tint so it reads as "now engaged", not a neutral invite. */
    .person-slots:has(.person-partner:not(.is-ghost)) .person-toggle {
      border-color: rgba(199,154,84,.55);
    }
    .person-slots:has(.person-partner:not(.is-ghost)) .person-toggle:hover {
      border-color: var(--gold);
    }
    /* Partner slot — collapsed in ghost state, full-width when active.
       Transition on max-width + margin-left + opacity gives a smooth
       slide-out (and slide-in) that takes the form's fit-content width
       along with it. overflow:hidden clips the inputs while the slot
       grows/shrinks so nothing pokes out mid-transition. */
    .person-partner {
      max-width: 220px;
      margin-left: .65rem;
      overflow: hidden;
      opacity: 1;
      transition: max-width .4s cubic-bezier(.4, 0, .2, 1),
                  margin-left .4s cubic-bezier(.4, 0, .2, 1),
                  opacity .25s ease .05s;
    }
    .person-partner.is-ghost {
      max-width: 0;
      margin-left: 0;
      opacity: 0;
      transition: max-width .4s cubic-bezier(.4, 0, .2, 1),
                  margin-left .4s cubic-bezier(.4, 0, .2, 1),
                  opacity .2s ease;
    }
    @media (prefers-reduced-motion: reduce) {
      .person-partner { transition: none; }
      .person-toggle { transition: none; }
    }
    /* Small phones: keep the whole picker in ONE ROW — both people side by
       side, matching desktop — and keep the dropdown shortcut reachable. We
       only shrink the pieces so they fit; the row layout, the divider column,
       the dd-toggle, and the partner's max-width slide-in are all inherited
       from the base rules above.
       (This block previously stacked the two people vertically and hid the
       dropdown toggle + selects, so small-phone users couldn't pick a date by
       dropdown and adding a second person dropped their wheels BELOW the first
       instead of beside it. Reworked 2026-07-03.) */
    @media (max-width: 480px) {
      .finder-form { padding: 1rem .65rem; }   /* tighter so 4 wheels + divider fit */
      .finder-inputs { gap: .3rem; }
      .fw-frame { min-width: 48px; }            /* 64 → 48: two people fit down to ~320px */
      .person-divider { margin-left: .3rem; }
      .person-partner { margin-left: .3rem; }   /* (keeps the base max-width:220 slide-in) */
      /* Dropdown shortcut STAYS available on phones — native selects are the
         reliable tap/keyboard path. The two selects stack vertically under
         each person's wheels, so a person's column stays wheel-width and both
         people still fit side by side even at ~320px (side-by-side selects
         would force the columns too wide). */
      .finder-form.dd-on .finder-dd-row { flex-direction: column; gap: .4rem; }
      .finder-dd-row select.fw-bound { width: 100%; flex: none; }
    }
    .finder-result {
      display: none;
      padding: 1.75rem 0 0;
      width: 100%;
      /* scroll-margin-top keeps the panel from landing under the sticky
         header when loadCardInFinder calls scrollIntoView — the card
         lands just below the header instead of behind it. */
      scroll-margin-top: 80px;
    }
    .finder-result.visible { display: block; }
    /* Entry animation — triggered by JS adding .result-entry on the
       hidden→visible transition only. The helper class exists because
       animations don't reliably play when display flips from none to
       block in the same frame (the browser skips straight to the end
       state); a JS-side reflow between the two class additions makes
       the animation start from frame 0. */
    .finder-result.result-entry,
    .rel-result.result-entry {
      animation: finder-result-in .6s cubic-bezier(.2, .85, .3, 1) both;
    }
    @keyframes finder-result-in {
      from { opacity: 0; transform: translateY(28px) scale(.98); }
      to   { opacity: 1; transform: translateY(0)    scale(1);   }
    }
    /* Exit animation — mirror of the entry, slightly faster. .visible
       stays on the element during this so the panel remains in layout;
       JS strips both classes once the animation finishes. */
    .finder-result.result-exit,
    .rel-result.result-exit {
      animation: finder-result-out .4s cubic-bezier(.4, 0, .8, .4) both;
    }
    @keyframes finder-result-out {
      from { opacity: 1; transform: translateY(0)    scale(1);   }
      to   { opacity: 0; transform: translateY(28px) scale(.98); }
    }
    @media (prefers-reduced-motion: reduce) {
      .finder-result.result-entry,
      .rel-result.result-entry,
      .finder-result.result-exit,
      .rel-result.result-exit { animation: none; }
    }
    .finder-result-inner {
      max-width: var(--max-w); margin: 0 auto; padding: 0 2rem;
      position: relative;
    }
    .result-toggle {
      display: inline-flex; align-items: center; justify-content: center;
      margin: .6rem auto .1rem;
      width: 34px; height: 34px; padding: 0;
      border-radius: 50%;
      background: none; border: 1px solid rgba(255,255,255,.22);
      color: var(--gold); cursor: pointer;
      transition: background .2s, border-color .2s;
    }
    .result-toggle svg { width: 14px; height: 14px; display: block; transition: transform .25s ease; }
    .result-toggle:hover { background: rgba(255,255,255,.07); border-color: rgba(255,255,255,.45); }
    .result-toggle[hidden] { display: none; }
    /* Minimise chevron: points down when collapsed (∨), up when expanded (∧) — the
       button defaults to ∨, so flip it 180° while the panel is open. Same convention
       as the Tools popout chevron. */
    .finder-result:not(.collapsed) .result-collapse svg { transform: rotate(180deg); }
    .result-olney svg { width: 16px; height: 16px; }
    .result-actions { display: flex; gap: .5rem; justify-content: center; }
    /* Tight to the name/subtitle now that the vow has moved into the
       About box — the .result-card-row gap (.65rem) is the only space
       between the head text and these buttons. */
    .result-actions .result-toggle { margin: 0; }
    /* Copy-link confirmation */
    .result-toggle.copied { color: #fff; background: var(--gold); border-color: var(--gold); }
    .result-share svg { width: 13px; height: 13px; }
    /* Minimised: hide the long content; keep the card, name, meta facts and the
       toggle visible. (The Olney panel's collapsed rule is in the Olney block.) */
    .finder-result.collapsed .result-body,
    .rel-result.collapsed .result-body,
    .rel-result.collapsed .rel-bond,
    .rel-result.collapsed .rel-conn-wrap { display: none; }
    .rel-result:not(.collapsed) .result-collapse svg { transform: rotate(180deg); }
    /* "Back to orrery" reload icon — same chip shape as the share/minimise
       buttons, with a tiny gold reload glyph. The CSS rotation animates on
       hover so the affordance is obvious. */
    .result-reload svg { width: 14px; height: 14px; transition: transform .35s ease; }
    .result-reload:hover svg { transform: rotate(-180deg); }
    /* Reserve the button's slot when it's inactive — keeps the rest of the
       row (olney / share / collapse) anchored in the same spot instead of
       shifting as the reload + olney toggle on and off. */
    .result-toggle.is-inactive {
      visibility: hidden;
      pointer-events: none;
    }
    .result-card-row {
      display: flex; flex-direction: column; align-items: center;
      text-align: center; gap: .7rem; margin: 0 auto 1rem;
      /* loadCardInFinder scrolls this row to the top of the viewport.
         html already has scroll-padding-top: 64px (sticky-header
         clearance) globally, so we don't add a scroll-margin-top —
         that would stack on the padding and leave a finder sliver
         visible above the card. */
    }
    .result-card-row .result-kws { justify-content: center; margin: 0; }
    /* Subtitle has its own margin-bottom for use in other contexts; null
       it out here so the only space below "The Grand Magus" is the
       result-card-row flex gap. */
    .result-card-row .result-subtitle { margin-bottom: 0; }
    .result-head-text { flex: unset; min-width: unset; }
    /* Relationship header — centered column matching single-card layout */
    .rel-header {
      display: flex; flex-direction: column; align-items: center;
      text-align: center; gap: .65rem; margin: 0 auto 1rem;
    }
    .rel-result-kws { justify-content: center; }
    .result-head-text { flex: 1; min-width: 0; }
    .result-name { font-family: var(--serif); font-size: 1.5rem; line-height: 1.15; margin-bottom: .25rem; }
    .result-subtitle {
      font-size: .72rem; letter-spacing: .12em; text-transform: uppercase;
      color: var(--ink-muted); margin-bottom: .55rem;
    }
    .result-dates {
      font-size: .78rem; color: var(--gold-ink); font-weight: 500;
      letter-spacing: .06em;
    }
    .result-kws { display: flex; flex-wrap: wrap; gap: .35rem; margin: 1rem 0 .85rem; }
    .kw-tag {
      font-size: .67rem; font-weight: 600; letter-spacing: .07em;
      text-transform: uppercase;
      color: var(--gold-ink);
      /* No background pill — keywords read as plain gold-uppercase tokens.
         .result-kws / .num-kws gap handles the spacing. */
      background: transparent;
      padding: 0;
    }
    .result-personality {
      font-family: 'Lora', Georgia, serif; font-size: .95rem; line-height: 1.85;
      color: var(--text-primary); margin-bottom: 1.25rem; font-style: normal; text-align: center;
      padding: 1rem 1.25rem 1rem 1.15rem;
      background: var(--bg);
      border-left: 3px solid var(--gold);
      border-radius: 0 var(--r-sm) var(--r-sm) 0;
    }
    /* Desktop 2-col layout: personality+life-script left, light+shadow stacked right */
    .result-body {
      display: grid;
      grid-template-columns: 55fr 45fr;
      gap: 1.25rem;
      align-items: stretch;
      margin-bottom: 1rem;
    }
    /* Finder full profile: single centred column. The whole reading lives
       inside the .ns-pop (two-tab chip + shared popout: About tab gathers
       About + Light + Shadow; Essence tab gathers Numerology + Suit), so
       the body just needs to host that popout with a comfortable reading
       width. */
    .finder-result .result-body {
      display: block;
      max-width: 700px;
      margin-inline: auto;
    }
    /* Full profile WITHOUT the surface-alt boxes — sections sit on the
       starfield ground (heading underlines + keynote rule kept for structure) */
    .finder-result .result-box { background: none; border: none; }
    .result-left {
      display: flex; flex-direction: column; gap: 1rem;
    }
    .result-left .result-personality { margin-bottom: 0; }
    .result-right {
      display: flex; flex-direction: column; gap: .75rem;
    }
    .result-right .result-kws { margin: 0; }
    .result-right .result-grid {
      grid-template-columns: 1fr;
      grid-template-rows: auto 1fr;
      gap: 1rem;
      flex: 1;
      height: 100%;
    }
    .result-right .result-grid .result-box:last-child { height: 100%; }
    /* (The Finder-scoped .ls-grid / .ls-card 1:1-with-Quadration overrides were
       removed 2026-07-06 when Life Script moved to the card popup. The
       relationship Connections mini-grid keeps its own --quadration-actual-w
       sizing via .rel-conn below.) */
    .result-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
    .result-box {
      background: var(--surface-alt); border: 1px solid var(--border);
      border-radius: var(--r-md); padding: 1.1rem 1.15rem;
    }
    .result-box h4 {
      font-size: .68rem; font-weight: 600; letter-spacing: .12em;
      text-transform: uppercase; color: var(--text-muted);
      margin-bottom: .65rem; padding-bottom: .5rem;
      border-bottom: 1px solid var(--border);
      text-align: center;
    }
    /* Personality text sits inside a result-box — strip its own box styling */
    .result-box .result-personality {
      background: none; border: none; border-radius: 0; padding: 0; margin: 0;
    }
    /* Card keywords now sit inside the About box (mirrors the Numerology box) */
    .result-box .result-kws { margin: 0 0 .85rem; justify-content: center; }
    /* ── Numerology + Suit: chip-row + shared pop-out ─────────────
       Mirrors the iching trigrams (.tg-pop) pattern — two small chips
       sit in a row; clicking one activates it and grows an elevated
       detail card beneath. Click the active chip again to collapse
       back to the bare row; clicking the OTHER chip swaps the content
       in place. Only one is ever open. */
    /* The popout is now box-less — content sits directly on the page ground
       when open. Only the unfurl animation remains (grid 0fr → 1fr on
       .ns-detailwrap), with the card itself doing the opacity+slide fade. */
    .ns-pop {
      padding: 0;
    }
    /* Detail grows out of the row — same grid-rows 0fr→1fr trick the
       iching trigrams use, paired with an opacity+slide on the card. */
    .ns-detailwrap {
      display: grid; grid-template-rows: 0fr;
      margin-top: 0;
      transition: grid-template-rows .4s var(--ease),
                  margin-top .4s var(--ease);
    }
    .ns-pop.open .ns-detailwrap {
      grid-template-rows: 1fr;
      margin-top: .9rem;
    }
    .ns-detailmin { overflow: hidden; }
    .ns-card {
      opacity: 0; transform: translateY(-6px);
      transition: opacity .35s var(--ease), transform .35s var(--ease);
    }
    .ns-card[hidden] { display: none; }
    .ns-pop.open .ns-card:not([hidden]) { opacity: 1; transform: none; }
    /* Keyword pills inside a popout card — first child has no top
       margin/border to clash with. */
    .ns-card .result-kws {
      justify-content: center;
      gap: .85rem;          /* more breathing room now that the keyword pills are bg-less */
      margin: 0 0 .85rem;
    }
    /* Strip the gold left-rail box from .result-personality when it sits
       inside a popout card — content reads as plain prose on the page
       ground, no nested-box-on-box. */
    .ns-card .result-personality {
      background: none; border: none; border-radius: 0;
      padding: 0; margin: 0;
    }
    /* Vow-text + num-keynote share the same italic-gold-bordered styling;
       nothing extra needed inside .ns-card, but make sure the top border
       reads against the new no-fill popout — bump it to a faint gold. */
    .ns-card .vow-text { border-top-color: rgba(199,154,84,.18); }
    /* Sub-blocks inside a card — each ns-sub is one of the original sections
       (About / Light / Shadow inside the About tab, Numerology / Suit inside
       the Essence tab). Hairline divider between adjacent visible blocks. */
    .ns-sub + .ns-sub {
      margin-top: 1.1rem;
      padding-top: 1.1rem;
      border-top: 1px solid rgba(255,255,255,.06);
    }
    .ns-sub[hidden] { display: none; }
    .ns-sub-h {
      font-size: .68rem; font-weight: 600; letter-spacing: .12em;
      text-transform: uppercase; color: var(--text-muted);
      margin: 0 0 .65rem; padding-bottom: .5rem;
      border-bottom: 1px solid var(--border);
      text-align: center;
    }
    @media (prefers-reduced-motion: reduce) {
      .ns-pop, .ns-detailwrap, .ns-card { transition: none; }
    }
    /* (.result-life-script chrome removed 2026-07-06 — Life Script moved to the
       card popup; see .ccard-life-script.) */
    @media (max-width: 700px) {
      .result-body { grid-template-columns: 1fr; }
      .finder-result .result-body { grid-template-columns: 1fr; }
      .result-body .result-grid { grid-template-columns: 1fr 1fr; }
    }

    /* ── Relationship Finder ─────────────────────────────────────── */
    .rel-result {
      display: none;
      padding: 1.75rem 0 0;
      width: 100%;
      scroll-margin-top: 80px;
    }
    .rel-result.visible { display: block; }
    /* Planets / solar values / displacement live in the card popup
       (click any card to see them), so the duplicate strip under the
       relationship reading is suppressed in BOTH modes. */
    #relRMeta { display: none; }
    /* Relationship header — the "Name × Name" line and the keywords row
       feel redundant when the triptych is already showing the two cards.
       Hide them via CSS so the JS that still writes to them keeps working. */
    .rel-names-pair, .rel-result-kws { display: none; }
    /* Relationship cards row — orrery-style triptych. Layout is
       [parent₁]  [RESULT]  [parent₂] with the result card large and
       centred (matching the single-birthday result-pip-card) and the
       two contributing cards as smaller flankers on either side. The
       DOM order is still parent₁ + parent₂ = result so the equation
       semantics are preserved for assistive tech and the existing JS
       wiring; CSS `order` puts the result visually in the middle. The
       +/= operator glyphs are hidden because the visual hierarchy
       already reads as "these two make that one". */
    .rel-equation {
      display: flex; align-items: center; justify-content: center;
      gap: 1.1rem; flex-wrap: nowrap;
    }
    .rel-equation .rel-eq-op { display: none; }
    .rel-equation #relCardResult { order: 2; }
    .rel-equation #relCard2      { order: 3; }
    .rel-equation .spread-card {
      width: 50px; height: 69px; flex-shrink: 0;
      cursor: pointer; opacity: .9;
      transition: transform .15s ease, box-shadow .15s ease, opacity .2s ease;
    }
    .rel-equation .spread-card.rel-result-pip {
      width: 78px; height: 108px; opacity: 1;
    }
    .rel-equation .spread-card:hover {
      transform: translateY(-4px); opacity: 1;
      box-shadow: 0 6px 16px rgba(22, 16, 41, .22);
    }
    @media (prefers-reduced-motion: reduce) {
      .rel-equation .spread-card { transition: none; }
      .rel-equation .spread-card:hover { transform: none; }
    }
    .rel-note {
      text-align: center; max-width: 460px; margin: 0 auto;
      font-family: var(--serif); font-style: italic;
      font-size: .95rem; color: var(--gold); line-height: 1.75;
      background: var(--gold-light);
      border-radius: var(--r-md); padding: .85rem 1.25rem;
    }
    .rel-eq-op {
      font-family: var(--serif); font-size: 1.5rem;
      color: var(--border-strong); flex-shrink: 0;
    }
    .rel-names { text-align: center; }
    .rel-names-pair {
      font-size: .75rem; color: var(--text-muted); margin-bottom: .35rem; line-height: 1.5;
    }
    .rel-result-name { font-family: var(--serif); font-size: 1.25rem; margin-bottom: .3rem; }
    .rel-result-kws  { display: flex; flex-wrap: wrap; gap: .35rem; margin-bottom: .5rem; }
    /* Bond text (REL_TEXT) — replaces the generic profile in the relationship result */
    .rel-bond { max-width: 640px; margin: .9rem auto 0; }
    .rel-bond-syn {
      font-family: var(--serif); font-style: italic;
      font-size: 1.1rem; color: var(--text-primary);
      text-align: center; line-height: 1.55; margin: 0 0 .85rem;
    }
    .rel-bond-para {
      font-size: .88rem; color: var(--text-secondary); line-height: 1.75; margin: 0;
    }
    /* (Removed dead `.tool-btn` rule — unused; the canonical `.btn` in
       css/site.css now covers this neutral-utility-button look.) */

    /* ── Tools Section (bottom of page) ──────────────────────────── */
    /* Self-contained box matching the Quadrations panel: the section is
       transparent and just insets it from the viewport edges, and the navy
       --band sits on the inner .container as a rounded panel. The inner tool
       panels sit on the darker --accent inside. */
    .tools-section { padding: 0 1.25rem 1.25rem; }   /* last .page-section: keep a bottom inset above the footer */
    .tools-section .container {
      background: transparent;
      border: none;
      border-radius: var(--r-lg);
      padding: 0 2rem;
    }
    .tools-section.collapsed .container { padding: 1.25rem 2rem; }
    .tools-section.collapsed { padding: 0 1.25rem 1.25rem; }
    .tools-section.collapsed .tools-grid,
    .tools-section.collapsed .tool-panels { display: none; }
    /* Flat: the button row + active panel sit directly on the starfield — no
       framed card and no inner panel box (the nested purple boxes were removed).
       The per-panel grid-rows 0fr→1fr grow still drives the panel's own height. */
    .tools-pop { border: 0; padding: 0; }
    .tools-grid {
      display: flex; flex-wrap: nowrap; justify-content: center;
      gap: .5rem;
    }
    .tools-grid .tool-card-btn { flex: 0 0 auto; }
    @media (max-width: 560px) {
      /* 4 cards (incl. the longer "The Full Deck" / "Synchronicities" labels)
         no longer fit one nowrap row at phone widths — wrap to 2x2 rather
         than squeeze or overflow. */
      .tools-grid { flex-wrap: wrap; gap: .3rem; }
      .tools-grid .tool-card-btn { flex: 1 1 40%; padding: .5rem .3rem; }
    }
    /* Flat gold buttons — no box; the selected one gets the same gold outline
       the suits/trigrams use when active. */
    .tool-card-btn {
      display: flex; align-items: center; justify-content: center; gap: .5rem;
      background: none; border: none;
      border-radius: 4px; padding: .5rem .85rem; min-height: 44px;
      font-family: var(--serif); font-size: clamp(.82rem, 2.4vw, 1.05rem); font-weight: 400;
      line-height: 1.15; overflow-wrap: anywhere;
      color: var(--gold); text-align: center; cursor: pointer;
      transition: color var(--ease), transform var(--ease), text-shadow var(--ease);
    }
    .tool-card-text { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
    .tool-card-name { line-height: 1.2; }
    .tool-card-btn:hover {
      color: var(--gold-ink); transform: translateY(-1px);
      text-shadow: 0 0 14px rgba(199,154,84,.4);
    }
    .tool-card-btn[aria-expanded="true"] {
      color: var(--gold);
      outline: 2px solid var(--gold); outline-offset: 3px; border-radius: 4px;
    }
    /* Inline panels live in their own stack below the button grid.
       Expand/collapse via grid-template-rows 0fr→1fr (animates cleanly at
       any content height — no max-height guess). Accordion: JS keeps at
       most one panel open. */
    .tool-panels { display: block; }
    /* When a tool is open, let the Tools section fill the viewport so the selected
       tool can scroll right up to the top — while the footer still rests at the
       bottom of the screen (no oversized void below the panel). Tall tools that
       already exceed the viewport keep their natural height.
       Scoped to .section-open: a .tool-panel can stay .open underneath even
       while the whole Tools section is minimised (its own open/close state is
       independent of the panel it's remembering) — :has() doesn't care that
       .section-bodymin has collapsed the content to 0 height, so without this
       guard the section stayed forced to full-viewport height while minimised,
       leaving a tall gap between the collapsed header and the footer. */
    .tools-section.section-open:has(.tool-panel.open) { min-height: 100svh; }
    /* Inline accordion mechanics — shared by every self-toggling ("this
       element's own .open class, not an ancestor's") inline panel that
       expands in normal document flow rather than floating: .tool-panel
       here, and .q-settings-panel over in Quadrations. Both used to be
       independent hand-rolled copies of the same grid-template-rows 0fr→1fr
       trick; consolidated here so a future tweak — or another instance of
       the padding-leak bug already fixed twice separately — only has to
       happen once. Deliberately NOT merged with the page-level
       .section-bodywrap/.section-bodymin pattern in site.css: that one
       collapses off an ANCESTOR's .section-open state via one global
       toggleSection() call, whereas every panel here toggles its own local
       .open class — different enough (one global toggle vs. several
       independent self-toggling panels) that folding the two together
       wasn't worth the site-wide blast radius.
       The child (.tool-modal / .q-settings-inner) must be able to reach
       height 0: border-box height can never go below the element's own
       padding/border, so the closed state also zeroes those — a no-op for
       .tool-modal (no border/background of its own to zero) but needed for
       .q-settings-inner's boxed chrome, defined locally over in Quadrations. */
    /* (.q-settings-panel used to share this inline-accordion mechanism; it is now
       a floating popup — see its own rules below — so only the retired
       .tool-panel keeps these.) */
    .tool-panel {
      display: grid;
      grid-template-rows: 0fr;
      opacity: 0;
      visibility: hidden;
      transition: grid-template-rows .35s ease, opacity .25s ease,
                  margin .25s ease, visibility 0s linear .35s;
    }
    .tool-panel.open {
      grid-template-rows: 1fr;
      opacity: 1;
      visibility: visible;
      margin-top: .6rem;
      transition: grid-template-rows .35s ease, opacity .25s ease, margin .25s ease;
    }
    /* Suppress this element's own open/close animation for one state change —
       used when a tool-panel is force-closed as a side effect of minimising
       the whole Tools section, so its close doesn't run concurrently with
       (and visually fight) the outer section's own collapse. See
       _closeAllToolPanels()/toggleToolPanel() in cardsoflife.js. */
    .tool-panel.no-anim, .tool-panel.no-anim > .tool-modal { transition: none !important; }
    /* Targets the specific child classes (not a bare `> *`) so this keeps the
       same 3-class specificity as before consolidation — needed to reliably
       beat each component's own chrome rule below (.tool-panel .tool-modal's
       padding-top/bottom) regardless of source order; a universal `> *`
       selector drops to 2 classes and loses that tie-break, which is exactly
       what broke Tools panels (30px of dead space left when "closed") during
       this consolidation until caught by a live height check. */
    .tool-panel > .tool-modal {
      min-height: 0; overflow: hidden;
      transition: padding .35s ease, border-color .35s ease, background .35s ease;
    }
    .tool-panel:not(.open) > .tool-modal {
      padding-top: 0; padding-bottom: 0;
    }
    @media (prefers-reduced-motion: reduce) {
      .tool-panel, .tool-panel.open, .tool-panel > .tool-modal { transition: none; }
    }
    /* Tool panels render inline with NO inner box — transparent, no shadow or
       radius, so the content sits directly on the starfield (flat Tools section). */
    .tool-panel .tool-modal {
      margin: 0 auto; animation: none; max-height: none;
      background: transparent; box-shadow: none; border-radius: 0;
      padding-top: .9rem; padding-bottom: 1rem;
    }
    .tool-panel .cal-scroll-wrap { padding-bottom: 0; }

    /* ── Browse All Cards ────────────────────────────────────── */
    /* Each browse cell carries its solar value above the card face */
    .browse-cell { display: flex; flex-direction: column; align-items: center; gap: .2rem; }
    /* Deck cards render as calendar-style tiles (roomier than the dense calendar
       grid, so the token is a touch larger). */
    .browse-cell .cal-chip { width: 100%; padding: .45rem 0; cursor: pointer; }
    .browse-cell .cal-token { font-size: .82rem; }
    @media (max-width: 560px) { .browse-cell .cal-token { font-size: .68rem; } }
    .browse-sv {
      font-family: var(--serif); font-size: .95rem; font-weight: 700; line-height: 1;
      color: var(--gold); font-variant-numeric: tabular-nums; letter-spacing: .02em;
    }
    @media (max-width: 560px) { .browse-sv { font-size: .8rem; } }
    /* Solar-value formula beneath the deck */
    .browse-sv-formula {
      text-align: center; margin: 1.1rem 0 .1rem;
      font-family: var(--serif); font-style: italic; font-size: .95rem;
      color: rgba(255,255,255,.55); letter-spacing: .01em;
    }
    .browse-sv-formula strong { font-style: normal; font-weight: 700; color: var(--gold); }

    /* Interactive solar-value calculator (top of All Cards): type a DD/MM and the
       worked formula + resulting value update live, highlighting the card below. */
    .sv-calc { text-align: center; margin: .2rem 0 1.1rem; }
    .sv-calc-inputs { display: flex; align-items: flex-end; justify-content: center; gap: .5rem; margin-bottom: .55rem; position: relative; }
    .sv-field { display: flex; flex-direction: column; align-items: center; gap: .25rem; }
    .sv-field-lbl { font-family: var(--sans); font-size: .58rem; letter-spacing: .12em; text-transform: uppercase; color: var(--gold-ink); }
    .sv-calc input[type="number"] {
      width: 3.4rem; height: 2.6rem; text-align: center;
      background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.18);
      border-radius: var(--r-sm); color: #fff;
      font-family: var(--serif); font-size: 1.15rem; font-variant-numeric: tabular-nums;
      transition: border-color var(--ease), background var(--ease);
    }
    .sv-calc input[type="number"]:focus-visible { border-color: var(--gold); outline: none; background: rgba(255,255,255,.1); }
    .sv-calc input[type="number"]::-webkit-outer-spin-button,
    .sv-calc input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
    .sv-calc input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
    .sv-slash { font-family: var(--serif); font-size: 1.3rem; color: var(--ink-muted); align-self: center; padding-bottom: .35rem; }
    .sv-calc .browse-sv-formula { margin: 0; }
    .sv-result-card { font-style: normal; font-weight: 600; color: var(--gold-ink); }
    /* Found-card highlight in the grid — a gold ring, no scroll. */
    .browse-cell.sv-found .cal-chip {
      box-shadow: 0 0 0 2px var(--gold), 0 0 16px rgba(199,154,84,.55);
    }

    /* Parchment chips: a card-face background so the pips read in their true
       red / black, like the cards themselves (was white/red on dark purple). */
    .suit-filter-btn {
      display: inline-flex; align-items: center; justify-content: center;
      width: 2.75rem; height: 2.75rem; border-radius: 999px;
      border: 1.5px solid #e3d8c4;
      font-size: 1.05rem; line-height: 1;
      background: var(--card-paper);
      cursor: pointer;
      transition: border-color var(--ease), box-shadow var(--ease), transform var(--ease);
    }
    .suit-filter-btn.hearts,  .suit-filter-btn.diamonds { color: var(--red); }
    .suit-filter-btn.clubs,   .suit-filter-btn.spades   { color: var(--card-dark); }
    .suit-filter-btn:hover { border-color: var(--gold); box-shadow: var(--sh-sm); transform: translateY(-1px); }
    .suit-filter-btn.active { border-color: var(--gold); box-shadow: 0 0 0 2px var(--gold); }

    /* ── Suit filter trigger + popup (All Cards) ──
       The 4 suit-filter-btn chips above now live inside a small popup instead
       of their own always-visible row; this block is just the trigger + the
       popup's own positioning/chrome. .suit-menu docks to the right of the
       dd/mm inputs via position:absolute (against .sv-calc-inputs, which
       gets position:relative for this) rather than a flex margin-left:auto —
       an auto margin would've claimed the row's free space and dragged the
       day/month fields left with it, off dead-centre. Taking the icon out of
       flow instead leaves .sv-calc-inputs' own justify-content:center to
       centre the day/slash/month trio exactly as it did before the icon
       existed, while the icon floats independently over the right edge. */
    .suit-menu { position: absolute; right: 0; top: 50%; transform: translateY(-50%); }
    .suit-menu-icon {
      display: grid; grid-template-columns: repeat(2, 1fr); gap: 1px; width: 1.1em;
    }
    .suit-menu-icon span { display: block; font-size: .56rem; line-height: 1.15; text-align: center; }
    .suit-menu-icon .hearts, .suit-menu-icon .diamonds { color: var(--red); }
    .suit-menu-icon .clubs,  .suit-menu-icon .spades   { color: var(--card-dark); }
    /* Single active-suit state: one big pip instead of the 2x2 idle cluster. */
    .suit-menu-icon.suit-menu-icon--single { display: block; width: auto; }
    .suit-menu-icon--single span { font-size: 1.15rem; }
    .suit-menu-btn[aria-expanded="true"] { border-color: var(--gold); box-shadow: 0 0 0 2px var(--gold); }

    /* Small floating menu, anchored bottom-right of the trigger. Deliberately
       tiny (4 chips, no controls) — the Quadrations settings popover was
       retired in favour of an inline accordion because a bigger floating
       panel overlapped the card grid on small screens; this one is a fraction
       of that size and opens downward over content that's already tall
       (the card grid), so the same failure mode shouldn't apply. Revisit if
       it ever does. */
    .suit-menu-pop {
      position: absolute; top: calc(100% + .5rem); right: 0; z-index: 20;
      display: flex; gap: .4rem; padding: .5rem;
      background: var(--band); border: 1px solid var(--rule-strong);
      border-radius: var(--r-md); box-shadow: var(--sh-lg);
      opacity: 0; visibility: hidden; pointer-events: none;
      transform: translateY(-4px) scale(.97);
      transition: opacity var(--ease), transform var(--ease), visibility 0s linear .18s;
    }
    .suit-menu-pop.open {
      opacity: 1; visibility: visible; pointer-events: auto; transform: none;
      transition: opacity var(--ease), transform var(--ease);
    }
    @media (prefers-reduced-motion: reduce) { .suit-menu-pop { transition: none; } }
    @media (max-width: 400px) { .suit-menu-pop { gap: .3rem; padding: .4rem; } }

    /* Compare-popup + shared card animations (the old card modal was removed;
       cardFlip/cardFlipOut + .ccard-popup.closing below are used by the live
       compare-card popup). */
    @keyframes cardFlip {
      from { opacity: 0; transform: perspective(600px) rotateY(75deg) scale(.97); }
      to   { opacity: 1; transform: perspective(600px) rotateY(0deg)  scale(1); }
    }
    @keyframes cardFlipOut {
      from { opacity: 1; transform: perspective(600px) rotateY(0deg)  scale(1); }
      to   { opacity: 0; transform: perspective(600px) rotateY(75deg) scale(.97); }
    }
    .ccard-popup.closing {
      animation: cardFlipOut .2s cubic-bezier(.55,0,.68,.07) forwards;
    }
    /* Vow now sits inside the About box, below the personality text —
       styled like the num-keynote at the bottom of Numerology / Suit
       (italic, gold, top-bordered). Auto-hides if the card has no vow. */
    .vow-text {
      font-family: var(--serif); font-style: italic;
      font-size: .9rem; color: var(--gold);
      line-height: 1.5; text-align: center;
      margin: .85rem 0 0; padding-top: .7rem;
      border-top: 1px solid var(--border);
    }
    .vow-text:empty { display: none; }
    .sc-item {
      display: flex; align-items: flex-start; gap: .5rem;
      font-size: .84rem; color: var(--text-secondary);
      margin-bottom: .4rem; line-height: 1.5;
    }
    .sc-dot-s { color: var(--gold);       font-size: .45rem; margin-top: .42rem; flex-shrink: 0; }
    .sc-dot-c { color: var(--text-muted); font-size: .45rem; margin-top: .42rem; flex-shrink: 0; }

    /* ── Life Script grid (finder result) ──── */
    .ls-grid {
      display: grid; grid-template-columns: repeat(7, 1fr);
      gap: .4rem; align-items: end;
    }
    .ls-col {
      display: flex; flex-direction: column; align-items: center; gap: .35rem;
      min-width: 0;
    }
    .ls-planet { font-size: .95rem; color: var(--text-muted); line-height: 1; }
    .ls-planet-name {
      font-size: .52rem; letter-spacing: .1em; text-transform: uppercase;
      color: var(--text-muted); line-height: 1;
    }
    .ls-card {
      width: 100%; aspect-ratio: 5 / 7;
      min-width: 0 !important; min-height: 0 !important;
    }
    .ls-joker-note {
      grid-column: 1 / -1;
      font-family: var(--serif); font-style: italic;
      font-size: .9rem; line-height: 1.7;
      color: var(--text-secondary);
      /* Sits directly on the popup ground — no fill/rounding. The horizontal
         inset comes from the wrapper (.ccard-life-script padding), so the note
         aligns with the rest of the popup text. */
      padding: 0;
      text-align: center;
    }
    /* Joker popup drops the "Life Script" heading (the note isn't a life script). */
    .ccard-life-script.ls-is-joker h4 { display: none; }

    @media (max-width: 700px) {
      .ls-grid { gap: .3rem; }
      .ls-planet { font-size: .85rem; }
      .ls-planet-name { font-size: .48rem; }
    }

    /* ── Life Script inside the card compare-popup ──── */
    /* Sits between .ccard-body (the scrolling reading) and .ccard-footer as a
       fixed strip. The generic .ls-grid / .ls-card rules above do the layout;
       here we only add the wrapper chrome + a natural width cap. No
       --quadration-actual-w matching (that was Finder-specific) — the seven
       cards just size within the popup's own max-width. */
    .ccard-life-script {
      padding: .85rem 1.75rem 1rem;
      background: var(--surface);
      /* Flexible scroll region so every standard popup can share ONE fixed
         height regardless of how many rows the dates line wraps to — a taller
         header just shrinks this area rather than growing the whole box. The
         Joker shows a long note here (instead of the seven cards); it simply
         scrolls within this region. */
      flex: 1 1 auto;
      min-height: 0;
      overflow-y: auto;
      /* No border-top: in a plain card popup this follows the header directly,
         whose border-bottom already separates it (the body between them is
         hidden). */
    }
    /* Standard card popups target ONE height (450px) so a card whose dates line
       wraps to a second row doesn't make a taller box than a single-row one, and
       the Joker (long note, scrolled) matches too. The dates line itself now
       reserves a constant 2-line height (see .ccard-dates min-height below), so
       the header is the same height on every card and the Life Script always
       lands in the same place, fully visible without scrolling — the box height
       here just gives that reserved layout room to breathe. In Time period
       popups keep their own taller size + scrolling body. */
    .ccard-popup { height: 476px; }
    .ccard-popup.cc-period { height: 500px; }
    /* Installed PWA (standalone / WCO / etc.): a browser or OS minimum-font-size
       enlarges the small header text (dates, subtitle, planets, displacement),
       so the SAME card needs more height than in a normal browser tab — a fixed
       430px would clip it there. Give the popup a taller fixed size in app mode
       so every popup is still identical AND nothing is cut off; the browser keeps
       the compact 430/460. The Life Script region (flex + overflow) absorbs the
       header difference and scrolls the Joker's note, so all popups match. */
    @media (display-mode: standalone), (display-mode: window-controls-overlay),
           (display-mode: minimal-ui), (display-mode: fullscreen) {
      .ccard-popup, .ccard-popup.cc-period { height: 560px; }
    }
    .ccard-life-script h4 {
      font-size: .6rem; font-weight: 600; letter-spacing: .14em;
      text-transform: uppercase; color: var(--muted);
      text-align: center; margin: 0 0 .6rem;
    }
    /* Container so the labels (and glyphs) can size in cqw — i.e. proportional
       to the popup/card width — instead of a fixed size that only happens to fit
       at one width. */
    .ccard-life-script { container-type: inline-size; }
    .ccard-life-script .ls-grid { max-width: 340px; margin: 0 auto; }
    /* Planet label + glyph scale WITH the card width (cqw), clamped readable, so
       a narrower popup shrinks them to fit rather than clipping. Each label is
       also locked inside its own column and capped to the card width: if a
       browser/OS minimum-font-size (common in installed PWAs) forces it larger
       than that floor allows, it clips with an ellipsis in its own lane —
       NEP…, URA… — rather than colliding with its neighbours. */
    .ccard-life-script .ls-col { min-width: 0; overflow: hidden; }
    .ccard-life-script .ls-planet { font-size: clamp(.72rem, 4cqw, 1rem); }
    .ccard-life-script .ls-planet-name {
      font-size: clamp(.4rem, 2.3cqw, .56rem);
      max-width: 100%; white-space: nowrap;
      overflow: hidden; text-overflow: ellipsis; letter-spacing: .02em;
    }

    /* ── Relationship life-script connections ──── */
    /* One mini life-script grid per direction found (the partner's card lit
       up in the seat it occupies); the whole block hides when there's none. */
    .rel-conn-wrap { margin: .35rem auto 1.1rem; }
    .rel-conn-h {
      text-align: center; font-family: var(--serif);
      font-size: 1.1rem; color: var(--gold-ink);
      letter-spacing: .04em; margin: 0 0 .9rem;
    }
    .rel-conn { max-width: var(--quadration-actual-w, var(--quadration-max-w)); margin: 0 auto 1.4rem; }
    .rel-conn:last-child { margin-bottom: 0; }
    .rel-conn-title {
      text-align: center; font-family: var(--serif);
      font-size: 1.02rem; color: var(--text-primary);
      line-height: 1.4; margin-bottom: .65rem;
    }
    .rel-conn-title b { color: var(--gold-ink); font-weight: 600; }
    .rel-conn-glyph { color: var(--gold); font-size: 1.2rem; }
    .rel-conn-gloss {
      text-align: center; font-family: var(--serif); font-style: italic;
      font-size: .92rem; line-height: 1.65; color: var(--text-secondary);
      margin: .7rem auto 0; max-width: 340px;
    }
    .ls-card.ls-conn-pick {
      border-color: var(--gold);
      box-shadow: 0 0 0 2px var(--gold), 0 0 0 4px rgba(205,168,95,.20),
                  0 0 14px 2px rgba(205,168,95,.55);
      position: relative; z-index: 4;
      animation: finderGlow 2.6s ease-in-out infinite;
    }
    @media (prefers-reduced-motion: reduce) {
      .ls-card.ls-conn-pick { animation: none; }
    }

    /* ── Annual Spreads ──────────────────────────────────────── */
    /* The Quadrations panel is a self-contained box (not a full-bleed band):
       the section is transparent and just insets it from the viewport edges,
       and the navy --band sits on the inner .container as a rounded panel. */
    .spreads { padding: 0 1.25rem; }   /* gap from --section-gap; horizontal inset only */
    .spreads .container {
      background: transparent;
      border: none;
      border-radius: var(--r-lg);
      padding: 0 1.75rem;
    }
    .spreads-top {
      display: flex; flex-direction: column;
      align-items: center; gap: 0.6rem; margin-bottom: 0.5rem;
    }
    .spreads-top h2 { font-family: var(--serif); font-size: 1.8rem; font-weight: 400; color: rgba(255,255,255,.9); }
    .spreads-top p  { font-size: .9rem; color: rgba(255,255,255,.5); line-height: 1.75; max-width: 560px; margin-top: .5rem; }
    .spreads .age-btn { background: rgba(255,255,255,.08); border-color: rgba(255,255,255,.12); color: rgba(255,255,255,.7); }
    .spreads .age-btn:hover { background: rgba(255,255,255,.16); color: #fff; }
    .spreads .age-input { background: rgba(255,255,255,.08); border-color: rgba(255,255,255,.12); color: rgba(255,255,255,.9); }
    .spreads .age-input:focus { border-color: var(--gold); }
    .spreads .planet-col-label  { color: rgba(255,255,255,.38); }
    /* Contextual controls, sharing one slot (.q-age-slot) so the row never
       grows a second line: the age stepper shows only in Age mode, and the
       current-mode label (.q-mode-current) fills the same spot the rest of
       the time — "Spirit" / "Life" replacing the −/+ stepper rather than
       sitting in a row of its own. */
    .q-age-slot .age-controls { display: none; }
    .age-controls-wrap.qm-annual .q-age-slot .age-controls { display: flex; }
    .q-age-slot .q-mode-current { display: none; }
    .age-controls-wrap:not(.qm-annual) .q-age-slot .q-mode-current { display: inline-block; }

    .spread-layout {
      display: flex; gap: 1.25rem; align-items: flex-start;
      justify-content: center;
    }
    /* Wrapper that hugs the grid so the age selector can tuck into its corner */
    .spread-stack { position: relative; display: block; width: 100%; }
    .age-selector {
      display: flex; align-items: center; justify-content: center;
    }
    .age-controls { display: flex; align-items: center; gap: .5rem; }
    .age-btn {
      width: 32px; height: 32px; border-radius: 50%;
      background: var(--surface); border: 1px solid var(--border);
      font-size: 1.1rem; color: var(--text-secondary);
      display: flex; align-items: center; justify-content: center;
      transition: all var(--ease); cursor: pointer;
    }
    .age-btn:hover { background: var(--border); color: var(--text-primary); }
    .age-input {
      width: 60px; height: 32px;
      border: 1px solid var(--border); border-radius: var(--r-sm);
      background: var(--surface); color: var(--text-primary);
      font-family: var(--sans); font-size: .9rem; font-weight: 500;
      text-align: center; padding: 0 .4rem;
      -moz-appearance: textfield;
    }
    .age-input::-webkit-inner-spin-button,
    .age-input::-webkit-outer-spin-button { -webkit-appearance: none; }
    .age-input:focus { outline: none; border-color: var(--gold); }

    /* ── Crown-row bookend cells ──────────────────────────────────────
       The crown row's two leading / two trailing spacer columns are merged
       into a pair of host cells (buildSpreadGrid): the Joker tile on the left,
       the relocated settings gear + age wheel on the right. Each spans two of
       the seven columns; content is centred so the middle three crown seats
       keep their exact position. */
    .crown-side { grid-column: span 2; min-width: 0; }
    .crown-joker { display: flex; align-items: center; justify-content: center; }
    /* The Joker sits jauntily tilted (it belongs to no seat) — a static rotate,
       kept through hover so the shared .spread-card:hover lift doesn't reset it. */
    .crown-joker .spread-card { width: 48%; aspect-ratio: 5 / 7; transform: rotate(-70deg); }
    .crown-joker .spread-card:hover { transform: rotate(-70deg) translateY(-2px); }
    /* The controls cell is a query container so the age stepper can scale as a
       percentage of the cell width — otherwise it overflows / breaks when the
       grid narrows. The gear is pinned to the cell's top-right corner; the age
       stepper (or, in Spirit/Life, the mode label) sits centred. */
    .crown-controls {
      position: relative;
      container-type: inline-size;
      /* display:block (not flex) — a flex container reports the wrong query
         inline-size here, so cqw wouldn't track the cell width. The age
         selector fills the height and centres itself instead. */
      display: block;
      min-width: 0;
    }
    /* The age selector + gear are one centred group (the gear is appended into
       .age-selector by js/cardsoflife.js), so they scale and align together
       instead of the gear floating in the cell corner. Stacked as a column with
       the gear on top (order:-1), the stepper below. */
    .crown-controls .age-selector { height: 100%; flex-direction: column; gap: clamp(4px, 5cqw, 9px); }
    .crown-controls #qSettingsBtn { flex: 0 0 auto; order: -1; }
    /* The stepper scales with the cell width via cqw (≈% of the cell): each
       part's box is sized in cqw, clamped readable-at-widest / legible-at-
       narrowest, so it never overflows when the grid narrows. min-width:0 lets
       the field shrink past its intrinsic width. Scoped by #crownControls (id)
       so it beats the base .age-btn / .age-input / .spreads .age-btn rules.
       (cqw only tracks the cell because .crown-controls is display:block.) */
    #crownControls .age-controls { font-size: clamp(7.5px, 10cqw, 15px); gap: clamp(2px, 3cqw, 6px); }
    #crownControls .age-btn {
      width: clamp(15px, 20cqw, 30px); height: clamp(15px, 20cqw, 30px);
      font-size: 1em; min-width: 0; flex: 0 0 auto; padding: 0;
    }
    #crownControls .age-input {
      width: clamp(20px, 27cqw, 46px); height: clamp(15px, 20cqw, 30px);
      font-size: 1em; min-width: 0; padding: 0 2px; border-radius: var(--r-sm);
    }
    #crownControls .q-mode-current { font-size: clamp(.5rem, 9cqw, .62rem); }

    /* ── Reading direction (settings: "Read left to right") ──────────
       The spread is authored right-to-left (seat DOM order runs col 6→0, so
       position 0 — the first card read — sits at the RIGHT of each row).
       Toggling .q-ltr on #annualGrid mirrors the whole board with grid
       `direction: rtl`: auto-placement now flows right→left, so the same DOM
       order lands position 0 at the LEFT. The planet column labels are
       emitted in the same 6→0 order, so they mirror with (and stay under)
       their columns for free. Seats/labels get `direction: ltr` back so the
       cards' own text and the ghost-chip row lay out normally — only the
       grid PLACEMENT mirrors. */
    .spread-grid.q-ltr { direction: rtl; }
    .spread-grid.q-ltr .sl-seat,
    .spread-grid.q-ltr .planet-col-label { direction: ltr; }
    /* Crown row: the three crown seats mirror with the board (49·50·51 now
       reads left→right), but the two bookend host cells are chrome, not part
       of the reading — the Joker stays LEFT and the gear + age wheel stay
       RIGHT. The row resets to ltr and `order` re-sequences its auto-placed
       children (DOM order is joker, 51, 50, 49, controls). */
    .spread-grid.q-ltr .crown-row { direction: ltr; }
    .spread-grid.q-ltr .crown-row .crown-joker { order: 0; }
    .spread-grid.q-ltr .crown-row .sl-seat[data-pos="49"] { order: 1; }
    .spread-grid.q-ltr .crown-row .sl-seat[data-pos="50"] { order: 2; }
    .spread-grid.q-ltr .crown-row .sl-seat[data-pos="51"] { order: 3; }
    .spread-grid.q-ltr .crown-row .crown-controls { order: 4; }

    /* Spread grid */
    .spread-wrap { overflow-x: auto; padding: 1rem 0; -webkit-overflow-scrolling: touch; flex: 1; min-width: 0; display: flex; justify-content: center; }
    /* Controls header: the age-stepper/mode-label slot stays dead-centre
       (its own natural home, .age-selector unchanged), while the settings
       gear breaks out to the right edge of the whole panel — .q-quad-header
       spans the full container width so "right: 0" lands at the panel edge,
       not just beside the centred controls. */
    .age-controls-wrap { display: flex; flex-direction: column; align-items: center; gap: .55rem; }
    .q-quad-header {
      position: relative; width: 100%; min-height: 32px;
      display: flex; align-items: center; justify-content: center;
    }
    .q-disp-icon {
      width: 30px; height: 30px; flex: 0 0 30px; border-radius: 50%;
      border: 1px solid rgba(255,255,255,.22); background: transparent;
      color: rgba(255,255,255,.6); font-size: .95rem; line-height: 1;
      display: flex; align-items: center; justify-content: center;
      transition: background var(--ease), color var(--ease), border-color var(--ease);
    }
    .q-disp-icon:hover { color: rgba(255,255,255,.9); border-color: rgba(255,255,255,.4); }
    .q-disp-icon.active { border-color: var(--gold); background: var(--gold); color: #fff; }
    .q-age-slot { height: 32px; display: flex; align-items: center; justify-content: center; }
    /* Mode labels: Spirit · Age · Life (Age = annual view) */
    .q-mode-labels {
      display: flex; width: fit-content; margin: 0;
      border: 1px solid rgba(255,255,255,.18); border-radius: 999px; overflow: hidden;
    }
    .q-mode-label {
      background: transparent; border: none; cursor: pointer;
      box-sizing: border-box; min-width: 4.5rem; text-align: center;
      font-family: var(--sans); font-size: .64rem; font-weight: 600;
      letter-spacing: .08em; text-transform: uppercase;
      color: rgba(255,255,255,.6); padding: .38rem .55rem;
      transition: background var(--ease), color var(--ease);
    }
    .q-mode-label + .q-mode-label { border-left: 1px solid rgba(255,255,255,.14); }
    .q-mode-label:hover { color: rgba(255,255,255,.9); }
    .q-mode-label.active { background: var(--gold); color: #fff; }

    /* Quadrations settings — gear icon pinned to the right edge of
       .q-quad-header (see above), independent of the centred age-stepper/
       mode-label, and expands every occasional-use preference inline: the
       Spirit/Age/Life view (moved in from the old always-visible pill row),
       Alternate court cards, Show displacements, and the card-size slider.
       Only the age stepper stays outside the panel — that's the one control
       you touch constantly.
       .q-mode-current is a plain (non-interactive) label that fills the
       same slot as the age stepper when it's hidden (Spirit/Life mode) —
       see the display rules above — so the active view is still visible at
       a glance without opening the menu; qSetMode keeps its text in sync. */
    .q-mode-current {
      font-family: var(--sans); font-size: .62rem; font-weight: 600;
      letter-spacing: .14em; text-transform: uppercase; color: var(--gold-ink);
    }
    .q-disp-icon[aria-expanded="true"] { border-color: var(--gold); color: var(--gold); }
    /* Settings menu — a floating popup (it was briefly an inline accordion).
       The first popover version rendered UNDER / clipped by the card grid's
       overflow on narrow screens; to dodge that, js/cardsoflife.js relocates
       #qSettingsPanel to <body> and sets its top/left from the gear's rect on
       open, so it escapes every overflow ancestor and floats on top. */
    .q-settings-panel {
      position: absolute; z-index: 200;
      width: min(300px, 92vw);
      opacity: 0; visibility: hidden;
      transform: translateY(-6px) scale(.98); transform-origin: top right;
      transition: opacity .18s ease, transform .18s ease, visibility 0s linear .18s;
      pointer-events: none;
    }
    .q-settings-panel.open {
      opacity: 1; visibility: visible; transform: none; pointer-events: auto;
      transition: opacity .18s ease, transform .18s ease;
    }
    .q-settings-inner {
      background: var(--surface); border: 1px solid var(--border-strong);
      border-radius: 10px; padding: .35rem .85rem; box-shadow: var(--sh-lg);
    }
    @media (prefers-reduced-motion: reduce) {
      .q-settings-panel { transition: none; }
    }
    .q-setting-row {
      display: flex; align-items: center; justify-content: space-between;
      gap: .75rem; padding: .6rem 0;
    }
    .q-setting-row + .q-setting-row { border-top: 1px solid var(--border); }
    .q-setting-label { font-size: .8rem; color: var(--text-secondary); }
    .q-setting-row--size,
    .q-setting-row--mode { flex-direction: column; align-items: stretch; gap: .4rem; }
    .q-setting-row--size .q-setting-label,
    .q-setting-row--mode .q-setting-label {
      font-size: .64rem; font-weight: 600; letter-spacing: .06em;
      text-transform: uppercase; color: var(--gold-ink);
    }
    /* Inside the popover the pill group can stretch full-width, unlike its
       old fit-content sizing in the standalone row. */
    .q-setting-row--mode .q-mode-labels { width: 100%; }
    .q-setting-row--mode .q-mode-label { flex: 1; }
    /* Toggle switch (Alternate courts / Show displacements) */
    .q-switch {
      position: relative; flex-shrink: 0; width: 34px; height: 20px;
      padding: 0; border-radius: 999px;
      background: rgba(255,255,255,.15); border: 1px solid rgba(255,255,255,.22);
      cursor: pointer; transition: background var(--ease), border-color var(--ease);
    }
    .q-switch-knob {
      position: absolute; top: 1px; left: 1px; width: 16px; height: 16px;
      border-radius: 50%; background: #fff; transition: transform var(--ease);
    }
    .q-switch.on { background: var(--gold); border-color: var(--gold); }
    .q-switch.on .q-switch-knob { transform: translateX(14px); }

    /* Card-size slider — a manual scale for the Quadrations grid. Small/large
       "A" glyphs bracket the track (a familiar zoom-slider pattern). Persisted
       + read by qSetCardSize/_initQuadSettings in cardsoflife.js; resizing
       #annualGrid also cascades to the finder's life-script/connection
       mini-grids for free via the existing --quadration-actual-w tracking. */
    .q-size-row { display: flex; align-items: center; gap: .4rem; }
    .q-size-ico { font-family: var(--serif); color: rgba(255,255,255,.4); line-height: 1; user-select: none; }
    .q-size-ico--sm { font-size: .6rem; }
    .q-size-ico--lg { font-size: 1.05rem; }
    .q-size-val {
      font-family: var(--sans); font-size: .68rem; font-weight: 600;
      color: rgba(255,255,255,.45); min-width: 2.6em; text-align: left;
    }
    .q-size-slider {
      -webkit-appearance: none; appearance: none;
      flex: 1; min-width: 0; height: 32px; background: transparent; cursor: pointer;
    }
    .q-size-slider::-webkit-slider-runnable-track {
      height: 3px; border-radius: 999px; background: rgba(255,255,255,.18);
    }
    .q-size-slider::-moz-range-track {
      height: 3px; border-radius: 999px; background: rgba(255,255,255,.18);
    }
    .q-size-slider::-webkit-slider-thumb {
      -webkit-appearance: none; appearance: none;
      width: 15px; height: 15px; margin-top: -6px; border-radius: 50%;
      background: var(--gold); border: 2px solid var(--accent);
      transition: transform var(--ease);
    }
    .q-size-slider::-moz-range-thumb {
      width: 15px; height: 15px; border-radius: 50%;
      background: var(--gold); border: 2px solid var(--accent);
      transition: transform var(--ease);
    }
    .q-size-slider::-webkit-slider-thumb:hover,
    .q-size-slider::-moz-range-thumb:hover { transform: scale(1.15); }

    /* The Quadrations grid's manual size scale (set as an inline custom
       property on #annualGrid by qSetCardSize; defaults to 1 = unscaled).
       ID specificity intentionally overrides the shared .spread-grid rule in
       site.css, scoped to this one grid only. */
    #annualGrid { max-width: calc(var(--quadration-max-w) * var(--quad-scale, 1)); }
    @media (max-width: 700px) {
      .spread-layout { flex-direction: column; align-items: center; }
      .spread-stack { display: flex; flex-direction: column; align-items: center; width: 100%; }
      .spread-layout .spread-wrap { width: 100%; justify-content: center; }
      .spreads-top { gap: .5rem; }
    }
    /* Card faces + spread grid: shared base lives in css/site.css.
       Page-specific bits only below. */

    /* Pip cards — interactivity on top of the shared base */
    .spread-card {
      cursor: pointer;
      transition: box-shadow var(--ease), transform var(--ease), border-color var(--ease);
      min-width: 0; min-height: 0;
    }
    .spread-card:hover { box-shadow: var(--sh-md); transform: translateY(-2px); border-color: var(--border-strong); }

    /* Card chosen in the Birth Card Finder — a thick gold ring with a soft, continually
       breathing glow (static value below is the reduced-motion fallback) */
    .spread-card.finder-pick {
      border-color: var(--gold);
      box-shadow: 0 0 0 3px var(--gold), 0 0 0 6px rgba(205,168,95,.22), 0 0 16px 3px rgba(205,168,95,.55), 0 8px 22px rgba(181,134,74,.42);
      z-index: 4;
      animation: finderGlow 2.6s ease-in-out infinite;
    }
    /* A thin light glint sweeps across the card every few seconds, clipped to the card */
    .spread-card.finder-pick::after {
      content: ''; position: absolute; inset: 0; border-radius: inherit;
      background: linear-gradient(65deg, transparent 34%, rgba(255,252,242,.4) 47%, rgba(255,255,255,.85) 50%, rgba(255,252,242,.4) 53%, transparent 66%);
      transform: translateX(130%);
      animation: finderShine 5.5s linear infinite;
      pointer-events: none; z-index: 3;
    }
    @keyframes finderGlow {
      0%, 100% { box-shadow: 0 0 0 3px var(--gold), 0 0 0 5px rgba(205,168,95,.15), 0 0 13px 2px rgba(205,168,95,.40), 0 8px 22px rgba(181,134,74,.40); }
      50%      { box-shadow: 0 0 0 3px var(--gold), 0 0 0 8px rgba(205,168,95,.30), 0 0 22px 5px rgba(205,168,95,.72), 0 9px 24px rgba(181,134,74,.46); }
    }
    @keyframes finderShine {
      0%   { transform: translateX(130%); }
      16%  { transform: translateX(-130%); }
      100% { transform: translateX(-130%); }
    }
    @media (prefers-reduced-motion: reduce) {
      .spread-card.finder-pick { animation: none; }
      .spread-card.finder-pick::after { animation: none; opacity: 0; }
    }

    /* The picked card's seven Life Script cards (its planetary ruling cards) are
       highlighted ONLY at age 0 — the Life spread — where they sit contiguously
       just after the birth card. There they RIFFLE: each spins a full turn on its
       vertical axis (like a card flipped face-up), STAGGERED in script order
       (--ls-i, Mercury→Neptune) so a wave of flips rolls out from the birth card,
       then rests before repeating. A drop shadow peaks mid-flip to sell the depth.
       Other ages keep .ls-pick on the cards but give it no visual (the grid only
       carries .ls-lifespread at age 0, set in buildAnnualGrid). The flip uses
       transform; during an age change the brief FLIP re-seat of these seven won't
       tween — unnoticeable. */
    /* The one-shot flip is the ENTRANCE flourish; a quieter persistent state
       then takes over so the seven stay distinguished no matter when the user
       arrives (setScript often fires from the Finder while they're scrolled
       away, missing the 2.5s flip). The persistent pair mirrors .finder-pick's
       glow+shine but gentler/softer-gold so it stays subordinate to the birth
       card's own gold ring. Gated :not(.finder-pick) so a card that is BOTH
       the birth pick and one of its own script cards keeps only the prominent
       gold treatment (no competing double glow / double ::after shine). */
    .ls-lifespread .spread-card.ls-pick:not(.finder-pick) {
      z-index: 3;
      /* As the card turns past 90deg its (mirrored) front hides — the .ls-back
         overlay turns to meet the viewer instead. */
      backface-visibility: hidden;
      /* Flip once (per-card --ls-i stagger), then the idle glow starts exactly
         as that card's flip ends (2.5s + its own delay) and breathes forever.
         `both` fill on lsTurn holds its final frame through the handoff so
         there's no flash between flip-end and glow-start. */
      animation:
        lsTurn 2.5s cubic-bezier(.45,.05,.3,1) calc(var(--ls-i, 0) * .2s) both,
        lsIdleGlow 2.9s ease-in-out calc(2.5s + var(--ls-i, 0) * .2s) infinite;
    }
    /* Gentle diagonal shine, softer than .finder-pick's, that begins after the
       flip and ripples outward from the birth card in script order (staggered
       by --ls-i) so the seven don't all flash in unison. */
    .ls-lifespread .spread-card.ls-pick:not(.finder-pick)::after {
      content: ''; position: absolute; inset: 0; border-radius: inherit;
      background: linear-gradient(65deg, transparent 36%, rgba(240,224,190,.24) 47%, rgba(255,246,224,.52) 50%, rgba(240,224,190,.24) 53%, transparent 64%);
      transform: translateX(130%);
      animation: finderShine 6.5s linear infinite;
      animation-delay: calc(2.6s + var(--ls-i, 0) * .5s);
      pointer-events: none; z-index: 3;
    }
    /* Soft, softer-gold breathing glow — no solid ring (that stays the birth
       card's signature), just a gentle halo so the seven read as a set. */
    @keyframes lsIdleGlow {
      0%, 100% { box-shadow: 0 0 0 2px rgba(205,168,95,.24), 0 0 10px 1px rgba(205,168,95,.22), 0 7px 15px rgba(0,0,0,.24); }
      50%      { box-shadow: 0 0 0 3px rgba(205,168,95,.38), 0 0 17px 3px rgba(205,168,95,.40), 0 8px 17px rgba(0,0,0,.26); }
    }
    /* One slow, deliberate turn: face → back (held a beat) → face. The per-card
       --ls-i delay (.2s steps) rolls the turn out from the birth card as a wave.
       Plays once each time the Life spread is cast (no repeat). */
    @keyframes lsTurn {
      0%   { transform: perspective(420px) rotateY(0deg);   box-shadow: 0 2px 4px rgba(0,0,0,.16); }
      16%  { transform: perspective(420px) rotateY(90deg);  box-shadow: 0 10px 16px rgba(0,0,0,.30); }
      32%  { transform: perspective(420px) rotateY(180deg); box-shadow: 0 13px 20px rgba(0,0,0,.34); }
      50%  { transform: perspective(420px) rotateY(180deg); box-shadow: 0 13px 20px rgba(0,0,0,.34); }
      66%  { transform: perspective(420px) rotateY(270deg); box-shadow: 0 10px 16px rgba(0,0,0,.30); }
      82%  { transform: perspective(420px) rotateY(360deg); box-shadow: 0 2px 4px rgba(0,0,0,.16); }
      100% { transform: perspective(420px) rotateY(360deg); box-shadow: 0 2px 4px rgba(0,0,0,.16); }
    }
    /* ── Card back (riffle) ───────────────────────────────────────────
       A seat-level overlay (injected by setScript), not a child of the card —
       the card's overflow:hidden + container-type flatten 3D, so a true back
       face has to live outside it. Violet body + gold rim + the site's gold
       coin emblem (matches the favicon / footer mark). It runs the riffle 180deg
       out of phase with the front (lsRiffleBack), and both carry
       backface-visibility:hidden, so exactly one face ever shows. */
    #annualGrid .sl-seat { position: relative; }
    /* The riffle back now carries the shared .card-back visual (css/site.css);
       .ls-back only owns its overlay position + flip. right/bottom:auto cancel
       .card-back's inset:0 so the aspect-ratio drives the height (the seat is
       taller than the card — it also holds the ghost chips). */
    .ls-back {
      position: absolute; top: 0; left: 0; right: auto; bottom: auto;
      width: 100%; aspect-ratio: 5 / 7;
      z-index: 3; border-radius: var(--card-radius);
      transform: perspective(420px) rotateY(180deg);   /* back-facing at rest → hidden */
      backface-visibility: hidden; pointer-events: none;
    }
    .ls-lifespread .ls-back {
      animation: lsTurnBack 2.5s cubic-bezier(.45,.05,.3,1);
      animation-delay: calc(var(--ls-i, 0) * .2s);
    }
    @keyframes lsTurnBack {
      0%   { transform: perspective(420px) rotateY(180deg); box-shadow: 0 2px 4px rgba(0,0,0,.16); }
      16%  { transform: perspective(420px) rotateY(270deg); box-shadow: 0 10px 16px rgba(0,0,0,.30); }
      32%  { transform: perspective(420px) rotateY(360deg); box-shadow: 0 13px 20px rgba(0,0,0,.34); }
      50%  { transform: perspective(420px) rotateY(360deg); box-shadow: 0 13px 20px rgba(0,0,0,.34); }
      66%  { transform: perspective(420px) rotateY(450deg); box-shadow: 0 10px 16px rgba(0,0,0,.30); }
      82%  { transform: perspective(420px) rotateY(540deg); box-shadow: 0 2px 4px rgba(0,0,0,.16); }
      100% { transform: perspective(420px) rotateY(540deg); box-shadow: 0 2px 4px rgba(0,0,0,.16); }
    }
    @media (prefers-reduced-motion: reduce) {
      /* No flip and no breathing — but keep the seven clearly distinguished
         with a fixed soft gold-tinted halo (a static echo of lsIdleGlow), and
         never surface the back (it stays back-facing → hidden). Gated the same
         way so a both-card keeps only the birth card's gold ring. */
      .ls-lifespread .spread-card.ls-pick:not(.finder-pick) {
        animation: none;
        box-shadow: 0 0 0 2px rgba(205,168,95,.30), 0 0 9px 1px rgba(205,168,95,.22), 0 7px 13px rgba(0,0,0,.26);
      }
      .ls-lifespread .spread-card.ls-pick:not(.finder-pick)::after { animation: none; opacity: 0; }
      .ls-lifespread .ls-back { animation: none; }
    }

    /* Spread responsive */
    @media (max-width: 700px) {
      .spread-wrap { justify-content: center; width: 100%; overflow-x: visible; }
      .spread-layout .spread-wrap { width: 100%; }
      .spread-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: .35rem; min-width: 0; width: 100%; }
      .spread-card { min-width: 0; min-height: 0; aspect-ratio: 5/7; }
    }
    @media (max-width: 500px) {
      .spread-wrap { justify-content: center; width: 100%; overflow-x: visible; }
      .spread-layout .spread-wrap { width: 100%; }
      .spread-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: .22rem; min-width: 0; width: 100%; }
      .spread-card { min-width: 0; min-height: 0; aspect-ratio: 5/7; }
      .crown-row { gap: .35rem; }
    }

    /* ── Spirit & Life: one grid, two states ─────────────────── */
    .sl-seat { min-width: 0; }
    .sl-seat .spread-card { width: 100%; }
    /* Cards that keep their seat in both spreads — rings only shown when the
       grid opts in via .sl-rings (Spirit/Life views; not the Annual spread) */
    .sl-rings .spread-card.sl-fixed { box-shadow: 0 0 0 1.5px rgba(181,134,74,.7); }
    /* .sl-semi (twin cards) previously got a dashed outline in Spirit/Life view;
       removed — the solid .sl-fixed ring is enough visual distinction. */
    .cal-modal .planet-col-label { color: rgba(255,255,255,.38); }

    /* ── Planet readings — the detail panel for the top planets row (#planetRow);
       the Quadration grid's planet glyphs are now plain static labels. ── */
    /* The planet detail shares the suit/number detail's centred layout and type
       registers (.el-card / .el-head / .el-fig / .el-name / .el-kws / .el-text /
       .el-keynote) so all three top-row readings look identical. It keeps its
       richer content — the epithet subtitle and the Light / Shadow lines — laid
       out centred within the same language. */
    .pl-pop { max-width: 46rem; margin-inline: auto; }
    /* the detail "spawns" downward: grid-rows 0fr→1fr grows its height */
    .pl-detailwrap { display: grid; grid-template-rows: 0fr; margin-top: 0;
                     transition: grid-template-rows .4s var(--ease), margin-top .4s var(--ease); }
    .pl-pop.open .pl-detailwrap { grid-template-rows: 1fr; margin-top: .9rem; }
    .pl-detailmin { overflow: hidden; }
    .pl-detail { display: flex; align-items: center; justify-content: center; gap: .4rem;
                 max-width: 46rem; margin: 0 auto; padding: 0 .3rem;
                 opacity: 0; transform: translateY(-8px);
                 transition: opacity .35s var(--ease), transform .35s var(--ease); }
    .pl-pop.open .pl-detail { opacity: 1; transform: none; }
    @media (prefers-reduced-motion: reduce) { .pl-detailwrap, .pl-detail { transition: none; } }
    /* Centred, box-less card — mirrors .el-card. */
    .pl-card { flex: 1; min-width: 0; text-align: center; background: transparent; border: none; padding: 0; }
    /* Glyph beside name on one baseline, centred — mirrors .el-head / .el-fig / .el-name. */
    .pl-head { display: flex; align-items: baseline; justify-content: center; gap: .55rem; margin-bottom: .5rem; }
    .pl-fig { font-family: var(--serif); font-size: 1.8rem; color: var(--gold); line-height: 1; }
    .pl-name { font-family: var(--serif); font-size: var(--text-lg); line-height: 1.1; color: var(--text); }
    /* Epithet — the one field the suits/numbers lack; kept small + centred. */
    .pl-sub { font-family: var(--sans); font-size: .66rem; letter-spacing: .08em; text-transform: uppercase;
              color: var(--gold-ink); margin-bottom: .7rem; }
    /* Keyword pills — same styling as .el-kws / .kw-tag. */
    .pl-kws { display: flex; flex-wrap: wrap; justify-content: center; gap: .35rem; margin-bottom: .7rem; }
    /* Synopsis — italic gold, same register as .el-keynote; the closing note,
       sitting last (beneath the Light / Shadow lines). */
    .pl-syn { font-family: var(--serif); font-style: italic; font-size: 1.05rem; line-height: 1.6;
              color: var(--gold-ink); max-width: 40rem; margin: 1.1rem auto 0; }
    /* Light / Shadow — centred, each label stacked above its values; sits just
       below the main prose. */
    .pl-attrs { display: block; max-width: 40rem; margin: 1.1rem auto 0;
                font-family: var(--sans); font-size: .82rem; }
    .pl-attrs dt { color: var(--gold-ink); text-transform: uppercase; letter-spacing: .1em; font-size: .6rem;
                   font-weight: 600; margin-top: .7rem; }
    .pl-attrs dt:first-child { margin-top: 0; }
    .pl-attrs dd { margin: .15rem 0 0; color: var(--text-secondary); }
    /* Prose — same as .el-text (sans, muted, .96rem/1.7). */
    .pl-para { color: var(--ink-muted); font-size: .96rem; line-height: 1.7; }
    .pl-para p { margin: 0 0 .7em; }
    .pl-para p:last-child { margin-bottom: 0; }
    @media (max-width: 560px) {
      .pl-head { gap: .5rem; } .pl-name { font-size: 1.15rem; }
      .pl-fig { font-size: 1.5rem; }
    }

    /* ── Compare Card Popup ───────────────────────────────────── */
    .ccard-overlay {
      display: none; position: fixed; inset: 0; z-index: 300;
      background: rgba(22,16,41,.52);
      backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
      align-items: center; justify-content: center; padding: 1rem;
    }
    /* popup shell · suit bars · close button → shared in css/site.css */
    /* Prev / Next arrows */
    .ccard-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(--text-muted); font-size: 1rem;
      /* Rest at .45 (matching the shared .hx-nav) so the arrows are visible on
         touch, where there's no hover to reveal them. Hover/focus lifts to 1. */
      opacity: .45; transition: opacity var(--ease), color var(--ease);
      cursor: pointer; border-radius: var(--r-sm);
    }
    .ccard-nav-prev { left: .35rem; }
    .ccard-nav-next { right: .35rem; }
    .ccard-popup:hover .ccard-nav { opacity: 1; }
    .ccard-nav:hover { color: var(--text-primary); }
    /* Keyboard focus must be visible — reveal the arrow and ring it (a11y 2.4.7) */
    .ccard-nav:focus-visible { opacity: 1; color: var(--text-primary); outline: 2px solid var(--gold); outline-offset: 2px; }
    #ccFace {
      width: 78px; height: 109px; flex-shrink: 0; cursor: default;
    }
    #ccFace:hover { transform: none; box-shadow: none; }
    /* Dates render as a fixed 3-column grid, one date per cell. With at most 6
       dates on any card that is at most TWO rows, and the row count depends only
       on how many dates the card has — never on the rendered text width. That is
       what makes it identical across a browser tab and the installed desktop
       webapp (which enforce different small-text sizes): the same card always
       lays out to the same number of rows everywhere. A constant 2-row min-height
       (expressed in em, so it scales WITH the text size) keeps every card's
       header the same height, so the Life Script below never shifts. */
    .ccard-dates {
      font-size: .6rem; letter-spacing: .06em; color: var(--muted);
      margin-top: .35rem;
      display: grid;
      grid-template-columns: repeat(4, auto);
      justify-content: start;
      align-content: start;
      column-gap: .55rem; row-gap: .2rem;
      line-height: 1.4;
      /* The grid ALWAYS holds 12 cells (js pads the empty ones), so it is always
         exactly 3 rows of 4. The block therefore sizes ITSELF to three rows of
         the real rendered text — a constant height for every card (so the Life
         Script below never moves) that can never clip a row at any font size.
         An earlier fixed px/rem height clipped the bottom row in the installed
         desktop webapp, which renders this small text larger than a browser tab;
         self-sizing to the actual text removes that failure mode entirely. */
    }
    .cc-date { white-space: nowrap; }
    /* Padding cells hold a non-breaking space so their grid row stays a full line
       tall — a card with only a few dates still reserves all three rows. */
    .cc-date-pad::before { content: "\00a0"; }
    /* Planet + displacement lines inside the header info block */
    .ccard-planets { font-size: .62rem; letter-spacing: .08em; color: var(--muted); margin-top: .25rem; }
    .ccard-displace { font-size: .62rem; letter-spacing: .06em; color: var(--muted); margin-top: .15rem; }
    /* Period reading: hide planet + displacement */
    /* (Previously hid .ccard-planets + .ccard-displace in period popups —
       restored so Your-Cards popups carry the same card-stat rows as the
       compare-card popups elsewhere.) */
    /* Footer with CTA */
    .ccard-footer {
      padding: .9rem 1.75rem;
      border-top: 1px solid var(--border);
      background: var(--surface);
      display: flex; gap: .6rem;
    }
    /* Primary action ("Load in Finder") — a solid gold call-to-action.
       Equal-width with the ghost "Load in Quadrations" beside it — the two
       labels are now a matching "Load in ___" pair, so neither should read
       as visually bigger than the other. */
    .ccard-load-btn {
      flex: 1; display: block;
      background: var(--gold); color: var(--bg);
      padding: .72rem; border-radius: var(--r-sm);
      font-size: .68rem; font-weight: 700; letter-spacing: .08em;
      text-transform: uppercase; cursor: pointer; border: none;
      font-family: var(--sans); transition: filter var(--ease), background var(--ease), color var(--ease);
    }
    .ccard-load-btn:hover { filter: brightness(1.08); }
    /* Secondary action ("Load in Quadrations") — a quiet ghost button. */
    .ccard-load-btn.secondary {
      flex: 1;
      background: transparent; color: var(--gold-ink);
      border: 1px solid var(--border); font-weight: 600;
    }
    .ccard-load-btn.secondary:hover { background: var(--surface-alt); color: var(--text-primary); }

    /* ── Year Calendar ──────────────────────────────────────────── */
    .cal-modal {
      background: var(--accent); border-radius: var(--r-lg);
      box-shadow: var(--sh-lg); width: 100%; max-width: 600px;
      padding: 1.25rem 1.25rem 1.5rem; position: relative;
      animation: pop .22s cubic-bezier(.22,1,.36,1);
      margin: auto;
    }
    .cal-scroll-wrap { overflow-x: visible; padding-bottom: .25rem; }
    .cal-grid-main {
      display: grid;
      grid-template-columns: 1rem repeat(12, 1fr);
      gap: 2px;
      width: 100%;
    }
    .cal-col-header {
      text-align: center;
      font-family: var(--sans);
      font-size: .5rem; font-weight: 700; letter-spacing: .02em;
      text-transform: uppercase;
      color: var(--text-secondary); padding-bottom: .3rem; line-height: 1;
    }
    .cal-month-label {
      display: flex; align-items: center; justify-content: flex-end;
      font-family: var(--sans);
      font-size: .52rem; font-weight: 600;
      color: var(--text-muted); line-height: 1;
      padding-right: .16rem; font-variant-numeric: tabular-nums;
    }
    .cal-day {
      cursor: pointer; border-radius: 3px;
      transition: transform var(--ease);
    }
    .cal-day:hover { transform: scale(1.12); z-index: 2; }
    .cal-cell-empty { min-height: .9rem; }
    /* Subtle wash behind alternating month columns to guide the eye */
    .cal-col-alt { background: rgba(255,255,255,.035); border-radius: 2px; }
    .cal-chip {
      width: 100%; border-radius: 2px;
      display: flex; align-items: center; justify-content: center;
      padding: .16rem 0; line-height: 1;
    }
    .cal-token {
      font-family: var(--sans); font-size: .54rem; font-weight: 700;
      letter-spacing: 0; line-height: 1; white-space: nowrap;
    }
    @media (max-width: 500px) {
      .cal-modal { padding: 1rem .7rem 1.25rem; max-height: 90vh; }
      .cal-grid-main { grid-template-columns: .85rem repeat(12, 1fr); gap: 1.5px; }
      .cal-col-header { font-size: .46rem; }
      .cal-token { font-size: .48rem; }
      .cal-month-label { font-size: .46rem; padding-right: .2rem; }
      .cal-cell-empty { min-height: .75rem; }
    }

    /* ── Dark-background overrides for cal-modal ─────────────────── */
    .cal-modal .cal-col-header { color: rgba(255,255,255,.5); }
    .cal-modal .cal-month-label { color: rgba(255,255,255,.42); }
    /* Calendar chips — light face on dark background, suit-colored glyphs */
    .cal-modal .cal-chip { background: rgba(255,255,255,.94); }
    .cal-modal .cal-chip.hearts,   .cal-modal .cal-chip.diamonds { color: var(--red); }
    .cal-modal .cal-chip.clubs  { color: var(--green); }
    .cal-modal .cal-chip.spades { color: var(--navy); }
    .cal-modal .cal-chip.joker  { color: var(--card-dark); }
    .cal-token-joker { font-size: .42rem; letter-spacing: .01em; }
    @media (max-width: 560px) { .cal-token-joker { font-size: .38rem; } }
    /* Today's date — gold ring */
    .cal-modal .cal-today .cal-chip {
      outline: 2px solid var(--gold); outline-offset: 1px;
      box-shadow: 0 0 0 1px var(--gold), 0 0 8px rgba(181,134,74,.5);
    }

    /* ── Synchronicities tool panel (placeholder only — content pending,
       see dev/Book of Life/synchronicities-list.md) ────────────── */
    .sync-wip {
      text-align: center; font-family: var(--serif); font-style: italic;
      font-size: .95rem; color: rgba(255,255,255,.55);
      padding: 2rem 1rem; margin: 0;
    }

    /* ── Astrology tool panel ────────────────────────────────── */
    /* Plain UI note (not oracle voice) introducing the panel before the form —
       same small/muted treatment as .bday-note, just centred to sit above a
       centred form instead of left-aligned in a list. Exists mainly to
       surface the solar-time-at-birth result, which otherwise only appears
       after drawing and is easy to miss. */
    .astro-intro-note {
      text-align: center; font-size: .78rem; color: var(--text-muted);
      line-height: 1.5; margin: 0 0 1rem;
    }
    /* Two stacked rows, both centred: row 1 = Day / Month / Year / Time,
       row 2 = Birthplace + Draw. Form is a centred column so the rows
       align vertically about the same axis. */
    .astro-form {
      display: flex; flex-direction: column;
      gap: .6rem; align-items: center;
    }
    .astro-form-row {
      display: flex; flex-wrap: wrap;
      gap: .5rem; align-items: flex-end;
      justify-content: center;
    }
    .astro-field { display: flex; flex-direction: column; gap: .2rem; }
    .astro-field-lbl {
      font-size: .6rem; font-weight: 600; letter-spacing: .12em;
      text-transform: uppercase; color: var(--muted);
    }
    .astro-field select, .astro-field input {
      background: var(--surface-alt); border: 1px solid var(--border);
      border-radius: var(--r-sm); color: var(--text); padding: .35rem .45rem;
      font-size: .8rem; font-family: inherit;
    }
    .astro-field input[type="number"] { width: 5.5em; }
    .astro-field input[type="time"]   { width: 6.5em; }
    /* Birthplace now sits on its own row alongside the Draw button — give
       it a fixed-ish footprint so the pair feels balanced (not a stretched
       input next to a small button). */
    .astro-field-place { flex: 0 1 260px; min-width: 140px; max-width: 300px; }
    .astro-field-place input { width: 100%; }
    .astro-draw-btn { align-self: flex-end; white-space: nowrap; }
    /* Row 2 (Birthplace + Draw) stays a tight inline pair — no wrap, so
       the Draw button always sits to the right of the Birthplace input.
       The pair as a whole is centred by the parent .astro-form's
       align-items: center, and Draw bottom-aligns to the input via
       .astro-form-row's align-items: flex-end. */
    .astro-form-row + .astro-form-row {
      flex-wrap: nowrap;
    }
    /* Solar time, centered, sits above the birth card. We deliberately drop the
       "sun ahead/behind" verdict — just the time itself is enough information. */
    .astro-solar-strip {
      font-size: .82rem; color: rgba(255,255,255,.7); line-height: 1.6;
      margin: .8rem 0 .55rem; padding: .6rem 0 0;
      border-top: 1px solid var(--border);
      text-align: center;
    }
    .astro-solar-strip b { color: var(--gold-ink); }
    /* Birth card — full card, centered below the solar time. Sized to feel like
       a primary focal point of the panel without dominating the table below. */
    .astro-birth-card {
      display: flex; flex-direction: column; align-items: center;
      gap: .4rem; margin: 0 auto .9rem;
    }
    .astro-birth-card .spread-card {
      width: 110px; aspect-ratio: 5/7; cursor: pointer;
    }
    .astro-birth-card .astro-bc-name {
      font-size: .72rem; letter-spacing: .1em; text-transform: uppercase;
      color: var(--text-muted, var(--muted));
    }
    /* Table controls row — zodiac selector on the left, "More" toggle on the
       right. Both act as settings for the table beneath rather than crowding
       the solar strip up top. */
    .astro-table-controls {
      display: flex; justify-content: space-between; align-items: center;
      gap: .5rem; margin: .2rem 0 .35rem; flex-wrap: wrap;
    }
    .astro-zodiac-seg {
      display: inline-flex; gap: 0;
      border: 1px solid var(--border); border-radius: var(--r-sm); overflow: hidden;
    }
    .astro-zod-btn {
      background: none; border: 0; padding: .2rem .6rem;
      font-size: .68rem; font-weight: 600; letter-spacing: .06em;
      color: var(--muted); cursor: pointer; font-family: var(--sans);
      border-right: 1px solid var(--border);
      transition: background var(--ease), color var(--ease);
    }
    .astro-zod-btn:last-child { border-right: 0; }
    .astro-zod-btn:hover { color: var(--gold-ink); background: rgba(255,255,255,.03); }
    .astro-zod-btn[aria-pressed="true"] {
      background: var(--surface-alt); color: var(--gold-ink);
    }
    /* Standard minimise/maximise chevron toggle (matches the result/section ones). */
    .astro-more-btn {
      display: inline-flex; align-items: center; justify-content: center;
      width: 30px; height: 30px; padding: 0; flex: none;
      background: none; border: 1px solid rgba(255,255,255,.22); border-radius: 50%;
      color: var(--gold); cursor: pointer; transition: background var(--ease), border-color var(--ease);
    }
    .astro-more-btn svg { width: 13px; height: 13px; display: block; transition: transform .25s ease; }
    .astro-more-btn:hover, .astro-more-btn:focus-visible {
      background: rgba(255,255,255,.07); border-color: rgba(255,255,255,.45);
    }
    .astro-more-btn[aria-expanded="true"] svg { transform: rotate(180deg); }
    .astro-table-wrap { margin: .25rem 0 .8rem; }
    .astro-pl-table {
      width: 100%; border-collapse: collapse; font-size: .78rem;
      --yang: rgba(255,255,255,.9);
    }
    .astro-pl-table thead th {
      font-size: .6rem; font-weight: 600; letter-spacing: .1em;
      text-transform: uppercase; color: var(--muted);
      padding: .3rem .4rem; border-bottom: 1px solid var(--border);
      text-align: left;
    }
    .astro-pl-table tbody tr { border-bottom: 1px solid rgba(255,255,255,.06); }
    .astro-pl-table tbody td { padding: .35rem .4rem; color: rgba(255,255,255,.75); vertical-align: middle; }
    .astro-pl-table .al-planet { white-space: nowrap; }
    .astro-pl-table .al-glyph { margin-right: .3rem; font-size: .9rem; }
    .astro-pl-table .al-sign  { white-space: nowrap; }
    /* Full sign name on desktop, 3-letter on narrow viewports — there's plenty
       of room for the full word on the panel's normal width, but the column
       gets cramped on phones. Two spans, swap visibility via media query. */
    .astro-pl-table .al-sign-full  { display: inline; }
    .astro-pl-table .al-sign-short { display: none; }
    .astro-pl-table .al-retro { color: #dc8d85; font-size: .7rem; margin-left: .15rem; }
    .astro-pl-table .al-card-col { text-align: center; }
    /* House column normalized: just centered + sized, otherwise inherits the
       same sans / muted-white / small-uppercase head treatment as the rest. */
    .astro-pl-table .al-house { text-align: center; width: 2.2rem; }
    .al-nocard { color: rgba(255,255,255,.28); }
    .astro-pl-table .al-gate  {
      cursor: pointer; white-space: nowrap;
    }
    .astro-pl-table .al-gate-body  { --yang: #dc8d85; color: #dc8d85; }
    .astro-pl-table .al-gate-mind  { --yang: rgba(255,255,255,.85); color: rgba(255,255,255,.85); }
    .astro-pl-table .al-gate:hover { color: var(--gold-ink); }
    .astro-pl-table .al-gate svg { vertical-align: middle; margin-right: .25rem; }
    .astro-pl-table .al-cardchip {
      display: inline-block; padding: .1rem .35rem; border-radius: var(--r-sm);
      font-size: .72rem; font-weight: 600; cursor: pointer;
      background: var(--card-paper); border: 1px solid var(--border);
      transition: all var(--ease);
    }
    .astro-pl-table .al-cardchip.hearts, .astro-pl-table .al-cardchip.diamonds { color: var(--red); }
    .astro-pl-table .al-cardchip.clubs, .astro-pl-table .al-cardchip.spades { color: var(--card-dark); }
    .astro-pl-table .al-cardchip:hover { border-color: var(--gold); color: var(--gold-ink); }
    /* Hex popup (dynamically injected by astro tool) */
    #astroHxOverlay.hx-overlay {
      display: none; position: fixed; inset: 0; z-index: 300;
      background: rgba(22,16,41,.52);
      backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
      align-items: center; justify-content: center; padding: 1rem;
    }
    #astroHxOverlay .hx-header {
      display: flex; gap: 1rem; align-items: flex-start; padding: 1.2rem 1.5rem .8rem;
    }
    #astroHxOverlay .hx-body {
      flex: 1; overflow-y: auto; padding: 0 1.5rem 1.2rem;
    }
    #astroHxOverlay .hx-line-head {
      font-size: .7rem; font-weight: 600; letter-spacing: .12em;
      text-transform: uppercase; color: var(--gold-ink); margin: 1rem 0 .4rem;
      text-align: center;
    }
    @media (max-width: 560px) {
      .astro-form-row { gap: .35rem; }
      .astro-pl-table { font-size: .7rem; }
      .astro-pl-table thead th { font-size: .55rem; padding: .25rem .25rem; }
      .astro-pl-table tbody td { padding: .3rem .25rem; }
      .astro-pl-table .al-sign-full  { display: none; }
      .astro-pl-table .al-sign-short { display: inline; }
      .astro-birth-card .spread-card { width: 92px; }
    }

    /* ── Responsive ──────────────────────────────────────────── */
    @media (max-width: 700px) {
      .container { padding: 0 1rem; }
      .spreads { padding: 0 .85rem; }
      .spreads .container { padding: 0 1rem; }
      .tools-section { padding: 0 .85rem 1.25rem; }
      .tools-section .container { padding: 0 1rem; }
    }
    @media (max-width: 560px) {
      .finder-top { padding: 0 1rem; }
    }

    /* ── Personal Cards (Birth / Yearly / 52-day / Daily) ─────── */
    .personal-cards { padding: 0; }   /* gap from --section-gap */
    .pcards-row {
      display: grid; grid-template-columns: repeat(6, minmax(0, 1fr));
      justify-content: center; align-items: start;
      column-gap: 1.5rem; row-gap: 0;
      max-width: 648px; margin: 0 auto;
    }
    /* .pcard is display:contents so its children participate directly in the
       grid: cards live in row 1, labels in row 2, subs in row 3. When the
       longest label wraps, the whole label row grows together and every sub
       below it stays vertically aligned. */
    .pcard { display: contents; }
    .pcard .spread-card { width: 100%; height: auto; aspect-ratio: 5/7; grid-row: 1; }
    .pcard-label {
      grid-row: 2; margin-top: .6rem;
      font-size: .72rem; letter-spacing: .09em;
      text-transform: uppercase; color: var(--text-primary);
      text-align: center; line-height: 1.25;
    }
    .pcard-sub {
      grid-row: 3; margin-top: .15rem;
      font-size: .68rem; color: var(--text-muted);
      text-align: center; line-height: 1.35;
    }
    .pcards-empty {
      text-align: center; font-size: .88rem; color: var(--text-muted);
      max-width: 440px; margin: 0 auto; line-height: 1.6;
    }
    .spread-card.placeholder {
      background: transparent; border-style: dashed; border-color: var(--border);
      cursor: default;
    }
    .spread-card.placeholder::before { display: none; }
    .spread-card.placeholder:hover { box-shadow: none; transform: none; border-color: var(--border); }
    .pcard-sub.placeholder { color: var(--text-muted); opacity: .5; }
    .birth-actions { display: flex; gap: .5rem; align-items: center; justify-self: end; }
    /* Your Cards header: keep the label centred (aligned with the other section
       labels) via equal side columns, with the Add / Saved buttons pinned right */
    /* Section-label moved into the section-toggle button above; spreads-top
       now just holds the birth-actions controls, right-aligned. */
    .personal-cards .spreads-top {
      display: flex;
      flex-direction: row;        /* override the base .spreads-top column so the
                                     buttons right-align (not centre under the title) */
      justify-content: flex-end;
      align-items: center;
    }
    .personal-cards .birth-actions { grid-column: 3; }
    /* The Add + Saved triggers sit in a right-aligned row; both open the
       body-level #bdayOverlay (js/cardsoflife.js) rather than a dropdown. */
    .birth-actions { display: flex; gap: .5rem; align-items: center; }
    .birth-btn {
      font-size: .68rem; letter-spacing: .1em; text-transform: uppercase;
      font-weight: 600; padding: .45rem .9rem; background: transparent;
      color: var(--text-primary); border: 1px solid var(--border);
      border-radius: 6px; cursor: pointer; line-height: 1;
      transition: background var(--ease), border-color var(--ease);
    }
    .birth-btn:hover { background: var(--surface-alt); border-color: var(--border-strong); }
    .birth-btn.icon-btn {
      width: 34px; height: 34px; padding: 0;
      display: inline-flex; align-items: center; justify-content: center;
      position: relative;
    }
    .birth-btn.icon-btn svg { width: 14px; height: 14px; display: block; }
    .birth-btn-badge {
      position: absolute; top: -5px; right: -5px;
      min-width: 16px; height: 16px; padding: 0 4px;
      border-radius: 999px;
      background: var(--gold); color: #fff;
      font-family: var(--sans); font-size: .58rem; font-weight: 600;
      letter-spacing: .02em; line-height: 1;
      display: none; align-items: center; justify-content: center;
      box-sizing: border-box; pointer-events: none;
    }
    .birth-btn-badge.visible { display: inline-flex; }
    /* Inside the overlay the add form is a static block, revealed by .open
       (toggled from the toolbar's Add button). */
    .birth-add-panel { display: none; }
    .birth-add-panel.open { display: block; }
    .birth-add-row {
      display: flex; gap: .3rem; align-items: center;
      margin-bottom: .45rem;
    }
    .birth-add-row input {
      font-family: var(--mono); font-size: .88rem;
      text-align: center; padding: .38rem .3rem;
      border: 1px solid var(--border); border-radius: 6px;
      background: var(--surface); color: var(--text-primary);
      min-width: 0; box-sizing: content-box; line-height: 1;
    }
    .birth-add-row input:focus { outline: none; border-color: var(--gold); }
    .birth-add-row .dd, .birth-add-row .mm { width: 2.4ch; }
    .birth-add-row .yy { width: 4.5ch; }
    .birth-add-row .sep { color: var(--text-muted); font-family: var(--mono); font-size: .88rem; }
    .birth-add-save {
      width: 30px; height: 30px; padding: 0; flex-shrink: 0;
      display: inline-flex; align-items: center; justify-content: center;
      margin-left: .25rem;
      background: transparent; color: var(--text-primary);
      border: 1px solid var(--border); border-radius: 6px;
      cursor: pointer;
      transition: background var(--ease), border-color var(--ease);
    }
    .birth-add-save:hover { background: var(--surface-alt); border-color: var(--border-strong); }
    .birth-add-save svg { width: 13px; height: 13px; display: block; }
    .birth-add-name {
      width: 100%; box-sizing: border-box;
      font: inherit; font-size: .85rem;
      padding: .4rem .55rem; border: 1px solid var(--border); border-radius: 6px;
      background: var(--surface); color: var(--text-primary);
      margin-bottom: .35rem;
    }
    .birth-add-name:focus { outline: none; border-color: var(--gold); }
    .birth-add-error {
      font-size: .7rem; color: #e98a8a; min-height: 1em; line-height: 1.3;
      margin-top: .15rem;
    }
    .birth-add-error:empty { display: none; }
    .birth-item {
      display: flex; align-items: center; gap: .5rem;
      padding: .65rem .9rem; border-bottom: 1px solid var(--border); cursor: pointer;
    }
    .birth-item:last-child { border-bottom: none; }
    .birth-item:hover { background: var(--surface-alt); }
    .birth-item-body { flex: 1; min-width: 0; }
    .birth-name { font-size: .9rem; color: var(--text-primary); margin-bottom: .15rem;
      overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .birth-date { font-size: .68rem; color: var(--text-muted); font-family: var(--mono); letter-spacing: .04em; }
    .birth-del { flex-shrink: 0; font-size: 1rem; color: var(--text-muted);
      padding: 2px 7px; border-radius: 5px; line-height: 1;
      background: transparent; border: none; cursor: pointer; }
    .birth-del:hover { color: var(--text-primary); background: var(--border); }
    .birth-empty { padding: 1.4rem 1rem; text-align: center; font-size: .82rem; color: var(--text-muted); font-style: italic; }

    /* ── Saved birthdays overlay ─────────────────────────────────────
       Body-level modal (escapes the Your Cards section's overflow:hidden,
       which was clipping the old dropdown so it wouldn't scroll). Holds the
       add form + import/export + the scrollable list. Chrome mirrors the
       olney / compare-card overlays. */
    .bday-overlay, .cal-overlay, .deck-overlay {
      position: fixed; inset: 0; z-index: 1000;
      display: flex; align-items: center; justify-content: center;
      padding: 1.25rem;
      background: rgba(9, 5, 20, .62);
      opacity: 0; visibility: hidden; pointer-events: none;
      transition: opacity .25s var(--ease), visibility .25s var(--ease);
    }
    .bday-overlay.open, .cal-overlay.open, .deck-overlay.open { opacity: 1; visibility: visible; pointer-events: auto; }
    html:has(.bday-overlay.open), html:has(.cal-overlay.open), html:has(.deck-overlay.open) { overflow: hidden; }
    /* The calendar + deck reuse .cal-modal (grid chip colours are scoped to it);
       inside the overlay it stacks head + toggle + grid + note and scrolls. */
    .cal-overlay .cal-modal, .deck-overlay .cal-modal { display: flex; flex-direction: column; max-height: 88vh; overflow: auto; }
    .bday-modal {
      width: 460px; max-width: 100%; max-height: 85vh;
      display: flex; flex-direction: column;
      background: var(--surface); color: var(--text-primary);
      border: 1px solid var(--rule-strong); border-radius: 14px;
      box-shadow: 0 30px 80px rgba(0, 0, 0, .6);
      overflow: hidden;
      transform: translateY(8px) scale(.99);
      transition: transform .25s var(--ease);
    }
    .bday-overlay.open .bday-modal { transform: none; }
    .bday-head {
      display: flex; align-items: center; gap: .5rem;
      padding: .85rem 1rem; border-bottom: 1px solid var(--rule);
    }
    .bday-head-mark { width: 16px; height: 16px; color: var(--gold); flex: 0 0 auto; }
    .bday-title { font-family: var(--serif); font-weight: 500; font-size: 1.1rem;
      color: var(--gold-light); margin: 0; }
    .bday-count {
      min-width: 20px; height: 18px; padding: 0 6px; border-radius: 999px;
      background: var(--gold); color: #fff; font-family: var(--sans);
      font-size: .62rem; font-weight: 600; line-height: 18px; text-align: center;
    }
    .bday-count:empty { display: none; }
    .bday-close {
      margin-left: auto; background: none; border: none; cursor: pointer;
      color: var(--text-muted); font-size: 1.05rem; line-height: 1; padding: .2rem .35rem;
    }
    .bday-close:hover { color: var(--text-primary); }
    .bday-toolbar { display: flex; align-items: center; gap: .5rem; padding: .8rem 1rem .3rem; }
    .bday-io { margin-left: auto; display: flex; gap: .4rem; }
    /* "For: You / Partner" — only shown (via [hidden] toggled in JS) when the
       Finder is in two-person mode, so a saved birthday fills the right slot. */
    /* Player 1 / Player 2 segmented toggle — CENTRED above its panel (the old
       "For" label is gone; the two player names carry the meaning alone). */
    .bday-target { display: flex; align-items: center; justify-content: center; gap: .55rem; padding: .55rem 1rem .1rem; }
    .bday-target[hidden] { display: none; }
    .bday-seg { display: inline-flex; border: 1px solid var(--rule-strong); border-radius: 8px; overflow: hidden; }
    .bday-seg-btn {
      background: none; border: none; cursor: pointer;
      font-family: var(--sans); font-size: .78rem; color: var(--text-muted);
      padding: .35rem .9rem; transition: background var(--ease), color var(--ease);
    }
    .bday-seg-btn + .bday-seg-btn { border-left: 1px solid var(--rule); }
    .bday-seg-btn.is-active { background: var(--gold); color: #fff; }
    .bday-addform { padding: .35rem 1rem 0; }
    .bday-list {
      flex: 1 1 auto; min-height: 0; overflow-y: auto;
      padding: .35rem .5rem .5rem; margin-top: .35rem;
    }
    .bday-note {
      padding: .7rem 1rem; border-top: 1px solid var(--rule);
      font-size: .7rem; color: var(--text-muted); line-height: 1.45;
    }
    .bday-list .birth-item { border-bottom: none; border-radius: 9px; padding: .55rem .6rem; }
    /* Birth-card chip on each row — a mini parchment card face. */
    .bi-chip {
      flex: 0 0 auto; width: 30px; height: 42px; border-radius: 5px;
      background: var(--card-paper); border: 1px solid #e3d8c4;
      display: flex; align-items: center; justify-content: center;
      font-family: var(--serif); font-size: .82rem; line-height: 1;
      color: var(--card-dark);
    }
    .bi-chip.red { color: var(--red); }
    @media (prefers-reduced-motion: reduce) {
      .bday-overlay, .bday-modal { transition: none; }
    }

    /* ── Personal Cards: date nav bar ─────────────────────────────── */
    .pcards-date-bar {
      display: none; align-items: center; justify-content: center;
      flex-wrap: wrap; gap: .35rem;
      margin-top: 1.75rem;
    }
    .pcards-date-bar.visible { display: flex; }
    .pdate-nav {
      width: 30px; height: 30px; padding: 0;
      display: inline-flex; align-items: center; justify-content: center;
      background: var(--surface); border: 1px solid var(--border);
      border-radius: 50%; line-height: 1;
      font-family: var(--serif); font-size: 1rem;
      color: var(--text-secondary); cursor: pointer;
      transition: all var(--ease);
    }
    .pdate-nav:hover { background: var(--border); color: var(--text-primary); }
    /* Month-step buttons use ‹‹ / ›› (two chevrons) — tighten letter-spacing
       so the pair fits comfortably inside the 30px circle. */
    .pdate-nav[data-shift-month] { letter-spacing: -.18em; padding-right: .18em; }
    .pdate-label {
      font-family: var(--serif); font-size: 1rem;
      padding: .35rem .9rem; min-width: 11rem; text-align: center;
      background: var(--surface); border: 1px solid var(--border);
      border-radius: 6px; line-height: 1.3;
      color: var(--text-primary); cursor: pointer;
      transition: background var(--ease), border-color var(--ease);
    }
    .pdate-label:hover { background: var(--surface-alt); border-color: var(--border-strong); }
    .pdate-input {
      position: absolute; opacity: 0; pointer-events: none;
      width: 0; height: 0; padding: 0; margin: 0; border: 0;
    }
    .pdate-today {
      font-family: var(--sans);
      font-size: .62rem; letter-spacing: .12em; text-transform: uppercase;
      font-weight: 600; color: var(--gold);
      background: transparent; border: none; cursor: pointer;
      padding: .25rem .55rem;
      display: none; width: fit-content; text-align: center; margin: 1.25rem auto 0;
    }
    .pdate-today.visible { display: block; }
    .pdate-today:hover { text-decoration: underline; }
    @media (max-width: 480px) {
      .pcards-row { column-gap: .4rem; }
    }

    /* ── Olney view (Richmond's Mystic Test Book reading + Grand Spread) ── */
    /* Olney is now a popup (see .olney-overlay below). Opened from #rOlneyBtn,
       which sits beside the share + minimise buttons in the result-actions row. */
    .result-actions { align-items: center; }
    /* Shared meta block (dates · crown · Solar/Spirit/Astral · displacement),
       shown above the action row. */
    .result-meta { display: flex; flex-direction: column; align-items: center; gap: .28rem; margin-top: -.1rem; }
    .result-meta:empty { display: none; }
    .result-vals { font-family: var(--mono); font-size: .64rem; letter-spacing: .05em; color: var(--gold-ink); }
    .result-displace { font-size: .66rem; color: var(--muted); letter-spacing: .02em; }

    /* ── Olney 1893 popup (book-opening reveal) ──────────────────────────
       The popup uses its own overlay rather than the shared .ccard-overlay
       chrome because the Olney panel is wider (Richmond's two facing pages
       on desktop) and gets a perspective book-open animation. */
    .olney-overlay {
      position: fixed; inset: 0; z-index: 100;
      display: none; align-items: center; justify-content: center;
      padding: clamp(.5rem, 2.5vw, 1.5rem);
      background: rgba(8, 6, 18, .82);
      perspective: 1800px;
      animation: olneyFadeIn .35s ease;
    }
    .olney-overlay[hidden] { display: none !important; }
    .olney-overlay.open { display: flex; }
    /* Lock background scroll while open (matches the shared rule for the
       compare-card / hexagram popups). */
    html:has(.olney-overlay.open) { overflow: hidden; }
    .olney-book {
      position: relative; max-width: 1080px; width: 100%; max-height: 92vh;
      background: linear-gradient(180deg, var(--surface) 0%, var(--bg) 100%);
      border: 1px solid rgba(217, 184, 122, .35);
      border-radius: var(--r-lg);
      box-shadow: 0 30px 80px rgba(0, 0, 0, .6), inset 0 1px 0 rgba(255, 255, 255, .04);
      display: flex; flex-direction: column; overflow: hidden;
      transform-origin: left center;
      animation: olneyBookOpen .7s cubic-bezier(.2, .8, .25, 1);
    }
    /* The spine: a thin gold rule down the left edge — the visual hinge the book opens around. */
    .olney-book::before {
      content: ''; position: absolute; left: 0; top: 0; bottom: 0;
      width: 3px; background: linear-gradient(180deg, transparent 0, var(--gold-ink) 12%, var(--gold-ink) 88%, transparent 100%);
      opacity: .55;
    }
    .olney-book-inner {
      flex: 1; min-height: 0; overflow-y: auto;
      padding: 1.5rem clamp(1rem, 3vw, 2.4rem) 2rem;
      scrollbar-gutter: stable;
    }
    .olney-close {
      position: absolute; top: .8rem; right: .8rem; z-index: 2;
      background: var(--surface-alt); border: 1px solid var(--border);
      color: var(--muted); font-size: 1rem; line-height: 1;
      width: 30px; height: 30px; border-radius: 50%;
      display: flex; align-items: center; justify-content: center;
      cursor: pointer; transition: all var(--ease);
    }
    .olney-close:hover { background: var(--border); color: var(--text); }
    /* Prev / Next arrows (same pattern as card popup) */
    .olney-nav {
      position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
      width: 2.4rem; height: 4rem;
      display: flex; align-items: center; justify-content: center;
      color: var(--muted); font-size: 1.2rem;
      opacity: 0; transition: opacity var(--ease), color var(--ease);
      cursor: pointer; border-radius: var(--r-sm);
      background: none; border: none;
    }
    .olney-nav-prev { left: .5rem; }
    .olney-nav-next { right: .5rem; }
    .olney-book:hover .olney-nav { opacity: 1; }
    .olney-nav:hover { color: var(--text); }
    .olney-nav:focus-visible { opacity: 1; color: var(--text); outline: 2px solid var(--gold); outline-offset: 2px; }
    /* Open / close keyframes — the popup rotates open around its left edge (the
       spine), like the front cover of a book swinging toward the viewer.
       Honoured by browsers without view transitions; reduced-motion respects below. */
    @keyframes olneyFadeIn { from { opacity: 0; } to { opacity: 1; } }
    @keyframes olneyBookOpen {
      0%   { opacity: 0; transform: rotateY(-100deg) scale(.9); }
      60%  { opacity: 1; }
      100% { opacity: 1; transform: rotateY(0deg) scale(1); }
    }
    @keyframes olneyBookClose {
      0%   { opacity: 1; transform: rotateY(0deg) scale(1); }
      100% { opacity: 0; transform: rotateY(-100deg) scale(.9); }
    }
    .olney-overlay.closing { animation: olneyFadeOut .35s ease forwards; }
    .olney-overlay.closing .olney-book { animation: olneyBookClose .35s cubic-bezier(.7, .1, .8, .3) forwards; }
    @keyframes olneyFadeOut { from { opacity: 1; } to { opacity: 0; } }
    @media (prefers-reduced-motion: reduce) {
      .olney-overlay, .olney-overlay.closing,
      .olney-book, .olney-overlay.closing .olney-book { animation: none; }
    }

    .olney-panel { padding: .25rem 0 .2rem; }
    .olney-sun { display: grid; grid-template-columns: repeat(3, 1fr); gap: .3rem; margin-bottom: .55rem; }
    .olney-sun .spread-card { width: 100%; }
    .olney-grid { display: flex; flex-direction: column; gap: .3rem; max-width: 560px; margin: 0 auto; }
    .olney-row { display: grid; grid-template-columns: repeat(7, 1fr); gap: .3rem; align-items: center; }
    .olney-row .spread-card { width: 100%; }
    .olney-focus { outline: 2px solid var(--gold); outline-offset: 1px; }
    .olney-note { text-align: center; color: var(--muted); font-style: italic; font-size: .9rem; padding: 1.4rem .5rem; }
    .olney-delin { max-width: 620px; margin: 0 auto; }
    /* Cap the plate on a single column (stacked / wide tablet) so the 7-across grid
       can't blow the small engraved cards up far past their desktop size — centred.
       (Desktop two-column resets this to none below; the delineation text already
       caps at 620px above.) */
    .olney-spread { max-width: 500px; margin: 1.4rem auto 0; border-top: 1px solid var(--border); padding-top: 1.2rem; }
    /* Left "page" header in his book's style: running title, then the card at the
       left with its Solar / Spirit / Astral values centred, then a book-style DOUBLE
       rule (border-bottom: double) before the planet delineations (book p.80). */
    .olney-bookhead { text-align: center; border-bottom: 6px double var(--ink-muted); padding-bottom: .8rem; margin-bottom: .9rem; }
    .olney-booktitle {
      font-family: 'Roboto Slab', var(--serif); font-weight: 500;
      font-size: .8rem; letter-spacing: .18em; text-transform: uppercase; color: var(--gold-ink);
    }
    /* Book p.80 header: the card engraving sits at the LEFT, its Solar / Spirit /
       Astral values centred on the page beside it. A mirrored empty 1fr column on
       the right keeps the values truly page-centred regardless of the card width. */
    .olney-headrow {
      display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
      gap: .5rem; margin-top: .55rem;
    }
    .olney-cardface { width: 4rem; margin: 0; grid-column: 1; justify-self: start; }
    .olney-vals { grid-column: 2; margin: 0; display: grid; gap: .14rem; justify-items: center; }
    .olney-vals > div { display: flex; justify-content: center; gap: .5rem; align-items: baseline; }
    .olney-vals dt { font-family: 'Roboto Slab', var(--serif); font-size: .52rem; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-muted); }
    .olney-vals dt::after { content: ','; }                 /* "SOLAR VALUE," — as set in the book */
    .olney-vals dd { margin: 0; font-family: var(--mono); font-size: .72rem; color: var(--gold-ink); }
    .olney-vals dd::after { content: '.'; color: var(--ink-muted); }   /* trailing period, book-style */

    /* Desktop: Richmond's two facing pages — delineation (left) and the engraved
       plate (right), split by a centre gutter, equal height. Mobile stays stacked
       (above). */
    @media (min-width: 760px) {
      /* The book's two facing pages are EQUAL width, and the plate is a dense full
         page of small engraved cards (not a few large ones) — so equal columns at a
         book-like overall width. Each header is its own natural height (no shared
         header zone): the plate's sun-row runs straight into the grid, the
         delineation's double rule sits right under its values. The popup's
         .olney-book is already capped at 1080px wide. */
      .olney-panel {
        display: grid; grid-template-columns: 1fr 1fr; gap: 0; align-items: stretch;
      }
      .olney-delin  { max-width: none; margin: 0; padding-right: 1.3rem; border-right: 1px solid var(--border); }
      .olney-spread { max-width: none; margin: 0; border-top: none; padding-top: 0; padding-left: 1.3rem; }
      /* Plate page: the sun (crown) row flows straight into the grid at the same row
         gap as the rest of the 7×7 — no separating rule (book p.81). (The delineation
         keeps its own double rule, set on .olney-bookhead above — book p.80.) */
      .olney-platehead { margin-bottom: .14rem; }
      .olney-platehead .olney-head { margin-bottom: 0; }
    }
    /* Planet delineations: centred heading + centred text, as the book sets them. */
    .olney-pl { font-family: 'Lora', Georgia, serif; font-size: .85rem; color: var(--secondary); line-height: 1.7; margin: 0 0 .9rem; text-align: center; }
    .olney-pl b { font-family: var(--sans); font-size: .62rem; letter-spacing: .12em; text-transform: uppercase; color: var(--gold-ink); display: block; margin-bottom: .25rem; }
    .olney-pl b::after { content: '.'; }                    /* "MERCURY." — centred heading, book-style */
    @media (max-width: 560px) {
      .olney-row, .olney-head, .olney-sun { gap: .16rem; }
    }

    /* ── Grand Spread as Richmond's engraved plate, on the dark ground ──
       Transparent background so the plate melts into the starfield panel: the
       engraved sunburst masthead + Grand-Spread / Quadrated-to-Time + Future/Past
       chrome render in light gold, with the site's own card faces inside.
       Plate-local tokens point at the site's AA-tuned light tokens. */
    .olney-plate {
      --plate-ink:  var(--gold-ink);   /* light-gold engraving on the dark ground */
      --plate-rule: var(--ink-muted);
      --plate-gold: var(--gold);
      background: transparent;
      color: var(--plate-ink);
      padding: 1.1rem clamp(.55rem, 2vw, 1.2rem) 1.2rem;
    }
    .olney-mast { display: flex; align-items: flex-end; justify-content: center; gap: .3rem; flex-wrap: wrap; margin-bottom: .9rem; }
    .olney-mast-t {
      font-family: 'Rye', var(--serif); font-weight: 400;
      font-size: clamp(.95rem, 3vw, 1.5rem); line-height: 1; color: var(--plate-ink);
      text-shadow: 0 1px 4px rgba(0,0,0,.5);
      padding-bottom: .25rem; white-space: nowrap;
    }
    .olney-sunburst { flex: 0 0 auto; width: clamp(64px, 18vw, 96px); height: auto; }
    .olney-head {
      display: grid; grid-template-columns: repeat(7, 1fr); align-items: start;
      gap: .3rem; margin-bottom: 1rem;
    }
    .olney-hside { padding-top: .3rem; }
    .olney-hside.left  { grid-column: 1 / span 2; text-align: right; }
    .olney-head .olney-sun { grid-column: 3 / span 3; }
    .olney-hside.right { grid-column: 6 / span 2; text-align: left; }
    .olney-hlabel {
      font-family: 'Roboto Slab', var(--serif); font-weight: 700;
      font-size: clamp(.5rem, 1.7vw, .7rem); letter-spacing: .08em;
      text-transform: uppercase; line-height: 1.3; color: var(--plate-ink);
    }
    .olney-horn { font-size: .6rem; color: var(--plate-rule); margin: .15rem 0; letter-spacing: .2em; }
    .olney-hfp {
      font-family: 'Roboto Slab', var(--serif); font-weight: 700;
      font-size: clamp(.62rem, 2vw, .9rem); letter-spacing: .14em;
      text-transform: uppercase; color: var(--plate-ink);
    }
    /* plate-local layout + focus colour. The chart fills the plate so the cards
       are as large as the page allows; the sun line aligns with the grid columns. */
    .olney-plate .olney-sun { margin-bottom: 0; }
    /* Dense engraved-plate grid: small cards packed with tight gaps, the way the
       7×7 lay-out reads on the book's full-page plate. */
    .olney-plate .olney-grid { max-width: none; gap: .14rem; }
    .olney-plate .olney-row  { gap: .14rem; }
    .olney-plate .olney-head { gap: .14rem; }
    .olney-plate .olney-focus  { outline-color: var(--plate-gold); }
    /* Book-style cards: drop the corner rank indices AND trim the parchment margin
       so the pips / court figures fill more of the card, like the engraved cards on
       his plates. Number cards just get a modestly enlarged pip box + pip size. The
       court figure is grown, and — crucially — its TWO mirrored suit-pips are moved
       in lock-step toward the corners so each stays beside the head: the suit-pip is
       a separate element pinned to a fixed card coordinate, decoupled from the figure
       art, so growing the figure alone strands it (that was the long-standing bug);
       moving the pips with it keeps them placed. .olney-cardface (the specimen on the
       delineation page) shares every rule, so it matches the cards in the chart. */
    .olney-plate .spread-card .card-corner,
    .olney-cardface .card-corner { display: none; }
    .olney-plate .card-pips,  .olney-cardface .card-pips { top: 9%; bottom: 9%; left: 8%; right: 8%; }
    .olney-plate .pip,        .olney-cardface .pip { font-size: 22cqw; }
    .olney-plate .pip.ace,    .olney-cardface .pip.ace { font-size: 52cqw; }
    /* Figure already fills the card height; to make it read a little bigger we zoom
       INTO the art (scale > 1), pushing the transparent margin baked into the webp
       off the edges. The card is overflow:hidden so the overflow clips cleanly to
       its own card (no spill onto neighbours), and the suit-pips are siblings so they
       don't move. ~1.08 is about the ceiling — beyond ~1.15 the crown/feet clip. */
    .olney-plate .court-art,  .olney-cardface .court-art { inset: 0; width: 100%; height: 100%; transform: scale(1.08); }
    /* The two court suit-pips are the card's only direct-child <span>s (number cards
       keep their pips inside .card-pips, so this never touches them). Spread them out
       toward the left / right corner margins so they clear the centred court figure's
       head: top-left pip at left:23%, its mirror at 77%. The vertical (15.5% / 84.5%)
       stays on the number-grid row. !important overrides the per-card inline left/top. */
    .olney-plate .spread-card > span:first-of-type,
    .olney-cardface > span:first-of-type { left: 23% !important; top: 15.5% !important; }
    .olney-plate .spread-card > span:last-of-type,
    .olney-cardface > span:last-of-type  { left: 77% !important; top: 84.5% !important; }
    /* caption under the chart — Richmond's name for this lay-out */
    .olney-spreadname {
      margin-top: .7rem; text-align: center;
      font-family: 'Roboto Slab', var(--serif); font-weight: 500;
      font-size: .56rem; letter-spacing: .14em; text-transform: uppercase;
      color: var(--plate-rule);
    }

    /* ── Card Elements row (suits + numbers), top of Cards of Life ────────
       Four gold suits always shown; the thirteen numbers reveal once a suit
       is selected. Click a glyph to drop its reading (‹ › / ← → / swipe). */
    .elements { padding: 1.6rem 1rem .15rem; }
    /* Tighten the gap from the suits row to the Finder (matched on I Ching). */
    /* First section under the page title (the Finder since the Card Elements
       row moved to the page foot 2026-07-06) gets the top breathing room the
       old first row used to carry as padding. */
    main > h1 + .page-section { margin-block-start: 1.6rem; }
    .el-inner { max-width: var(--max-w); margin: 0 auto; }
    /* Reading-voice toggle (Claude | Olney) — matches the I Ching hexagram
       Fu Xi / King Wen segmented pill (.hex-ref-toggle in iching.css): one
       rounded container split in two, muted-white labels, the active segment
       filled gold with dark-violet text. Hidden until a suit is picked; swaps
       the suit, number AND planet readings. */
    .el-voice {
      display: flex; gap: 0; width: fit-content; margin: 1rem auto .2rem;
      border: 1px solid rgba(255,255,255,.18); border-radius: 999px; overflow: hidden;
    }
    .el-voice[hidden] { display: none; }
    .el-voice-btn {
      font-family: var(--sans); font-size: .66rem; font-weight: 600;
      letter-spacing: .08em; text-transform: uppercase; color: rgba(255,255,255,.6);
      background: transparent; border: none; flex: 0 0 6.5rem; min-height: 44px;
      display: inline-flex; align-items: center; justify-content: center;
      padding: 0 1.1rem; cursor: pointer;
      transition: background var(--ease), color var(--ease);
    }
    .el-voice-btn.is-on { background: var(--gold); color: var(--accent); }
    .suit-row, .num-row {
      display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
      gap: clamp(.6rem, 3vw, 1.7rem);
    }
    /* Fixed glyph-zone height so the Finder sits the same distance below the
       suits as the Inquiry does below the trigrams (matched in iching.css). */
    .suit-row { min-height: 3rem; }
    .num-row { gap: clamp(.35rem, 2vw, .85rem); margin-top: .9rem; }
    .num-row[hidden] { display: none; }
    .el-suit, .el-num {
      background: none; border: none; cursor: pointer; color: var(--gold);
      line-height: 1; padding: .15rem .35rem; border-radius: var(--r-sm);
      font-family: var(--serif);
      display: inline-flex; align-items: center; justify-content: center;
      transition: opacity var(--ease), transform var(--ease), text-shadow var(--ease);
    }
    /* Suits: full-strength gold to match the I Ching trigrams, drawn with the
       deck's own pip glyphs (♠/♣ are SVG, ♥/♦ unicode — via pipMark). */
    .el-suit { font-size: clamp(1.9rem, 6vw, 2.7rem); opacity: 1; }
    .el-suit .pip-svg { width: 1em; height: 1em; }
    .el-num  { font-size: clamp(1rem, 3.4vw, 1.35rem); font-weight: 600; min-width: 1.5em; opacity: .55; }
    .el-suit:hover, .el-suit:focus-visible { transform: translateY(-1px); text-shadow: 0 0 14px rgba(199,154,84,.5); }
    .el-num:hover, .el-num:focus-visible { opacity: 1; }
    .el-suit.sel, .el-num.sel { opacity: 1; transform: translateY(-2px); text-shadow: 0 0 16px rgba(199,154,84,.6); outline: 2px solid var(--gold); outline-offset: 2px; border-radius: 3px; }
    .el-suit.lit, .el-num.lit { opacity: 1; text-shadow: 0 0 14px rgba(199,154,84,.7); }

    /* Planets row — the same gold-glyph language as the suits, one register
       smaller. Sits below the suit/number detail; clicking a glyph opens that
       planet's reading in #plPop (mutually exclusive with the suit/number card). */
    .planet-row {
      display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
      gap: clamp(.5rem, 2.6vw, 1.4rem);
      margin-top: clamp(1.3rem, 4vw, 2rem);
    }
    /* Hidden until a suit is selected (same as the A–K number row). */
    .planet-row[hidden] { display: none; }
    .el-planet {
      background: none; border: none; cursor: pointer; color: var(--gold);
      font-family: var(--serif); line-height: 1; padding: .15rem .35rem;
      border-radius: var(--r-sm); font-size: clamp(1.5rem, 5vw, 2.1rem); opacity: .8;
      display: inline-flex; align-items: center; justify-content: center;
      transition: opacity var(--ease), transform var(--ease), text-shadow var(--ease);
    }
    .el-planet:hover, .el-planet:focus-visible { opacity: 1; transform: translateY(-1px); text-shadow: 0 0 14px rgba(199,154,84,.5); }
    .el-planet.sel { opacity: 1; transform: translateY(-2px); text-shadow: 0 0 16px rgba(199,154,84,.6); outline: 2px solid var(--gold); outline-offset: 2px; border-radius: 3px; }

    .el-detailwrap { display: grid; grid-template-rows: 0fr; margin-top: 0; transition: grid-template-rows .4s var(--ease), margin-top .4s var(--ease); }
    #elements.el-open .el-detailwrap { grid-template-rows: 1fr; margin-top: .9rem; }
    .el-detailmin { overflow: hidden; }
    .el-detail {
      display: flex; align-items: center; justify-content: center; gap: .4rem;
      max-width: 46rem; margin: 0 auto; padding: 0 .3rem;
      opacity: 0; transition: opacity .35s var(--ease);
    }
    #elements.el-open .el-detail { opacity: 1; }
    .el-nav {
      flex: none; background: none; border: none; cursor: pointer; color: var(--gold-ink);
      font-size: 1.8rem; line-height: 1; padding: .2rem .5rem; border-radius: var(--r-sm);
      opacity: .6; transition: opacity var(--ease);
    }
    .el-nav:hover, .el-nav:focus-visible { opacity: 1; }
    .el-card { flex: 1; min-width: 0; text-align: center; }
    .el-head { display: flex; align-items: baseline; justify-content: center; gap: .55rem; margin-bottom: .5rem; }
    .el-fig { font-family: var(--serif); font-size: 1.8rem; color: var(--gold); line-height: 1; }
    .el-name { font-family: var(--serif); font-size: var(--text-lg); color: var(--text); }
    .el-kws { display: flex; flex-wrap: wrap; justify-content: center; gap: .35rem; margin-bottom: .7rem; }
    .el-text { color: var(--ink-muted); font-size: .96rem; line-height: 1.7; }
    .el-text p { margin: 0 0 .7em; }
    .el-keynote { margin-top: .6rem; font-family: var(--serif); font-style: italic; color: var(--gold-ink); }
    @media (prefers-reduced-motion: reduce) {
      .el-detailwrap, .el-detail, .el-suit, .el-num, .el-planet { transition: none; }
    }
