/* =============================================================================
 * prowess.hiphop — theme
 * Prowess The Testament · "Prowess is not given. It is earned."
 *
 * Task P1-10 · spec: 1vsM-Vault/PRDs/prowess.hiphop/spec.md § "Visual system"
 * Contrast audit (measured, do not re-guess):
 *   1vsM-Vault/Evergreen/a-suspected-contrast-failure-is-not-a-measured-one.md
 *
 * -----------------------------------------------------------------------------
 * NAMESPACE CONTRACT — read before adding a token.
 *
 * Every custom property in this file lives in `--pr-*`. Tailwind v4 owns
 * `--color-*`, `--radius-*`, `--shadow-*`, `--text-*`, `--font-*`, `--leading-*`
 * and `--tracking-*`; an unlayered `:root` redefinition of any of those silently
 * overrides every Tailwind utility class on the site with no error. Nothing in
 * this file may be defined in those namespaces — not even "just one".
 *
 * Every `var()` in this file carries a literal fallback. An undefined `var()` is
 * dropped silently: no error, no warning, the element simply renders unstyled.
 *
 * -----------------------------------------------------------------------------
 * PALETTE HONESTY — why some colors ship twice.
 *
 * Nine of the 22 pairings this design actually renders fail WCAG AA. The ten
 * brand hexes from §5 are Prowess's identity and are NOT altered here. Instead
 * each failing color ships twice:
 *
 *   --pr-<name>       the exact brand hex — large display type, decorative fills
 *   --pr-<name>-aa    lightness raised, hue + saturation held — normal-size text,
 *                     borders, focus rings, anything at or below 24px / 18.66px bold
 *   --pr-<name>-aa-ink   the same, retuned for the WORST ground the page can
 *                     produce — see §1.2. Since session 02 the page ground is
 *                     a descent, not Void, so this is the default on the ground
 *
 * Changing the brand hexes outright is Prowess's call, not ours. This keeps the
 * palette honest and the site conformant at the same time.
 *
 * Large-text escape hatch: Dragon Rose and Ember both clear AA large text (3:1)
 * at their brand hex today. Used at >= --pr-size-large-min they need no variant.
 * ============================================================================= */

@layer theme, base, components, utilities;

/* -----------------------------------------------------------------------------
 * 1. TOKENS
 * Deliberately unlayered: `--pr-*` collides with nothing, and unlayered custom
 * properties resolve regardless of where Tailwind's output lands in the cascade.
 * -------------------------------------------------------------------------- */

:root {
  /* --- 1.1 The ten brand hexes (§5, exact — do not alter) ----------------- */
  --pr-void:           #0D0912;  /* base ground                              */
  --pr-ink:            #292425;  /* elevated surface                         */
  --pr-storm:          #37206B;  /* atmosphere                               */
  --pr-arcane:         #4C3388;  /* available state, edges                   */
  --pr-bloodstone:     #62292F;  /* borders (decorative use only at this hex) */
  --pr-dragon-rose:    #B84F6A;  /* selected                                 */
  --pr-ember:          #914E49;  /* warning                                  */
  --pr-spectral-flame: #60CFC7;  /* unlocked — USE SPARINGLY, it has meaning */
  --pr-ash:            #A2AFC3;  /* body text                                */
  --pr-lightning:      #F2F4F8;  /* headings, high-emphasis text             */

  /* --- 1.2 AA-corrected variants (measured; hue + saturation held) --------
   * Two families, and which one you need depends on WHAT IS BEHIND THE TEXT.
   *
   *   -aa       tuned on bare Void. Correct only where Void is guaranteed.
   *   -aa-ink   tuned on the WORST GROUND THIS PAGE CAN PRODUCE.
   *
   * That worst ground moved when Claude Design session 02 replaced the eight
   * per-section background colours with one descent gradient (§1.3b). The page
   * ground is no longer Void — it ramps through violet to a warm #241C2E, and
   * the storm bloom (§1.11) composites another 18% Arcane on top of that at
   * every section threshold. Measured, that composite is #2B203E, and on it the
   * old Ink-tuned variants sat on their floors with nothing to spare: Ember
   * 4.48:1 and Dragon Rose 4.50:1 against a 4.5 floor, Bloodstone 3.00:1
   * against a 3.0 one, and the Arcane edge — which had no Ink variant at all —
   * 2.32:1. Each is raised by one step and Arcane gains its own variant. Every
   * pairing they previously passed measures higher now, so nothing regresses;
   * the tightest margin anywhere on the page is 3.04:1.
   *
   *   on #2B203E (= #241C2E + storm bloom at its 18% peak):
   *     Ember       2.20:1 -> 4.54:1   Dragon Rose   2.83:1 -> 4.56:1
   *     Bloodstone  1.24:1 -> 3.04:1   Arcane edge   1.86:1 -> 3.04:1
   *   the same variants on bare Ink: 4.56 · 4.58 · 3.05 · 3.05
   *   on bare Void:                   5.88 · 5.90 · 3.94 · 3.93
   *
   * Lightning-on-Rose is unrelated to the ground — it is text on a fill.
   * ---------------------------------------------------------------------- */
  --pr-ember-aa:            #AE655F;
  --pr-ember-aa-ink:        #BB7C77;
  --pr-dragon-rose-aa:      #BC5972;
  --pr-dragon-rose-aa-ink:  #C77489;
  --pr-lightning-aa:        #F6F8FA;
  --pr-bloodstone-aa:       #9A404A;
  --pr-bloodstone-aa-ink:   #B54E58;
  --pr-arcane-aa:           #6847BA;
  --pr-arcane-aa-ink:       #7B5DC2;

  /* These PASS at their brand hex and must NOT be "fixed":
   * Ash/Void 8.88 · Ash/Ink 6.89 · Ash/Storm 5.98 · Lightning/Arcane 8.93
   * Lightning/Ember 5.63 · Spectral/Void 10.56 · Spectral/Ink 8.19
   * Spectral/Storm 7.11 · Lightning/Void 17.91
   * And "Locked = Ash + low-opacity violet" measures 6.75–8.38:1 once the violet
   * is composited over Void. It PASSES. Do not lighten Ash. */

  /* --- 1.3 Surfaces --------------------------------------------------------
   * Dark-mode depth comes from surface tokens, NOT from shadows. Surfaces are
   * not required to contrast with each other — that is what elevation is for.
   * A surface pair needs a ratio only when a border is the sole boundary signal;
   * then grade the border, not the surfaces. */
  /* The elevation ramp is VIOLET-TINTED, from Claude Design session 01. Ink
   * (#292425) is a warm brown-grey, and stepping up through it read as dust
   * rather than as storm — it fought the Void→Storm→Arcane spine the rest of the
   * palette is built on. Session 01 ramped through #171223 → #1E1830 instead:
   * the same lightness progression, tinted toward Storm.
   *
   * Contrast only improves. Both are DARKER than the Ink they replace, and every
   * text role on them is light (Ash 8.88:1 on Void, more here), so no measured
   * pairing regresses. Ink is retained as surface-3 for the deepest card ground,
   * which is where the audit measured it. */
  --pr-surface-base:     var(--pr-void, #0D0912);
  --pr-surface-elevated: #171223; /* violet-tinted step 1 — cards, sections */
  --pr-surface-raised:   #1E1830; /* violet-tinted step 2 — hover/active depth */
  --pr-surface-arcane:   var(--pr-storm, #37206B); /* Ash on Storm = 5.98:1 */
  --pr-surface-sunken:   #070510; /* Void darkened; wells, track rows, inputs.
                                   * Darker than Void => every light text role
                                   * gains contrast, never loses it. */

  /* --- 1.3b The descent — ONE ground under all eight sections -------------
   * Claude Design session 02. The sections carry NO background of their own;
   * the whole page ground is a single gradient painted on `.pr-main`, so no
   * two grounds ever butt against each other and there is no seam to disguise.
   * Separation is a fading hairline plus a storm bloom, not a change of colour.
   *
   * Void at the top -> deepest violet under #quest / #music -> warming back
   * toward Ink under #pros -> Void again at the base.
   *
   * 🔴 The warm end stops at #241C2E and does NOT reach Ink (#292425), which is
   * what session 02 proposed. Measured: on bare Ink the Ink-tuned text variants
   * land at exactly 4.50:1, and the storm bloom composites on top of the ground
   * at every section threshold — at Ink that pushes them to 4.00:1, below AA.
   * There is no value of the bloom that rescues it; even at 12% Ink fails. Ink
   * keeps its job as an elevated CARD surface (§1.3), where nothing composites
   * over it. Do not "restore" the Ink stop here without re-measuring. */
  --pr-ground-0: var(--pr-void, #0D0912);
  --pr-ground-1: #120D1C;
  --pr-ground-2: #171124;
  --pr-ground-3: #1B1430;   /* deepest violet — under #quest and #music */
  --pr-ground-4: #241C2E;   /* the warm return — the LIGHTEST ground stop */

  /* --- 1.4 Text roles ------------------------------------------------------ */
  --pr-text-strong:   var(--pr-lightning, #F2F4F8);
  --pr-text:          var(--pr-ash, #A2AFC3);
  --pr-text-on-rose:  var(--pr-lightning-aa, #F6F8FA);
  --pr-text-unlocked: var(--pr-spectral-flame, #60CFC7);
  /* Both of each pair now resolve to the SAME variant. The distinction existed
   * because the ground was Void and cards were Ink; with the descent (§1.3b)
   * the ground is the harder of the two, so the ground-tuned variant is the
   * only safe default. The -ink names stay as call sites for the surfaces that
   * genuinely sit on Ink, and so existing references keep resolving. */
  --pr-text-warning:      var(--pr-ember-aa-ink, #BB7C77);
  --pr-text-warning-ink:  var(--pr-ember-aa-ink, #BB7C77);
  --pr-text-selected:     var(--pr-dragon-rose-aa-ink, #C77489);
  --pr-text-selected-ink: var(--pr-dragon-rose-aa-ink, #C77489);
  /* Locked text stays Ash — it passes over the composited violet. */
  --pr-text-locked:   var(--pr-ash, #A2AFC3);

  /* --- 1.5 Borders and edges (3:1 floor when load-bearing) ---------------- */
  --pr-border:              var(--pr-bloodstone-aa-ink, #B54E58);
  --pr-border-on-elevated:  var(--pr-bloodstone-aa-ink, #B54E58);
  --pr-border-arcane:       var(--pr-arcane-aa-ink, #7B5DC2);
  --pr-border-unlocked:     var(--pr-spectral-flame, #60CFC7);
  /* Rose clears the 3:1 edge floor at its -aa value on every ground stop and
   * under the bloom (3.49:1 worst case), so "selected" keeps its saturation. */
  --pr-border-selected:     var(--pr-dragon-rose-aa, #BC5972);
  --pr-border-warning:      var(--pr-ember-aa-ink, #BB7C77);
  /* Hairline is DECORATIVE. Never the sole signal that a boundary exists —
   * if a boundary must be perceivable, use --pr-border-* above.
   * Session 01's quiet edge: a low violet rather than translucent Ash, so the
   * hairline belongs to the Storm/Arcane spine instead of greying it. Opaque, so
   * it renders identically on every surface in the ramp above.
   * Lifted from #2A2141 to the value sessions 03 and 05 both independently used.
   * The descent lightened the ground under it: measured against the worst
   * ground, #2A2141 is 1.01:1 — not "subtle", invisible. #3A2F52 is 1.24:1,
   * still decorative, still nowhere near a load-bearing edge, but present. */
  --pr-border-hairline:     #3A2F52;
  --pr-border-width:        1px;
  --pr-border-width-thick:  2px;

  /* --- 1.6 Focus -----------------------------------------------------------
   * Lightning, not Spectral Flame: Spectral means "unlocked" and a focus ring
   * must not claim that. Lightning on Void is 17.91:1. */
  --pr-focus-ring:   var(--pr-lightning, #F2F4F8);
  --pr-focus-width:  2px;
  --pr-focus-offset: 2px;

  /* --- 1.7 Type — two families, no more (§5) ------------------------------
   * Display: sharp editorial didone. Sans: geometric, for nav, trivia,
   * captions and scorekeeping. Web font <link>/@font-face belongs in the
   * document head (markup task), not here. */
  --pr-font-display: "Bodoni Moda", "Playfair Display", "Didot", "Times New Roman", Georgia, serif;
  --pr-font-sans: "Space Grotesk", "Inter", "Poppins", ui-sans-serif, system-ui,
                  -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  /* Mobile-first fluid scale, floors sized for a 320px viewport. */
  --pr-size-2xs:     0.75rem;    /* 12px — legal, copyright              */
  --pr-size-xs:      0.8125rem;  /* 13px — meta, credits                 */
  --pr-size-sm:      0.875rem;   /* 14px — captions, scorekeeping        */
  --pr-size-base:    1rem;       /* 16px — body, and the input FLOOR     */
  --pr-size-md:      1.0625rem;
  --pr-size-lg:      clamp(1.125rem, 1.05rem + 0.38vw, 1.375rem);
  --pr-size-xl:      clamp(1.375rem, 1.18rem + 0.95vw, 1.875rem);
  --pr-size-2xl:     clamp(1.75rem, 1.38rem + 1.85vw, 2.75rem);
  --pr-size-3xl:     clamp(2.125rem, 1.55rem + 2.9vw, 3.75rem);
  --pr-size-display: clamp(2.625rem, 1.6rem + 5.1vw, 6rem);
  /* Anything below this must use an -aa variant. At or above it, the brand hex
   * is conformant on its own (AA large text = 3:1). 24px, or 18.66px if bold. */
  --pr-size-large-min: 1.5rem;

  --pr-lead-display: 1.02;
  --pr-lead-tight:   1.18;
  --pr-lead-snug:    1.35;
  --pr-lead-body:    1.6;
  --pr-track-display: -0.02em;
  --pr-track-tight:   -0.01em;
  --pr-track-caps:     0.16em;
  /* Session 01's eyebrow tracking. Much wider than --pr-track-caps, and reserved
   * for the short all-caps labels that sit ABOVE a heading (the eyebrow, the
   * gate's "CHOOSE YOUR ENTRY"). At 0.28em it reads as an engraved caption
   * rather than as small text; do not use it on anything running longer than a
   * few words, where the letter gaps start breaking word shapes apart. */
  --pr-track-wide:     0.28em;
  --pr-weight-regular: 400;
  --pr-weight-medium:  500;
  --pr-weight-bold:    700;

  /* --- 1.8 Space, radius, layout ----------------------------------------- */
  --pr-space-1:  0.25rem;
  --pr-space-2:  0.5rem;
  --pr-space-3:  0.75rem;
  --pr-space-4:  1rem;
  --pr-space-5:  1.5rem;
  --pr-space-6:  2rem;
  --pr-space-7:  3rem;
  --pr-space-8:  4rem;
  --pr-space-9:  6rem;

  /* Vertical rhythm — Claude Design session 02. ONE scale drives the whole
   * descent, so the page reads as a fall at steady speed rather than as eight
   * stops. The --pr-space-* ramp above stays, for padding INSIDE a component;
   * these five are for the distance BETWEEN things.
   *
   * The two breaths are the only deliberate irregularity: one after the entry
   * gate (#signal) and one before the outro (#constellation). Everything
   * between them holds --pr-flow-section exactly. */
  --pr-flow-tight:   clamp(0.75rem, 2vw, 1rem);      /* 12 -> 16  label to thing   */
  --pr-flow-item:    clamp(1.25rem, 3vw, 1.75rem);   /* 20 -> 28  within a block   */
  --pr-flow-block:   clamp(2.25rem, 5vw, 3.5rem);    /* 36 -> 56  heading to body  */
  --pr-flow-section: clamp(5.5rem, 11vw, 9.25rem);   /* 88 -> 148 section to section */
  --pr-flow-breath:  clamp(8.25rem, 16vw, 13.5rem);  /* 132 -> 216 the two exhales */

  /* Sharpened to session 01's shape language: 0 / 2 / 6. The brief asks for a
   * sharp editorial face and a title screen that reads like an RPG plate, and
   * 14px cards were rounding that into a consumer app. Kept as a graded scale
   * rather than a flat 0 so inputs and cards can still differ, and pill stays
   * for the badges, where a capsule IS the shape. */
  --pr-radius-xs:   0px;
  --pr-radius-sm:   2px;
  --pr-radius-md:   6px;
  --pr-radius-lg:   6px;
  --pr-radius-pill: 999px;

  --pr-shell-max: 78rem;
  --pr-measure:   62ch;
  --pr-gutter:    clamp(1rem, 0.62rem + 1.9vw, 2.5rem);
  --pr-tap:       44px;   /* minimum interactive target */
  --pr-hero-art:  57%;    /* desktop split: art occupies 55–60% */
  --pr-player-h:  84px;   /* the persistent bar at >= 60rem (session 03) */

  /* --- 1.9 Shadows — subtle only. NOT the depth mechanism. ---------------- */
  --pr-shadow-soft: 0 18px 40px -22px rgba(13, 9, 18, 0.55);
  --pr-shadow-lift: 0 28px 64px -30px rgba(13, 9, 18, 0.6);
  --pr-glow-spectral: 0 0 34px -10px rgba(96, 207, 199, 0.26);
  --pr-glow-rose:     0 0 34px -10px rgba(184, 79, 106, 0.26);

  /* --- 1.10 Motion --------------------------------------------------------- */
  --pr-dur-fast:      140ms;
  --pr-dur:           240ms;
  --pr-dur-slow:      480ms;
  --pr-dur-cinematic: 3000ms;  /* entry gate transition: 2–4s, always skippable */
  --pr-ease:      cubic-bezier(0.2, 0.7, 0.3, 1);
  --pr-ease-out:  cubic-bezier(0.16, 1, 0.3, 1);
  --pr-drift-slow: 42s;
  --pr-drift-ember: 19s;
  --pr-shimmer: 7s;
  --pr-pulse: 1.8s;

  /* --- 1.11 Texture — three base materials, 3%–10%, never more ------------
   * The site has THREE recurring materials, not a skin per section, and since
   * Claude Design session 02 each has ONE job and one place:
   *
   *   GRAIN  6%  the whole page ground, once, on `.pr-main::after`
   *   STRATA 4%  elevated surfaces only — .pr-card .pr-form .pr-slot-empty
   *              .pr-slot-empty. This is what makes elevation legible; it is
   *              the reason the shadows stay subtle instead of doing that work.
   *   BLOOM 18%  section thresholds only, bleeding 220px down from the seam.
   *              It marks arrival, then dissolves.
   *
   * Grain and Strata use `multiply`: they can only DARKEN the ground. Every
   * text role here is lighter than every ground, so a darker ground strictly
   * increases contrast. "Texture may never reduce contrast" is structural for
   * those two, not a promise.
   *
   * 🔴 Bloom is the exception — it is ADDITIVE violet, so it lightens, and it
   * is the only material that can cost contrast. Session 02 specified 30%; that
   * is measured to fail. At 30% the ground-tuned text variants fall to 4.23:1
   * over the warm end, and to 4.00:1 had the ground reached Ink. Capped at 18%
   * they hold 4.54:1 over the warm end, which is where the cap comes from. That
   * cap and the warm end stopping short of Ink (§1.3b) together produce the
   * worst composite the page can make, #2B203E — and that is what every -aa-ink
   * variant in §1.2 is tuned against. Change either number and both have to be
   * re-measured together.
   */
  --pr-texture-min:      0.03;
  --pr-texture-strength: 0.06;   /* grain */
  --pr-texture-strata-opacity: 0.04;
  --pr-texture-max:      0.10;
  --pr-texture-scale:    150px;
  /* The fading section hairline. Arcane's ground-tuned edge at 42%; decorative,
   * never the sole signal that a boundary exists. */
  --pr-rule: rgba(123, 93, 194, 0.42);

  --pr-texture-grain:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='150' height='150' viewBox='0 0 150 150'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.86' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='150' height='150' filter='url(%23g)'/%3E%3C/svg%3E");

  /* Strata — session 02's horizontal 1-in-4px banding, but painted in the
   * sunken ink and composited with `multiply` instead of in Lightning. The
   * design's banding was light, and light texture lightens the ground: measured
   * over this descent, a Lightning film at 6% drops the ground-tuned text roles
   * to 3.83:1. Inverted it reads the same at a glance and can only darken. */
  --pr-texture-strata:
    repeating-linear-gradient(
      180deg,
      rgba(7, 5, 16, 0.9) 0px,
      rgba(7, 5, 16, 0.9) 1px,
      rgba(7, 5, 16, 0)   1px,
      rgba(7, 5, 16, 0)   4px
    );

  /* Storm bloom. One radial, anchored to the TOP of the element it is on, so
   * it bleeds down from a threshold and is gone within 220px. 18% peak — see
   * the cap note in §1.11. */
  --pr-texture-bloom:
    radial-gradient(70% 55% at 50% 0%, rgba(76, 51, 136, 0.18) 0%,
                                       rgba(76, 51, 136, 0) 72%);

  /* Overlays are ALWAYS gradients — never a flat scrim. */
  /* Mobile hero: transparent across the top 46% — that band holds the face and
   * torso of the 1501x2048 portrait and must stay unobstructed — then ramping
   * to near-opaque under the copy well. */
  --pr-overlay-hero:
    linear-gradient(180deg, rgba(13, 9, 18, 0) 0%, rgba(13, 9, 18, 0) 46%,
                            rgba(13, 9, 18, 0.28) 58%, rgba(13, 9, 18, 0.72) 74%,
                            rgba(13, 9, 18, 0.94) 100%);
  --pr-overlay-hero-desktop:
    linear-gradient(90deg, rgba(13, 9, 18, 0.97) 0%, rgba(13, 9, 18, 0.86) 34%,
                           rgba(13, 9, 18, 0.34) 62%, rgba(13, 9, 18, 0) 86%);
  --pr-overlay-backdrop:
    linear-gradient(160deg, rgba(13, 9, 18, 0.92) 0%, rgba(23, 14, 42, 0.88) 48%,
                            rgba(13, 9, 18, 0.94) 100%);
  --pr-overlay-card:
    linear-gradient(180deg, rgba(13, 9, 18, 0) 38%, rgba(13, 9, 18, 0.86) 100%);

  /* State fills. Locked violet stays within the measured 15%–50% band. */
  --pr-fill-locked:    rgba(76, 51, 136, 0.22);
  --pr-fill-available: rgba(76, 51, 136, 0.34);
  --pr-fill-selected:  var(--pr-dragon-rose, #B84F6A);
  --pr-fill-unlocked:  rgba(96, 207, 199, 0.14);
  --pr-fill-warning:   rgba(145, 78, 73, 0.22);
  --pr-fill-disabled:  rgba(162, 175, 195, 0.08);
}

/* -----------------------------------------------------------------------------
 * 2. BASE
 * -------------------------------------------------------------------------- */

@layer base {

  html {
    box-sizing: border-box;
    -webkit-text-size-adjust: 100%;
    scroll-behavior: smooth;
    scroll-padding-block-start: var(--pr-space-8, 4rem);
  }

  *, *::before, *::after { box-sizing: inherit; }

  body {
    margin: 0;
    min-inline-size: 320px;
    background-color: var(--pr-surface-base, #0D0912);
    color: var(--pr-text, #A2AFC3);
    font-family: var(--pr-font-sans, system-ui, sans-serif);
    font-size: var(--pr-size-base, 1rem);
    line-height: var(--pr-lead-body, 1.6);
    font-weight: var(--pr-weight-regular, 400);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    overflow-x: hidden;
  }

  h1, h2, h3, h4 {
    margin: 0 0 var(--pr-space-4, 1rem);
    font-family: var(--pr-font-display, Georgia, serif);
    color: var(--pr-text-strong, #F2F4F8);
    font-weight: var(--pr-weight-bold, 700);
    line-height: var(--pr-lead-tight, 1.18);
    letter-spacing: var(--pr-track-display, -0.02em);
    text-wrap: balance;
  }

  h1 { font-size: var(--pr-size-3xl, 2.125rem); line-height: var(--pr-lead-display, 1.02); }
  h2 { font-size: var(--pr-size-2xl, 1.75rem); }
  h3 { font-size: var(--pr-size-xl, 1.375rem); }
  h4 {
    font-family: var(--pr-font-sans, system-ui, sans-serif);
    font-size: var(--pr-size-lg, 1.125rem);
    letter-spacing: var(--pr-track-tight, -0.01em);
  }

  p, li { max-inline-size: var(--pr-measure, 62ch); }
  p { margin: 0 0 var(--pr-space-4, 1rem); }

  a {
    color: var(--pr-text-strong, #F2F4F8);
    text-decoration-color: var(--pr-border-arcane, #7B5DC2);
    text-underline-offset: 0.18em;
    text-decoration-thickness: 1px;
    transition: color var(--pr-dur-fast, 140ms) var(--pr-ease, ease),
                text-decoration-color var(--pr-dur-fast, 140ms) var(--pr-ease, ease);
  }
  a:hover { color: var(--pr-text-unlocked, #60CFC7); text-decoration-color: currentColor; }
  a:active { color: var(--pr-text-selected, #C77489); }

  img, picture, video, canvas, svg { display: block; max-inline-size: 100%; height: auto; }

  hr {
    border: 0;
    block-size: 1px;
    background-image: linear-gradient(
      90deg,
      rgba(162, 175, 195, 0) 0%,
      var(--pr-border-hairline, #3A2F52) 50%,
      rgba(162, 175, 195, 0) 100%
    );
    margin-block: var(--pr-space-7, 3rem);
  }

  /* One focus treatment for the whole site. Halo first so the ring reads on
   * Void, Ink, Storm and on a Dragon Rose fill alike. */
  :focus-visible {
    outline: var(--pr-focus-width, 2px) solid var(--pr-focus-ring, #F2F4F8);
    outline-offset: var(--pr-focus-offset, 2px);
    border-radius: var(--pr-radius-sm, 2px);
  }
  :focus:not(:focus-visible) { outline: none; }

  ::selection {
    background-color: var(--pr-fill-available, rgba(76, 51, 136, 0.34));
    color: var(--pr-text-strong, #F2F4F8);
  }

  input, textarea, select, button { font: inherit; color: inherit; }
  /* 16px floor in inputs — anything smaller triggers iOS zoom-on-focus. */
  input, textarea, select { font-size: max(var(--pr-size-base, 1rem), 16px); }
}

/* -----------------------------------------------------------------------------
 * 3. LAYOUT SHELL + THE NINE SECTIONS
 * IDs are fixed by the markup task — do not rename:
 * #threshold #signal #quest #music #bio #shop #pros #constellation
 * -------------------------------------------------------------------------- */

@layer components {


  /* --- 3.0 The descent — vertical rhythm (Claude Design session 02) -------
   * ONE gradient across all eight sections. The sections themselves carry no
   * background at ALL, which is the whole point: with nothing to butt against,
   * there is no seam to disguise. What separates one section from the next is
   * a hairline that fades out at both ends plus a storm bloom bleeding down
   * from the threshold — not a change of ground.
   *
   * Stacking, and it matters: `.pr-main` isolates, its ::after paints the grain
   * at z-index 0, and every section sits at z-index 1. Drop the section
   * z-index and the grain — which comes later in DOM order — paints over the
   * content instead of into the ground. */
  .pr-main {
    position: relative;
    isolation: isolate;
    background-image: linear-gradient(
      180deg,
      var(--pr-ground-0, #0D0912) 0%,
      var(--pr-ground-0, #0D0912) 12%,
      var(--pr-ground-1, #120D1C) 24%,
      var(--pr-ground-2, #171124) 38%,
      var(--pr-ground-3, #1B1430) 52%,
      var(--pr-ground-2, #171124) 66%,
      var(--pr-ground-4, #241C2E) 82%,
      var(--pr-ground-0, #0D0912) 100%
    );
  }

  /* Material 1 — grain, over the whole ground, once. `multiply`, so it can
   * only darken: contrast strictly improves. */
  .pr-main::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background-image: var(--pr-texture-grain, none);
    background-repeat: repeat;
    background-size: var(--pr-texture-scale, 150px) var(--pr-texture-scale, 150px);
    mix-blend-mode: multiply;
    opacity: var(--pr-texture-strength, 0.06);
  }

  .pr-section {
    position: relative;
    z-index: 1;
    isolation: isolate;
    /* 🔴 THE HORIZONTAL GUTTER AND MEASURE LIVE HERE NOW.
     *
     * They used to live on `.pr-shell`, which appears in no template and never
     * has — so every section on the site ran edge to edge, text touching the
     * viewport at 320px, and stretched the full width of a desktop display. The
     * suite could not see it: section 6 checks tokens inside the CSS, section 3
     * checks ids inside the markup, and neither correlates the two. `.pr-shell`
     * is the class that check was written after finding.
     *
     * Put on .pr-section rather than reviving .pr-shell because .pr-section is
     * what every page already uses; a wrapper nobody remembers to add is the
     * failure mode being fixed. */
    inline-size: 100%;
    max-inline-size: var(--pr-shell-max, 78rem);
    margin-inline: auto;
    padding-inline: var(--pr-gutter, 1rem);
    padding-block-start: var(--pr-flow-section, 5.5rem);
    padding-block-end: 0;
    scroll-margin-block-start: var(--pr-space-7, 3rem);
  }
  /* The last section owes the page a floor. */
  .pr-section:last-of-type { padding-block-end: var(--pr-flow-breath, 8.25rem); }

  /* Separation, part 1: a hairline that fades to nothing at both ends, so it
   * reads as a horizon rather than as the top edge of a box. Decorative only —
   * no boundary here is load-bearing, the spacing is what separates. */
  .pr-section + .pr-section::before {
    content: "";
    position: absolute;
    inset-block-start: 0;
    inset-inline: var(--pr-gutter, 1rem);
    block-size: 1px;
    pointer-events: none;
    background-image: linear-gradient(
      90deg,
      rgba(123, 93, 194, 0) 0%,
      var(--pr-rule, rgba(123, 93, 194, 0.42)) 18%,
      var(--pr-rule, rgba(123, 93, 194, 0.42)) 82%,
      rgba(123, 93, 194, 0) 100%
    );
  }

  /* Separation, part 2 — material 3: the storm bloom. Marks the arrival, then
   * dissolves within 220px. z-index -1 keeps it behind the section's content
   * but above the ground; capped at 18% (§1.11) and measured there. */
  .pr-section + .pr-section::after {
    content: "";
    position: absolute;
    inset-block-start: 0;
    inset-inline: 0;
    block-size: 220px;
    z-index: -1;
    pointer-events: none;
    background-image: var(--pr-texture-bloom, none);
  }

  /* The two breaths. Everything else holds --pr-flow-section exactly, so these
   * are the only two places the page slows down: after the entry gate, and
   * before the outro. */
  #signal        { padding-block-start: var(--pr-flow-breath, 8.25rem); }
  #constellation { padding-block-start: var(--pr-flow-breath, 8.25rem); }

  /* Material 2 — strata, on the elevated surfaces and nowhere else. THIS is
   * what carries elevation; the shadows stay subtle instead of doing that job.
   * `multiply` again, so it only darkens. */
  .pr-card,
  .pr-form,
  .pr-slot-empty { position: relative; isolation: isolate; }

  .pr-card::before,
  .pr-form::before,
  .pr-slot-empty::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;                 /* behind content, above the element's own bg */
    pointer-events: none;
    border-radius: inherit;
    background-image: var(--pr-texture-strata, none);
    mix-blend-mode: multiply;
    opacity: var(--pr-texture-strata-opacity, 0.04);
  }

  /* The eyebrow is the one label that takes the wide engraved tracking. */
  .pr-eyebrow {
    display: block;
    margin-block-end: var(--pr-flow-tight, 0.75rem);
    font-size: var(--pr-size-sm, 0.875rem);
    font-weight: var(--pr-weight-medium, 500);
    letter-spacing: var(--pr-track-wide, 0.28em);
    text-transform: uppercase;
    color: var(--pr-text, #A2AFC3);
  }

  /* --- 3.2 Hero / #threshold — Storm Threshold ---------------------------
   * The character NEVER animates. Only environment. */

  #threshold {
    display: grid;
    min-block-size: 100svh;
    padding-block: 0;
    align-content: stretch;
  }

  .pr-hero {
    position: relative;
    isolation: isolate;
    display: grid;
    /* 🔴 100svh is for the FULL-BLEED ARTWORK this hero was designed around —
     * copy pinned to the lower third of a dragon illustration. That artwork was
     * never delivered (the brief called it "the attached artwork"; the PDF
     * carries zero embedded images), so with no image the rule produced a full
     * viewport of near-empty gradient above her name. Measured at 390x844: the
     * hero was 844px tall and the wordmark started below the fold.
     *
     * The height is now conditional on there being something to fill it.
     * `data-art` is set by index.php from artist-profile.php's avatar_url; drop
     * a press photo in and the cinematic hero comes back on its own. */
    min-block-size: auto;
    /* Mobile-first: art near-full-bleed, copy stacked over its lower third. */
    grid-template-areas: "stage";
    grid-template-rows: minmax(0, 1fr);
    /* 🔴 Without this the implicit column is `auto`, which sizes to the item's
     * content rather than to the container — so .pr-hero-copy's `inline-size:
     * 100%` resolved against an indefinite track and the whole hero collapsed to
     * roughly 380px on a 1280px page. The bio wrapped after five words and the
     * two calls to action stacked instead of sitting side by side. */
    grid-template-columns: minmax(0, 1fr);
  }

  /* Only a hero that HAS art gets the cinematic height. */
  .pr-hero[data-art="yes"] { min-block-size: 100svh; }

  .pr-hero-art {
    grid-area: stage;
    position: relative;
    z-index: 0;
    margin: 0;
    block-size: 100%;
    overflow: hidden;
  }

  .pr-hero-img {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
    /* Face and torso sit in the upper 45% of a 1501x2048 portrait; anchoring
     * high keeps them clear of the copy well below. */
    object-position: 50% 12%;
  }

  /* The hero overlay is a GRADIENT, never a flat scrim — the copy well begins
   * where it is already >= 0.72 alpha, so overlay text never lands on bare
   * artwork, and the top 46% stays clear to protect the face and torso.
   * It lives on `.pr-hero-art::after` (§9.3). It used to live on a separate
   * `.pr-hero__scrim` element that the markup never rendered — a BEM leftover
   * that styled nothing, silently, which is exactly the failure mode the CLASS
   * NAMES contract exists to prevent. The rule is gone; do not reintroduce it. */

  .pr-hero-copy {
    grid-area: stage;
    z-index: 2;
    align-self: end;
    inline-size: 100%;
    max-inline-size: var(--pr-shell-max, 78rem);
    margin-inline: auto;
    padding: var(--pr-space-6, 2rem) var(--pr-gutter, 1rem) var(--pr-space-8, 4rem);
  }

  .pr-hero-wordmark {
    margin: 0 0 var(--pr-space-3, 0.75rem);
    font-family: var(--pr-font-display, Georgia, serif);
    font-size: var(--pr-size-display, 2.625rem);
    line-height: var(--pr-lead-display, 1.02);
    letter-spacing: var(--pr-track-display, -0.02em);
    color: var(--pr-text-strong, #F2F4F8);
    text-wrap: balance;
  }

  .pr-hero-tagline {
    max-inline-size: 34ch;
    margin-block-end: var(--pr-space-6, 2rem);
    font-size: var(--pr-size-lg, 1.125rem);
    color: var(--pr-text, #A2AFC3);
  }


  @media (min-width: 60rem) {
    /* Desktop: split cinematic. Art right, 55–60%. Copy on a dark gradient left. */
    .pr-hero {
      grid-template-areas: "copy stage";
      grid-template-columns: minmax(0, 1fr) var(--pr-hero-art, 57%);
      grid-template-rows: 1fr;
      align-items: stretch;
    }
    .pr-hero-art { grid-area: stage; }
    .pr-hero-img { object-position: 50% 8%; }
    /* The desktop overlay direction is set on .pr-hero-art::after in §9.3. */
    .pr-hero-copy {
      grid-area: copy;
      align-self: center;
      padding-inline: clamp(2rem, 5vw, 5rem);
      padding-block: var(--pr-space-8, 4rem);
    }
  }

  /* --- 3.3 Signal / Sound Vault / Bio / Shop / Pros / Constellation ------- */

  .pr-card {
    position: relative;
    isolation: isolate;
    padding: var(--pr-space-5, 1.5rem);
    border: var(--pr-border-width, 1px) solid var(--pr-border-hairline, #3A2F52);
    border-radius: var(--pr-radius-lg, 6px);
    background-color: var(--pr-surface-elevated, #171223);
    box-shadow: var(--pr-shadow-soft, 0 18px 40px -22px rgba(13, 9, 18, 0.55));
    transition: background-color var(--pr-dur, 240ms) var(--pr-ease, ease),
                border-color var(--pr-dur, 240ms) var(--pr-ease, ease),
                transform var(--pr-dur, 240ms) var(--pr-ease, ease);
  }
  /* Depth on hover comes from the raised SURFACE, not from a heavier shadow. */
  .pr-card:hover {
    background-color: var(--pr-surface-raised, #1E1830);
    border-color: var(--pr-border-arcane, #7B5DC2);
  }

  /* .pr-cards / .pr-projects removed 2026-08-10 — superseded by .pr-card-grid
   * (§11, used by index.php and music.php) and .pr-project-list (about.php).
   * They were never in the rebuilt markup. */

  /* Sound Vault card art gets a gradient foot so the title always has ground. */
  .pr-card-cover {
    position: relative;
    isolation: isolate;
    border-radius: var(--pr-radius-md, 6px);
    overflow: hidden;
  }
  .pr-card-cover::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-image: var(--pr-overlay-card,
      linear-gradient(180deg, rgba(13, 9, 18, 0) 38%, rgba(13, 9, 18, 0.86) 100%));
  }
  .pr-card-title {
    margin: var(--pr-space-3, 0.75rem) 0 var(--pr-space-1, 0.25rem);
    font-size: var(--pr-size-lg, 1.125rem);
  }

  /* Origin Scroll — the one place display type runs at full size, so the brand
   * hexes are conformant here without an -aa variant (AA large text, 3:1). */

  /* The Armory is SEALED at launch: no product promises, no prices. */

  /* No border-block-start here any more: with one continuous ground, every
   * section is separated the same way (§3.0) and a hard rule on this one alone
   * put a box back around the outro. */
  #constellation {
    padding-block-end: var(--pr-flow-breath, 8.25rem);
  }
  #constellation .pr-copyright {
    font-size: var(--pr-size-2xs, 0.75rem);
    color: var(--pr-text, #A2AFC3);
  }

  /* --- 3.4 Navigation — Quest · Music · Bio · Shop ------------------------ */

  .pr-nav {
    position: sticky;
    inset-block-start: 0;
    z-index: 40;
    background-image: linear-gradient(
      180deg,
      rgba(13, 9, 18, 0.96) 0%,
      rgba(13, 9, 18, 0.82) 62%,
      rgba(13, 9, 18, 0) 100%
    );
    backdrop-filter: blur(10px);
  }
  .pr-nav-list {
    display: flex;
    /* 🔴 WRAP, do not scroll. This was `overflow-x: auto` with the scrollbar
     * hidden, which was fine for four anchors and is not fine for six routes:
     * measured at 390px, PRESS ended at 454px and SHOP at 527px — both off the
     * screen, in a scroller with its scrollbar deliberately hidden, so there was
     * no affordance of any kind telling a visitor the shop existed. A hidden
     * scrollbar on a primary navigation hides destinations, not chrome.
     *
     * Wrapping costs one extra header line on a phone and loses nothing. */
    flex-wrap: wrap;
    gap: var(--pr-space-1, 0.25rem);
    margin: 0;
    padding: var(--pr-space-2, 0.5rem) var(--pr-gutter, 1rem);
    list-style: none;
  }
  .pr-nav-link {
    display: inline-flex;
    align-items: center;
    min-block-size: var(--pr-tap, 44px);
    padding-inline: var(--pr-space-3, 0.75rem);
    border-radius: var(--pr-radius-sm, 2px);
    font-size: var(--pr-size-sm, 0.875rem);
    font-weight: var(--pr-weight-medium, 500);
    letter-spacing: var(--pr-track-caps, 0.16em);
    text-transform: uppercase;
    text-decoration: none;
    color: var(--pr-text, #A2AFC3);
    white-space: nowrap;
  }
  .pr-nav-link:hover { color: var(--pr-text-strong, #F2F4F8); background-color: var(--pr-fill-locked, rgba(76, 51, 136, 0.22)); }
  .pr-nav-link[aria-current="true"] {
    color: var(--pr-text-strong, #F2F4F8);
    background-color: var(--pr-fill-available, rgba(76, 51, 136, 0.34));
  }
}

/* -----------------------------------------------------------------------------
 * 4. THE SIX INTERFACE STATES (§5)
 * These are structure, not decoration. Apply to trial cards, track cards,
 * trivia options, rank steps — anything the quest moves through.
 *
 *   Locked    Ash + low-opacity violet
 *   Available Lightning + Arcane
 *   Selected  Dragon Rose
 *   Correct / unlocked  Spectral Flame
 *   Warning   Ember
 *   Complete  Spectral Flame + a restrained Lightning pulse
 * -------------------------------------------------------------------------- */

@layer components {


  /* 1. LOCKED — Ash on violet composited over Void. Measured 6.75–8.38:1.
   *    Do not lighten Ash to "fix" this; it already passes. */

  /* 2. AVAILABLE — Lightning on Arcane. Measured 8.93:1. */

  /* 3. SELECTED — Dragon Rose. Filled: text uses the AA'd Lightning (4.52:1).
   *    Outlined: the label uses the AA'd Rose, never the brand hex at body size. */
  /* On the elevated Ink ground the Rose text needs the -ink variant. */
  #music .pr-state.is-selected--outline,
  #shop .pr-state.is-selected--outline,
  .pr-card .pr-state.is-selected--outline { color: var(--pr-text-selected-ink, #C77489); }

  /* 4. CORRECT / UNLOCKED — Spectral Flame. Measured 10.56:1 on Void.
   *    Used SPARINGLY. It means "unlocked" and nothing else. */

  /* 5. WARNING — Ember, AA'd for body-size text. Never the bare hex below 24px. */
  #music .pr-state.is-warning,
  #shop .pr-state.is-warning,
  .pr-card .pr-state.is-warning { color: var(--pr-text-warning-ink, #BB7C77); }

  /* 6. QUEST COMPLETE — Spectral Flame plus a restrained Lightning pulse.
   *    Finite count: it announces, then stops. */

}

/* -----------------------------------------------------------------------------
 * 5. CONTROLS — every button carries default / hover / active / disabled,
 * plus focus-visible. Minimum target 44px.
 * -------------------------------------------------------------------------- */

@layer components {

  .pr-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--pr-space-2, 0.5rem);
    min-block-size: var(--pr-tap, 44px);
    min-inline-size: var(--pr-tap, 44px);
    padding: var(--pr-space-3, 0.75rem) var(--pr-space-5, 1.5rem);
    border: var(--pr-border-width, 1px) solid transparent;
    border-radius: var(--pr-radius-sm, 2px);
    font-family: var(--pr-font-sans, system-ui, sans-serif);
    font-size: var(--pr-size-sm, 0.875rem);
    font-weight: var(--pr-weight-medium, 500);
    letter-spacing: var(--pr-track-caps, 0.16em);
    text-transform: uppercase;
    text-decoration: none;
    text-align: center;
    cursor: pointer;
    background-color: transparent;
    color: var(--pr-text-strong, #F2F4F8);
    transition: background-color var(--pr-dur-fast, 140ms) var(--pr-ease, ease),
                border-color var(--pr-dur-fast, 140ms) var(--pr-ease, ease),
                color var(--pr-dur-fast, 140ms) var(--pr-ease, ease),
                transform var(--pr-dur-fast, 140ms) var(--pr-ease, ease);
  }

  /* Primary — Dragon Rose fill. Label uses the AA'd Lightning: on the brand
   * Rose fill, plain Lightning is 4.37:1 and misses.
   *
   * 🔴 THIS TREATMENT LIVES ON .pr-button ITSELF NOW, and that is a fix, not a
   * refactor. The rule that used to carry it was `.pr-button-quest`, and it left
   * with the quest — so a bare `.pr-button` was `border: 1px solid transparent`
   * on `background-color: transparent`, i.e. INVISIBLE. Every primary call to
   * action on the rebuilt site rendered as a line of uppercase text with no box:
   * "HEAR THE CATALOGUE" on the home page, "LISTEN & BUY ON BANDCAMP" on every
   * release page. The old markup never hit it because it always paired
   * .pr-button with a variant. A default that renders nothing is not a default. */
  .pr-button {
    background-color: var(--pr-dragon-rose, #B84F6A);
    border-color: var(--pr-dragon-rose, #B84F6A);
    color: var(--pr-text-on-rose, #F6F8FA);
  }
  .pr-button:hover {
    background-color: var(--pr-dragon-rose-aa, #BC5972);
    border-color: var(--pr-lightning, #F2F4F8);
  }
  .pr-button:active { transform: translateY(1px); }
  .pr-button:focus-visible {
    outline: var(--pr-focus-width, 2px) solid var(--pr-focus-ring, #F2F4F8);
    outline-offset: var(--pr-focus-offset, 2px);
  }
  /* The disabled treatment is declared ONCE, further down in §9 — the block
   * that also covers .is-disabled. A first, narrower copy used to sit here and
   * was shadowed on every property it set, so the values you read here were
   * never the values that rendered. Removed 2026-08-10. */

  /* .pr-button-outbound removed 2026-08-10 — superseded by .pr-button-ghost
   * below, which is what / and /links actually render for every outbound and
   * secondary CTA. */

  /* Ghost — body-level actions. */

  /* Unlock — Spectral Flame. Sparingly: only for actions that actually unlock. */


  /* Disabled — one treatment for every variant. Never conveyed by color alone;
   * the markup must also carry `disabled` or `aria-disabled="true"`. */
  .pr-button:disabled,
  .pr-button[aria-disabled="true"],
  .pr-button.is-disabled {
    background-color: var(--pr-fill-disabled, rgba(162, 175, 195, 0.08));
    border-color: var(--pr-border-hairline, #3A2F52);
    border-style: dashed;
    color: var(--pr-text, #A2AFC3);
    opacity: 0.55;
    cursor: not-allowed;
    transform: none;
    box-shadow: none;
    pointer-events: none;
  }

  /* Trial Locked — the player's own disabled state (§ audio). */
  .pr-button.is-trial-locked {
    background-color: var(--pr-fill-locked, rgba(76, 51, 136, 0.22));
    border-color: var(--pr-border-arcane, #7B5DC2);
    border-style: dashed;
    color: var(--pr-text-locked, #A2AFC3);
    cursor: not-allowed;
  }

  /* Icons match the line-height of the text beside them. */
  @supports (block-size: 1lh) {
  }
  .pr-button .pr-icon { inline-size: 1.25em; block-size: 1.25em; }
}

/* -----------------------------------------------------------------------------
 * 6. FORMS — Pros & Prose, shop waitlist, trivia entry.
 * Focus AND error-with-message are both required; an error color alone is not
 * an error state.
 * -------------------------------------------------------------------------- */

@layer components {

  .pr-field {
    display: grid;
    gap: var(--pr-space-2, 0.5rem);
    margin-block-end: var(--pr-space-4, 1rem);
    max-inline-size: 34rem;
  }

  .pr-field-label {
    font-size: var(--pr-size-sm, 0.875rem);
    font-weight: var(--pr-weight-medium, 500);
    letter-spacing: var(--pr-track-caps, 0.16em);
    text-transform: uppercase;
    color: var(--pr-text, #A2AFC3);
  }


  .pr-input,
  .pr-textarea,
  .pr-select {
    inline-size: 100%;
    min-block-size: var(--pr-tap, 44px);
    padding: var(--pr-space-3, 0.75rem) var(--pr-space-4, 1rem);
    /* 16px floor — smaller triggers zoom-on-focus in iOS Safari. */
    font-size: max(var(--pr-size-base, 1rem), 16px);
    font-family: var(--pr-font-sans, system-ui, sans-serif);
    color: var(--pr-text-strong, #F2F4F8);
    background-color: var(--pr-surface-sunken, #070510);
    border: var(--pr-border-width, 1px) solid var(--pr-border-arcane, #7B5DC2);
    border-radius: var(--pr-radius-sm, 2px);
    transition: border-color var(--pr-dur-fast, 140ms) var(--pr-ease, ease),
                background-color var(--pr-dur-fast, 140ms) var(--pr-ease, ease);
  }
  .pr-textarea { min-block-size: 7rem; resize: vertical; }

  .pr-input::placeholder,
  .pr-textarea::placeholder { color: var(--pr-ash, #A2AFC3); opacity: 0.72; }

  .pr-input:hover,
  .pr-textarea:hover,
  .pr-select:hover { border-color: var(--pr-lightning, #F2F4F8); }

  .pr-input:focus-visible,
  .pr-textarea:focus-visible,
  .pr-select:focus-visible {
    outline: var(--pr-focus-width, 2px) solid var(--pr-focus-ring, #F2F4F8);
    outline-offset: var(--pr-focus-offset, 2px);
    border-color: var(--pr-lightning, #F2F4F8);
    background-color: var(--pr-void, #0D0912);
  }

  .pr-input:disabled,
  .pr-textarea:disabled,
  .pr-select:disabled {
    background-color: var(--pr-fill-disabled, rgba(162, 175, 195, 0.08));
    border-color: var(--pr-border-hairline, #3A2F52);
    border-style: dashed;
    color: var(--pr-text, #A2AFC3);
    opacity: 0.55;
    cursor: not-allowed;
  }

  /* Error — border at the 3:1 border floor, message in AA'd Ember, and an
   * icon so the state is not carried by color alone. */
  .pr-input[aria-invalid="true"],
  .pr-textarea[aria-invalid="true"],
  .pr-select[aria-invalid="true"] {
    border-color: var(--pr-border-warning, #BB7C77);
    border-width: var(--pr-border-width-thick, 2px);
    background-color: var(--pr-fill-warning, rgba(145, 78, 73, 0.22));
  }

  /* .pr-field__error and .pr-field__success are GONE — two more BEM leftovers
   * whose elements the templates never rendered, after .pr-hero__scrim. The
   * markup's single element is `.pr-form-result`, and its states are
   * `.is-error` / `.is-success` in §11 — the one vocabulary the markup and
   * assets/js/contact-form.js can reach. (Session 05's competing
   * `data-state="error"|"success"` form was removed from §10.3 on 2026-08-13;
   * see the note there.) The Ink-tuned Ember they reached for is now the only
   * Ember there is, so the override they carried has nothing left to
   * override. */

  .pr-field-check {
    display: flex;
    align-items: flex-start;
    gap: var(--pr-space-3, 0.75rem);
    font-size: var(--pr-size-sm, 0.875rem);
    color: var(--pr-text, #A2AFC3);
  }
  .pr-field-check input[type="checkbox"] {
    inline-size: 1.25rem;
    block-size: 1.25rem;
    margin: 0.18rem 0 0;
    accent-color: var(--pr-arcane, #4C3388);
    flex: 0 0 auto;
  }

  /* The score is a game score. It must never read as money. */
}

/* -----------------------------------------------------------------------------
 * 7. PLAYER + OVERLAYS
 * -------------------------------------------------------------------------- */

@layer components {

  /* THE PERSISTENT MINI-PLAYER — Claude Design session 03.
   *
   * This replaces a BEM spec (.pr-player__title, __credit, __progress, __played)
   * that could never have matched anything, because the shipped vocabulary is
   * single-hyphen. The markup does not exist yet either way; the difference is
   * that these are the names it will actually carry when it is written, so the
   * spec is now merge-able instead of a second rewrite waiting to happen.
   *
   * 🔴 It must never look like it restarts. Three things buy that, and all three
   * are load-bearing: position lives in --pr-player-pos, which the scrub track
   * reads directly rather than re-deriving; `.pr-player` has no enter transition
   * at all; and the meter runs `infinite alternate` with a per-bar delay, so it
   * is never re-seeded. Paused holds elapsed time, fill and bar heights — only
   * motion and colour drop. Do not add an entrance animation here.
   *
   * The markup owes this component: --pr-player-pos and --pr-player-vol set
   * inline as percentages, data-state on .pr-player (playing / paused / locked),
   * and a .pr-icon span inside each control. */


  /* The scrub reads --pr-player-pos directly. Re-deriving it from the audio
   * element on every frame is what makes a player look like it restarted. */

  /* Scale meter — environment motion, never the character. */

  /* 🔴 VOLUME IS HIDDEN BELOW 60rem, AND THAT IS DELIBERATE.
   * Session 03 hid it on the assumption that hardware volume is the right
   * control on touch. The stronger reason: on iOS Safari
   * HTMLMediaElement.volume is READ-ONLY — assignment is silently ignored — so
   * an in-page slider there is a control that moves and does nothing, which is
   * worse than one that is absent. Android Chrome does honour it, so the width
   * query is a proxy rather than the real predicate; if that costs a real user
   * something, feature-detect writability instead of widening the breakpoint. */


  @media (min-width: 60rem) {
  }

  /* Overlays are gradients, never a flat scrim. */

  .pr-visually-hidden {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  .pr-skip {
    position: absolute;
    inset-block-start: -100vh;
    inset-inline-start: var(--pr-gutter, 1rem);
    z-index: 100;
    padding: var(--pr-space-3, 0.75rem) var(--pr-space-4, 1rem);
    background-color: var(--pr-void, #0D0912);
    color: var(--pr-text-strong, #F2F4F8);
    border: var(--pr-border-width-thick, 2px) solid var(--pr-focus-ring, #F2F4F8);
    border-radius: var(--pr-radius-sm, 2px);
  }
  .pr-skip:focus { inset-block-start: var(--pr-space-2, 0.5rem); }
}

/* -----------------------------------------------------------------------------
 * 8. ENVIRONMENT MOTION
 * The character NEVER animates. Only environment: lightning pulse, cloud drift,
 * ember drift, scale shimmer.
 * -------------------------------------------------------------------------- */

@keyframes pr-lightning-pulse {
  0%   { opacity: 0;    transform: scale(1); }
  18%  { opacity: 0.85; }
  100% { opacity: 0;    transform: scale(1.035); }
}

@keyframes pr-cloud-drift {
  from { transform: translate3d(-4%, 0, 0) scale(1.06); }
  to   { transform: translate3d(4%, -1.5%, 0) scale(1.06); }
}

@keyframes pr-ember-drift {
  0%   { transform: translate3d(0, 12%, 0);  opacity: 0; }
  16%  { opacity: 0.7; }
  84%  { opacity: 0.5; }
  100% { transform: translate3d(3%, -22%, 0); opacity: 0; }
}

@keyframes pr-scale-shimmer {
  0%, 100% { opacity: 0.28; background-position: 0% 50%; }
  50%      { opacity: 0.52; background-position: 100% 50%; }
}

/* The player's scale meter. Height, not opacity — see the note at the meter. */
@keyframes pr-meter-rise {
  0%   { transform: scaleY(0.42); }
  100% { transform: scaleY(1); }
}

@layer components {


  /* The character art is exempt from every animation, unconditionally. */
  .pr-hero-art,
  .pr-hero-art *,
  .pr-hero-img {
    animation: none !important;
    transition: none !important;
    transform: none;
  }

}

/* Reduced motion: kill the lightning pulse, cloud drift, ember drift and scale
 * shimmer outright. The entrance stays usable — it becomes an instant cut, not
 * a blocked one. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }


  /* The meter still has to READ as a meter with the motion off, so it holds a
   * height rather than collapsing to the 0.42 scale its keyframe starts on. */

  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}

/* Forced-colors: hand the whole thing to the OS palette rather than fight it. */
@media (forced-colors: active) {
  .pr-button,
  .pr-state,
  .pr-input,
  .pr-textarea,
  .pr-select { border-color: ButtonBorder; }
  .pr-button:disabled,
  .pr-button[aria-disabled="true"] { color: GrayText; border-color: GrayText; }
  .pr-main::after,
  .pr-section + .pr-section::before,
  .pr-section + .pr-section::after,
  .pr-card::before,
  .pr-form::before,
  .pr-slot-empty::before,
  .pr-env { display: none; }
}

/* Print: core content must survive without the experience. */
@media print {
  .pr-nav, .pr-player, .pr-overlay, .pr-env, .pr-skip { display: none !important; }
  body { background: #FFFFFF; color: #000000; }
  /* The ground gradient and all three materials come off in print. */
  .pr-main { background-image: none; }
  .pr-main::after,
  .pr-section + .pr-section::before,
  .pr-section + .pr-section::after,
  .pr-card::before,
  .pr-form::before,
  .pr-slot-empty::before { display: none; }
}

/* =============================================================================
 * 9. THE SHIPPED MARKUP
 * =============================================================================
 * Everything above was authored against a BEM vocabulary the templates never
 * used: of 144 pr-* classes in the markup, NINE had a rule. The selectors that
 * mapped 1:1 have been renamed in place; this section supplies the rest, so the
 * stylesheet finally describes the page that actually ships.
 *
 * Rules for anything added here:
 *   · --pr-* only. Never --color-/--radius-/--shadow-/--text-/--font-/
 *     --leading-/--tracking- : Tailwind v4 owns those and an unlayered :root
 *     redefinition silently beats every utility class.
 *   · Every var() carries a literal fallback. An undefined var() is DROPPED,
 *     with no error — the element just renders unstyled.
 *   · No new colour values. Only the tokens above, which are measured; the -aa
 *     variants are mandatory for normal-size text, borders and focus rings.
 *   · Mobile-first. Nothing here may introduce a fixed width.
 * -------------------------------------------------------------------------- */

@layer components {

  /* --- 9.1 Document + shell ---------------------------------------------- */

  .pr-body { min-block-size: 100%; }

  .pr-main { display: block; }

  /* THE GUTTER. .pr-shell carried padding-inline and appears in no template, so
   * every heading, paragraph and table ran flush to both viewport edges at
   * 320px. The gutter belongs on the section itself. */
  .pr-section {
    padding-inline: var(--pr-gutter, 1rem);
  }
  /* Content stays readable on wide viewports without a wrapper element. */
  .pr-section > * {
    max-inline-size: var(--pr-shell-max, 78rem);
    margin-inline: auto;
  }
  /* #threshold owns its own full-bleed grid — it must NOT be guttered. */
  #threshold.pr-section { padding-inline: 0; }
  #threshold.pr-section > * { max-inline-size: none; }

  /* --- 9.2 Header, nav, score ------------------------------------------- */

  /* Sticky moved off .pr-nav: the nav is nested inside <header>, and a sticky
   * child can only stick within its parent's box, so the "persistent header"
   * of §13 never persisted. The header is the element that scrolls with you. */
  .pr-header {
    position: sticky;
    inset-block-start: 0;
    z-index: 40;
    background-image: linear-gradient(
      180deg,
      rgba(13, 9, 18, 0.96) 0%,
      rgba(13, 9, 18, 0.86) 68%,
      rgba(13, 9, 18, 0.72) 100%
    );
    backdrop-filter: blur(10px);
  }
  /* min-inline-size: 0 is load-bearing, not tidying. .pr-nav is a flex item of
   * .pr-header-inner, and a flex item's automatic minimum size is its content,
   * so the nav refused to shrink and pushed the header 18px past the viewport
   * at 320px — while .pr-nav-list's own `overflow-x: auto` sat there unused,
   * because the list was never actually narrower than its content. Measured at
   * 320px: 338 wide in a 320 box. Zero it and the list scrolls as designed. */
  .pr-nav {
    position: static;
    min-inline-size: 0;
    background-image: none;
    backdrop-filter: none;
  }

  .pr-header-inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--pr-space-2, 0.5rem) var(--pr-space-4, 1rem);
    max-inline-size: var(--pr-shell-max, 78rem);
    margin-inline: auto;
    padding: var(--pr-space-2, 0.5rem) var(--pr-gutter, 1rem);
  }

  .pr-wordmark {
    display: inline-flex;
    flex-direction: column;
    /* Measured 39.13px — just under the floor, on every route. It is the
     * home link, so it is a real target, not prose. */
    justify-content: center;
    min-block-size: var(--pr-tap, 44px);
    font-family: var(--pr-font-display, Georgia, serif);
    font-size: var(--pr-size-md, 1.0625rem);
    line-height: var(--pr-lead-snug, 1.35);
    letter-spacing: var(--pr-track-tight, -0.01em);
    text-decoration: none;
    color: var(--pr-text-strong, #F2F4F8);
  }
  .pr-wordmark-line { display: block; }
  .pr-wordmark-line + .pr-wordmark-line {
    font-family: var(--pr-font-sans, system-ui, sans-serif);
    font-size: var(--pr-size-2xs, 0.75rem);
    letter-spacing: var(--pr-track-caps, 0.16em);
    text-transform: uppercase;
    color: var(--pr-text, #A2AFC3);
  }

  .pr-nav-item { display: inline-flex; }


  /* --- 9.3 #threshold — hero, gate, lexicon ----------------------------- */


  /* The scrim was a separate .pr-hero__scrim element that the markup never
   * rendered, so overlay text sat on bare artwork. It becomes a pseudo-element
   * on the art itself — a GRADIENT, never a flat scrim, with the top 46% clear
   * so the face and torso stay unobscured. */
  .pr-hero-art {
    position: relative;
    isolation: isolate;
  }
  .pr-hero-art::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background-image: var(--pr-overlay-hero,
      linear-gradient(180deg, rgba(13, 9, 18, 0) 46%, rgba(13, 9, 18, 0.94) 100%));
  }
  @media (min-width: 60rem) {
    .pr-hero-art::after {
      background-image: var(--pr-overlay-hero-desktop,
        linear-gradient(90deg, rgba(13, 9, 18, 0.97) 0%, rgba(13, 9, 18, 0) 86%));
    }
  }


  .pr-hero-cta-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--pr-space-3, 0.75rem);
    margin-block-start: var(--pr-space-5, 1.5rem);
  }


  /* --- 9.4 Section headings, shared text --------------------------------- */

  .pr-section-heading {
    margin: 0 0 var(--pr-space-4, 1rem);
    font-family: var(--pr-font-display, Georgia, serif);
    font-size: var(--pr-size-2xl, 1.75rem);
    line-height: var(--pr-lead-tight, 1.18);
    letter-spacing: var(--pr-track-display, -0.02em);
    color: var(--pr-text-strong, #F2F4F8);
    text-wrap: balance;
  }
  @media (min-width: 48rem) {
    .pr-section-heading { font-size: var(--pr-size-3xl, 2.125rem); }
  }
  /* A subordinate heading inside a section — smaller, still display. */
  @media (min-width: 48rem) {
  }

  .pr-subheading {
    margin: var(--pr-space-5, 1.5rem) 0 var(--pr-space-3, 0.75rem);
    font-family: var(--pr-font-sans, system-ui, sans-serif);
    font-size: var(--pr-size-sm, 0.875rem);
    font-weight: var(--pr-weight-medium, 500);
    letter-spacing: var(--pr-track-caps, 0.16em);
    text-transform: uppercase;
    color: var(--pr-text, #A2AFC3);
  }

  .pr-note,
  .pr-privacy {
    max-inline-size: var(--pr-measure, 66ch);
    font-size: var(--pr-size-sm, 0.875rem);
    color: var(--pr-text, #A2AFC3);
  }

  .pr-inline-link {
    color: var(--pr-text-strong, #F2F4F8);
    text-decoration: underline;
    text-underline-offset: 0.18em;
    text-decoration-thickness: from-font;
  }
  .pr-inline-link:hover { color: var(--pr-text-unlocked, #60CFC7); }

  /* An honest empty state. Content that is blocked on the artist renders as a
   * declared absence, not as a broken or blank region. Dashed, so it is not
   * mistaken for a card that failed to load. */
  .pr-slot-empty {
    display: block;
    padding: var(--pr-space-5, 1.5rem);
    border: var(--pr-border-width, 1px) dashed var(--pr-border-arcane, #7B5DC2);
    border-radius: var(--pr-radius-md, 6px);
    background-color: var(--pr-fill-locked, rgba(76, 51, 136, 0.22));
    font-size: var(--pr-size-sm, 0.875rem);
    color: var(--pr-text-locked, #A2AFC3);
  }


  /* --- 9.5 #signal ------------------------------------------------------- */

  .pr-signal { display: block; }


  /* --- 9.7 #music — the four track cards -------------------------------- */

  .pr-music { display: block; }
  /* Element badge. Neutral until she assigns an element — it must not imply a
   * reading of her method that she has not given. */
  .pr-card-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--pr-space-2, 0.5rem);
    margin-block-start: var(--pr-space-4, 1rem);
  }

  .pr-project { display: grid; gap: var(--pr-space-1, 0.25rem); }
  .pr-project-title {
    margin: 0;
    font-family: var(--pr-font-display, Georgia, serif);
    font-size: var(--pr-size-lg, 1.125rem);
    color: var(--pr-text-strong, #F2F4F8);
  }
  .pr-project-role,
  .pr-project-line {
    margin: 0;
    font-size: var(--pr-size-sm, 0.875rem);
    color: var(--pr-text, #A2AFC3);
  }

  /* --- 9.8 #bio --------------------------------------------------------- */

  .pr-bio { display: block; }


  .pr-proof-points { display: grid; gap: var(--pr-space-3, 0.75rem); margin: 0; padding: 0; list-style: none; }
  @media (min-width: 48rem) { .pr-proof-points { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

  /* --- 9.9 #shop (sealed) + #pros --------------------------------------- */

  .pr-shop,
  .pr-pros { display: block; }

  /* Sealed, not broken. Silhouettes with no product promises — no names, no
   * prices, no dates. The visually-hidden label inside each slot needs a
   * sighted equivalent, which is what the silhouette is. */
  /* .pr-armory-slot and its ::before silhouette are defined in §10.2 now, from
   * Claude Design session 05. The pseudo-element that used to draw the
   * silhouette here — a translucent Ash rectangle — was still painting
   * alongside the real `.pr-silhouette` element, as a pale block beside each
   * shape. Deleted, not restyled: two silhouettes per slot was never the
   * design, and the shape is markup now so it can differ per slot. */


  .pr-form { max-inline-size: 34rem; }

  /* The honeypot MUST be invisible. With no rule it rendered as a fully visible,
   * unlabelled text input that screen readers could not describe (it is
   * aria-hidden) and whose completion silently rejects the sender as a bot. */
  .pr-honeypot {
    position: absolute !important;
    inline-size: 1px !important;
    block-size: 1px !important;
    padding: 0 !important;
    border: 0 !important;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .pr-field-check { margin-block-end: var(--pr-space-4, 1rem); }
  .pr-checkbox {
    inline-size: 1.25rem;
    block-size: 1.25rem;
    margin: 0.18rem 0 0;
    accent-color: var(--pr-arcane, #4C3388);
    flex: 0 0 auto;
  }

  /* Error-with-message and success. The markup hosts both in one
   * role="status" aria-live element, so the state is a modifier on it. An error
   * colour alone is not an error state — there is a glyph and a message. */
  .pr-form-result {
    display: flex;
    align-items: flex-start;
    gap: var(--pr-space-2, 0.5rem);
    margin-block-start: var(--pr-space-3, 0.75rem);
    font-size: var(--pr-size-sm, 0.875rem);
    color: var(--pr-text, #A2AFC3);
  }
  .pr-form-result:empty { display: none; }
  .pr-form-result.is-error { color: var(--pr-text-warning, #BB7C77); }
  .pr-form-result.is-error::before { content: "\26A0"; line-height: var(--pr-lead-body, 1.6); }
  .pr-form-result.is-success { color: var(--pr-text-unlocked, #60CFC7); }
  .pr-form-result.is-success::before { content: "\2713"; line-height: var(--pr-lead-body, 1.6); }
  /* On the elevated Ink ground the message needs the Ink-tuned Ember. */
  #music .pr-form-result.is-error,
  #shop .pr-form-result.is-error { color: var(--pr-text-warning-ink, #BB7C77); }

  /* --- 9.10 #constellation + footer ------------------------------------- */

  .pr-links {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
    gap: var(--pr-space-2, 0.5rem);
    margin: 0 0 var(--pr-space-6, 2rem);
    padding: 0;
    list-style: none;
  }

  .pr-booking {
    padding-block-start: var(--pr-space-5, 1.5rem);
    border-block-start: var(--pr-border-width, 1px) solid
                        var(--pr-border-hairline, #3A2F52);
  }
  /* .pr-booking-contact dropped 2026-08-10 — /press renders -headline/-line/
   * -route/-close/-alt and never -contact. */
  .pr-booking-line,
  .pr-booking-close { margin: 0 0 var(--pr-space-2, 0.5rem); }
  .pr-booking-close { color: var(--pr-text, #A2AFC3); }

  .pr-footer {
    padding: var(--pr-space-6, 2rem) var(--pr-gutter, 1rem);
    border-block-start: var(--pr-border-width, 1px) solid
                        var(--pr-border-hairline, #3A2F52);
    background-color: var(--pr-surface-base, #0D0912);
  }
  .pr-copyright {
    margin: var(--pr-space-2, 0.5rem) 0 0;
    font-size: var(--pr-size-2xs, 0.75rem);
    color: var(--pr-text, #A2AFC3);
  }

  /* --- 9.11 Aliases for treatments the markup names differently ---------- */

  /* .pr-button-submit removed 2026-08-10. It was written as an alias so the
   * submit could not drift from the primary treatment — but contact.php:82
   * renders a plain .pr-button, which already IS the primary treatment, so the
   * alias only ever restated it. An alias nothing applies cannot keep anything
   * in sync; it just reads as a state that exists. */
}


/* =============================================================================
 * 10. CLAUDE DESIGN SESSIONS 03 + 05
 * =============================================================================
 * 03 — the featured track card (the player itself lives in §7).
 * 05 — the sealed Armory, and the Pros & Prose form states.
 *
 * Both sessions returned a full `.pr-button` treatment as incidental context
 * around what they were actually asked for. That is NOT integrated: the button
 * is shared by every section on the page, neither session was commissioned to
 * redesign it, and taking a component's styling from a session that only drew
 * it as scenery is how a page loses its coherence one session at a time. Only
 * their `.pr-icon` sizing inside a button is taken, and it already existed.
 *
 * Their token names are mapped onto the shipped vocabulary, not added beside it
 * — --pr-arcane-edge is --pr-border-arcane, --pr-rose-text is
 * --pr-text-selected, --pr-step-* are --pr-size-*, and so on. Two vocabularies
 * for one palette is the condition this file's namespace contract exists to
 * prevent.
 * -------------------------------------------------------------------------- */

@layer components {

  /* --- 10.1 The track card (session 03) ---------------------------------- */

  /* Sound Vault cards sit on Ink — the one place Ink is correct, because it is
   * a CARD surface and nothing composites over it. The page ground never
   * reaches Ink; see §1.3b. */
  #music .pr-card {
    background-color: var(--pr-ink, #292425);
    background-image: var(--pr-texture-strata, none);
    border-color: var(--pr-border-hairline, #3A2F52);
  }
  #music .pr-card:hover { background-color: #31292C; }

  /* .pr-card-element removed 2026-08-10, and the .pr-card[data-state="playing"]
   * / ="locked" rules that sat here removed 2026-08-13 — all of it
   * retired-quest debris, none of it a forthcoming component.
   *
   * `.pr-card-element` styled a track's "element" chip (the five chambers:
   * Fire, Water…); "playing" was the first-party player's now-playing rail and
   * "locked" was a track a visitor had not yet unlocked. All three concepts
   * died with the quest, and the ~120 rule blocks that expressed them moved to
   * _game/assets/quest.css on 2026-08-09. These did not, because their
   * selectors lead with `.pr-card`, which IS live — it is the release card.
   *
   * 🔴 THAT IS THE LESSON, AND IT IS WHY THIS COMMENT IS LONGER THAN THE RULES
   * IT REPLACES. The dead-rule ratchet in tests/test-prowess-content.php
   * reconciles CLASS names — it extracts `\.(pr-[A-Za-z0-9_-]+)` from the CSS
   * and looks for each in `class="…"` literals. An attribute selector is
   * invisible to it: `.pr-card[data-state="locked"]` reads as a use of
   * `.pr-card`, which is used on every release card, so eight rules that
   * nothing could ever match sat inside a green check for four days. The check
   * was not wrong about what it measures; it was read as measuring more than it
   * does.
   *
   * Nothing sets `data-state` anywhere outside `_game/` and the archived
   * `docs/design-sessions/*.output.html`. Verified as a POSITIVE probe rather
   * than an empty grep: the only live state-modifier vocabulary on this site is
   * §11's `.pr-form-result.is-error` / `.is-success`, which
   * assets/js/contact-form.js now actually sets.
   *
   * If a gated state ever does return, it returns with the quest, and
   * _game/assets/quest.css already holds the measured version of these rules. */

  .pr-card:focus-within { outline: var(--pr-focus-width, 2px) solid var(--pr-focus-ring, #F2F4F8); outline-offset: 3px; }

  .pr-card-cover { aspect-ratio: 1 / 1; inline-size: 100%; object-fit: cover; }

  /* The element badge is a capsule, because a capsule IS the shape of a badge —
   * it is the one place --pr-radius-pill survives the sharpened scale.
   *
   * Measured and deliberately left: the Arcane edge is 3.05:1 against the Ink
   * card it sits on, and 2.68:1 against its own violet fill. Only the first
   * number is the one 1.4.11 asks for — a boundary is judged against what
   * surrounds the component, not against its own interior — and the badge is a
   * label rather than a control, so its outline is not required to be
   * perceivable at all. The Lightning text on that fill is 12.23:1. Do not
   * lighten the edge to "fix" the inner number; it would cost the outer one. */

  .pr-card-actions { display: flex; flex-wrap: wrap; gap: var(--pr-space-2, 0.5rem); margin-block-start: auto; padding-block-start: var(--pr-space-2, 0.5rem); list-style: none; }
  .pr-card-actions .pr-button { flex: 1 1 auto; }


  /* --- 10.2 The Armory, sealed (session 05) ------------------------------ */

  /* "Not yet" has to read as intentional, not broken. That is bought by the
   * slot being a FULL-WEIGHT card — strata, border, hover, focus ring, full
   * strength Ash — rather than a dashed placeholder, and by nothing being
   * dimmed with opacity. The dashed border the scaffold used said "unfinished";
   * a solid one says "shut". */

  /* Gradient, never a flat scrim — the silhouette sinks into the ground rather
   * than sitting on it, so there is something to look at and nothing to read.
   * That is the whole trick: a shape with no edge makes no promise. */


  /* The seal. One saturated mark in the whole block, and the only place
   * Bloodstone appears at full strength. */

  /* --- 10.3 Pros & Prose form states (session 05) ------------------------ */

  /* A reward being offered, never a wall. The error state carries a MESSAGE,
   * not just a colour — an error colour alone is not an error state. That
   * principle is kept; the rules that expressed it here were removed
   * 2026-08-13.
   *
   * Session 05 gave `.pr-form-result` a `[data-state="error"|"success"]`
   * vocabulary and an inner `.pr-icon` span. The artist rebuild independently
   * gave the SAME element a `.is-error` / `.is-success` vocabulary in §11,
   * with the glyph as a `::before` rather than a span, plus the `#music` /
   * `#shop` Ink-tuned overrides §10.3 never had. Two vocabularies for one
   * element is exactly what this file's namespace contract forbids, so the
   * older one goes: §11's is the one the markup and the script can reach, and
   * `.pr-icon` is rendered nowhere on the site. See §10.1 for why the ratchet
   * could not see either of these. */

  .pr-input:hover { border-color: var(--pr-lightning, #F2F4F8); }
  .pr-input[aria-invalid="true"] {
    border-color: var(--pr-border-warning, #BB7C77);
    border-width: var(--pr-border-width-thick, 2px);
  }
}

/* =============================================================================
 * 11. THE ARTIST SITE  (2026-08-09)
 * =============================================================================
 * Everything the seven-page rebuild introduced: the release grid, the release
 * page, the video facades, shows, press, links, forms and the shared page
 * furniture. Written the day the quest was retired; §§1–10 above are unchanged
 * apart from the regions that left with it (see _game/assets/quest.css).
 *
 * Rules this section keeps, from CLAUDE.md:
 *   · `--pr-*` tokens ONLY. Never --color-*, --radius-*, --shadow-*, --text-*,
 *     --font-*, --leading-* or --tracking-*: those are Tailwind v4-owned, and an
 *     unlayered :root redefinition silently overrides every utility class.
 *   · EVERY var() carries a literal fallback. An undefined var() is dropped with
 *     no error and the element renders unstyled — the exact way the old contact
 *     page's submit button lost its colour to `var(--color-brand)`.
 *   · Dark-mode depth comes from --pr-surface-elevated, NOT from shadows.
 *   · Wide content scrolls inside its own container; the body never scrolls
 *     horizontally.
 *   · Buttons carry default / hover / active / disabled plus focus-visible, and
 *     hit a 44px minimum target. §5 already defines .pr-button; only the ghost
 *     variant is added here.
 * ========================================================================== */
@layer components {

  /* --- 11.1 Page furniture ------------------------------------------------ */
  /* 🔴 §2 BASE sets `p, li { max-inline-size: 62ch }`, which is right for prose
   * and wrong for every STRUCTURAL list on this site. It was silently clamping
   * the tracklist to ~620px on a 1280px page — the rows simply stopped halfway
   * across and read as a layout bug with no rule to blame. Reset it where the
   * <li> is a row rather than a sentence. */
  .pr-card-grid > li,
  .pr-track,
  .pr-show,
  .pr-link-row,
  .pr-quote,
  .pr-tag,
  .pr-stack-item,
  .pr-project-list > .pr-project,
  .pr-nav-item,
  .pr-footer-links > li { max-inline-size: none; }


  .pr-page-heading {
    margin: 0 0 var(--pr-space-3, 0.75rem);
    font-family: var(--pr-font-display, Georgia, serif);
    font-size: var(--pr-size-3xl, 2.25rem);
    line-height: var(--pr-lead-display, 1.02);
    letter-spacing: var(--pr-track-display, -0.02em);
    color: var(--pr-text-strong, #F2F4F8);
  }

  .pr-section-subheading {
    margin: var(--pr-space-7, 3rem) 0 var(--pr-space-4, 1rem);
    font-family: var(--pr-font-display, Georgia, serif);
    font-size: var(--pr-size-xl, 1.375rem);
    letter-spacing: var(--pr-track-tight, -0.01em);
    color: var(--pr-text-strong, #F2F4F8);
  }

  .pr-section-lede {
    max-inline-size: var(--pr-measure, 66ch);
    margin: 0 0 var(--pr-space-6, 2rem);
    font-size: var(--pr-size-lg, 1.125rem);
    line-height: var(--pr-lead-body, 1.6);
    color: var(--pr-text, #A2AFC3);
  }

  .pr-eyebrow {
    margin: 0 0 var(--pr-space-2, 0.5rem);
    font-size: var(--pr-size-2xs, 0.75rem);
    font-weight: var(--pr-weight-medium, 500);
    letter-spacing: var(--pr-track-caps, 0.16em);
    text-transform: uppercase;
    color: var(--pr-text, #A2AFC3);
  }

  .pr-section-more {
    margin: var(--pr-space-5, 1.5rem) 0 0;
    font-size: var(--pr-size-sm, 0.875rem);
    color: var(--pr-text, #A2AFC3);
  }

  /* An honest empty state reads as a decision, not as a hole: full-weight
   * surface, real border, normal type. A dashed grey box reads as broken. */
  .pr-empty {
    max-inline-size: var(--pr-measure, 66ch);
    margin: var(--pr-space-4, 1rem) 0 0;
    padding: var(--pr-space-4, 1rem);
    border: var(--pr-border-width, 1px) solid var(--pr-border-hairline, #3A2F52);
    border-radius: var(--pr-radius-md, 6px);
    background-color: var(--pr-surface-elevated, #171223);
    font-size: var(--pr-size-sm, 0.875rem);
    color: var(--pr-text, #A2AFC3);
  }

  .pr-plain-list {
    max-inline-size: var(--pr-measure, 66ch);
    margin: 0 0 var(--pr-space-5, 1.5rem);
    padding-inline-start: var(--pr-space-5, 1.5rem);
  }
  .pr-plain-list > li { margin-block-end: var(--pr-space-2, 0.5rem); }

  /* Ghost button — same box and same 44px target as .pr-button, no fill. Used
   * where two actions sit side by side and only one of them is the point. */
  .pr-button-ghost {
    background-color: transparent;
    border-color: var(--pr-border-arcane, #7B5DC2);
    color: var(--pr-text-strong, #F2F4F8);
  }
  .pr-button-ghost:hover {
    background-color: var(--pr-fill-available, rgba(76, 51, 136, 0.34));
    border-color: var(--pr-lightning, #F2F4F8);
  }
  .pr-button-ghost:active { transform: translateY(1px); }
  .pr-button-ghost[aria-disabled="true"],
  .pr-button-ghost:disabled {
    background-color: var(--pr-fill-disabled, rgba(162, 175, 195, 0.12));
    border-color: var(--pr-border-hairline, #3A2F52);
    color: var(--pr-text-locked, #A2AFC3);
    cursor: not-allowed;
    transform: none;
  }

  /* --- 11.2 Hero ---------------------------------------------------------- */

  .pr-hero-lede {
    max-inline-size: var(--pr-measure, 66ch);
    margin: var(--pr-space-3, 0.75rem) 0 var(--pr-space-5, 1.5rem);
    font-size: var(--pr-size-lg, 1.125rem);
    line-height: var(--pr-lead-body, 1.6);
    color: var(--pr-text, #A2AFC3);
  }

  /* --- 11.3 The card grid ------------------------------------------------- */

  /* auto-fill + minmax rather than a fixed column count: at 320px it is one
   * column without a media query, and it never produces a half-width orphan. */
  .pr-card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 13rem), 1fr));
    gap: var(--pr-space-4, 1rem);
    margin: 0 0 var(--pr-space-6, 2rem);
    padding: 0;
    list-style: none;
  }
  .pr-video-grid { grid-template-columns: repeat(auto-fill, minmax(min(100%, 19rem), 1fr)); }

  .pr-release-card { padding: 0; overflow: hidden; }

  .pr-release-link,
  .pr-video-facade,
  .pr-shop-link {
    display: flex;
    flex-direction: column;
    block-size: 100%;
    text-decoration: none;
    color: inherit;
  }
  .pr-release-link:focus-visible,
  .pr-video-facade:focus-visible,
  .pr-shop-link:focus-visible {
    outline: var(--pr-focus-width, 2px) solid var(--pr-focus-ring, #F2F4F8);
    outline-offset: var(--pr-focus-offset, 2px);
  }

  /* Square covers fill the tile. `cover` is safe HERE and only here, because the
   * source is 1:1 — nothing is cropped away. */
  .pr-card-cover {
    inline-size: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    background-color: var(--pr-surface-sunken, #070510);
  }
  /* 🔴 The two covers that are NOT square (1200×900 and 1200×1078). `contain`
   * inside the same square box letterboxes them instead of cropping. Cropping an
   * artist's cover art to fit a grid is an edit, and not one to make silently.
   * An attribute rather than a modifier class — see parts/release-card.php. */
  .pr-card-cover[data-fit="contain"] { object-fit: contain; }

  /* --- Playable single tiles ---------------------------------------------
   * A single whose Bandcamp id is known plays in place instead of navigating
   * out; parts/release-card.php puts data-pr-album on the tile's own <a> and
   * assets/js/prowess.js swaps that <a> for the frame. Albums and EPs are NOT
   * playable here — they link inward to /release/{slug}, which already carries
   * the full-width player.
   *
   * The positioning is scoped to the ATTRIBUTE rather than added to the shared
   * .pr-release-link rule, so a non-playable tile is untouched. Same shape as
   * .pr-card-cover[data-fit="contain"] directly above. */
  .pr-release-link[data-pr-album] { position: relative; }

  /* Mirrors .pr-video-play: a gradient, never a flat scrim, per the overlay
   * rule — and pointer-events:none so the badge never swallows the click that
   * the whole tile exists to receive. */
  .pr-release-play {
    position: absolute;
    inset-inline: 0;
    inset-block-start: 0;
    display: flex;
    align-items: flex-start;
    justify-content: flex-end;
    padding: var(--pr-space-3, 0.75rem);
    block-size: 45%;
    background-image: linear-gradient(0deg, rgba(13, 9, 18, 0) 0%, rgba(13, 9, 18, 0.72) 100%);
    font-size: var(--pr-size-lg, 1.125rem);
    color: var(--pr-lightning, #F2F4F8);
    pointer-events: none;
  }

  /* --- 11.3b Page scopes and inline spans -------------------------------- */
  /* These carry no visual weight of their own; they exist so a page can be
   * targeted without reaching for an element selector, and so the reconciliation
   * check has a rule to find. Each one does something real, however small. */
  .pr-release,
  .pr-press,
  .pr-contact,
  .pr-404 { display: block; }
  /* The 404 is the one page a lost visitor sees; give it room rather than
   * letting it sit tight under the header. */
  .pr-404 { padding-block-end: var(--pr-flow-breath, 8.25rem); }
  /* Track count on a card, and the "on <record>" clause in a press citation:
   * both are secondary to the thing beside them and must not compete with it. */
  .pr-card-tracks,
  .pr-quote-about { color: var(--pr-text, #A2AFC3); }
  /* 🔴 The two covers that are NOT square (1200×900, 1200×1078). `contain` inside
   * the same square box letterboxes them instead of cropping. Cropping an
   * artist's cover art to fit a grid is an edit, and not one to make silently. */

  .pr-cover-fallback {
    display: flex;
    align-items: center;
    justify-content: center;
    inline-size: 100%;
    aspect-ratio: 1 / 1;
    background-color: var(--pr-surface-arcane, #37206B);
    font-family: var(--pr-font-display, Georgia, serif);
    font-size: var(--pr-size-3xl, 2.25rem);
    color: var(--pr-text, #A2AFC3);
  }

  .pr-card-body {
    display: flex;
    flex-direction: column;
    gap: var(--pr-space-1, 0.25rem);
    padding: var(--pr-space-3, 0.75rem);
  }
  .pr-card-meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--pr-space-2, 0.5rem);
    font-size: var(--pr-size-2xs, 0.75rem);
    color: var(--pr-text, #A2AFC3);
  }
  .pr-card-year  { font-variant-numeric: tabular-nums; }
  .pr-card-type  { letter-spacing: var(--pr-track-caps, 0.16em); }
  .pr-card-out {
    margin-block-start: var(--pr-space-1, 0.25rem);
    font-size: var(--pr-size-2xs, 0.75rem);
    color: var(--pr-text-unlocked, #60CFC7);
  }

  /* --- 11.4 Latest release, on the home page ------------------------------ */

  .pr-signal-body {
    display: grid;
    gap: var(--pr-space-5, 1.5rem);
    align-items: start;
  }
  @media (min-width: 48rem) {
    .pr-signal-body { grid-template-columns: minmax(0, 18rem) minmax(0, 1fr); }
  }
  .pr-signal-cover {
    inline-size: 100%;
    max-inline-size: 22rem;
    block-size: auto;
    border-radius: var(--pr-radius-md, 6px);
  }
  .pr-signal-copy  { display: grid; gap: var(--pr-space-3, 0.75rem); }
  .pr-signal-meta  { margin: 0; font-size: var(--pr-size-sm, 0.875rem); color: var(--pr-text, #A2AFC3); }
  .pr-signal-blurb { margin: 0; max-inline-size: var(--pr-measure, 66ch); }

  /* --- 11.5 The release page ---------------------------------------------- */

  .pr-release-head {
    display: grid;
    gap: var(--pr-space-5, 1.5rem);
    align-items: start;
    margin-block-end: var(--pr-space-6, 2rem);
  }
  @media (min-width: 48rem) {
    .pr-release-head { grid-template-columns: minmax(0, 20rem) minmax(0, 1fr); }
  }
  .pr-release-cover {
    inline-size: 100%;
    block-size: auto;
    border-radius: var(--pr-radius-md, 6px);
    background-color: var(--pr-surface-sunken, #070510);
  }
  .pr-release-meta  { display: grid; gap: var(--pr-space-3, 0.75rem); }
  .pr-release-facts {
    margin: 0;
    font-size: var(--pr-size-sm, 0.875rem);
    color: var(--pr-text, #A2AFC3);
  }
  .pr-release-type { letter-spacing: var(--pr-track-caps, 0.16em); }
  .pr-release-editorial {
    margin: 0;
    max-inline-size: var(--pr-measure, 66ch);
    font-family: var(--pr-font-display, Georgia, serif);
    font-size: var(--pr-size-lg, 1.125rem);
    color: var(--pr-text-strong, #F2F4F8);
  }
  .pr-release-blurb,
  .pr-release-credits { margin: 0; max-inline-size: var(--pr-measure, 66ch); }
  .pr-release-credits { font-size: var(--pr-size-sm, 0.875rem); color: var(--pr-text, #A2AFC3); }
  /* A caveat about the record itself ("most tracks are longer cuts") is a fact,
   * not a warning — railed, not alarmed. */
  .pr-release-note {
    margin: 0;
    padding-inline-start: var(--pr-space-3, 0.75rem);
    border-inline-start: var(--pr-border-width-thick, 2px) solid var(--pr-border-arcane, #7B5DC2);
    font-size: var(--pr-size-sm, 0.875rem);
    color: var(--pr-text, #A2AFC3);
  }

  .pr-tracklist { margin: 0 0 var(--pr-space-6, 2rem); padding: 0; list-style: none; }
  .pr-track {
    display: grid;
    grid-template-columns: 2.5rem minmax(0, 1fr) auto;
    gap: var(--pr-space-2, 0.5rem) var(--pr-space-3, 0.75rem);
    align-items: baseline;
    padding: var(--pr-space-3, 0.75rem) 0;
    border-block-end: var(--pr-border-width, 1px) solid var(--pr-border-hairline, #3A2F52);
  }
  .pr-track-n {
    font-variant-numeric: tabular-nums;
    font-size: var(--pr-size-sm, 0.875rem);
    color: var(--pr-text, #A2AFC3);
  }
  .pr-track-title { color: var(--pr-text-strong, #F2F4F8); }
  .pr-track-time {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    font-size: var(--pr-size-sm, 0.875rem);
    color: var(--pr-text, #A2AFC3);
  }
  /* The feature credit belongs under the title, not beside it — it wraps, and a
   * three-name credit must not push the runtime off the row. */
  .pr-track-feat {
    grid-column: 2 / 3;
    font-size: var(--pr-size-sm, 0.875rem);
    color: var(--pr-text, #A2AFC3);
  }

  .pr-tag-list {
    display: flex; flex-wrap: wrap;
    gap: var(--pr-space-2, 0.5rem);
    margin: 0 0 var(--pr-space-6, 2rem);
    padding: 0; list-style: none;
  }
  .pr-tag {
    padding: var(--pr-space-1, 0.25rem) var(--pr-space-3, 0.75rem);
    border: var(--pr-border-width, 1px) solid var(--pr-border-hairline, #3A2F52);
    border-radius: var(--pr-radius-pill, 999px);
    font-size: var(--pr-size-2xs, 0.75rem);
    color: var(--pr-text, #A2AFC3);
  }

  .pr-upcoming { margin-block-end: var(--pr-space-4, 1rem); }
  .pr-upcoming-title {
    font-family: var(--pr-font-display, Georgia, serif);
    font-size: var(--pr-size-lg, 1.125rem);
    color: var(--pr-text-strong, #F2F4F8);
  }
  .pr-upcoming-with  { margin-inline-start: var(--pr-space-2, 0.5rem); font-size: var(--pr-size-sm, 0.875rem); color: var(--pr-text, #A2AFC3); }
  .pr-upcoming-blurb { margin: var(--pr-space-1, 0.25rem) 0 0; max-inline-size: var(--pr-measure, 66ch); font-size: var(--pr-size-sm, 0.875rem); }

  /* --- 11.6 Video facades -------------------------------------------------- */

  .pr-video-card { padding: 0; overflow: hidden; position: relative; }
  .pr-video-facade { position: relative; }
  .pr-video-thumb {
    inline-size: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    background-color: var(--pr-surface-sunken, #070510);
  }
  /* The play badge sits on a GRADIENT, never a flat scrim — a flat wash over a
   * thumbnail reads as a disabled state. */
  /* Pinned to the THUMBNAIL (top of the facade, 16:9), not the whole card:
   * anchored at the card's bottom it washed the title in a dark gradient and
   * put the ▶ under the text — subtle on a violet ground, obvious on a light one. */
  .pr-video-play {
    position: absolute;
    inset-inline: 0;
    inset-block-start: 0;
    display: flex;
    align-items: flex-end;
    justify-content: flex-start;
    padding: var(--pr-space-3, 0.75rem);
    aspect-ratio: 16 / 9;
    background-image: linear-gradient(180deg, rgba(13, 9, 18, 0) 45%, rgba(13, 9, 18, 0.78) 100%);
    font-size: var(--pr-size-xl, 1.375rem);
    color: var(--pr-lightning, #F2F4F8);
    pointer-events: none;
  }
  .pr-video-body { display: grid; gap: var(--pr-space-1, 0.25rem); padding: var(--pr-space-3, 0.75rem); }
  .pr-video-note {
    margin: 0;
    padding: 0 var(--pr-space-3, 0.75rem) var(--pr-space-3, 0.75rem);
    font-size: var(--pr-size-sm, 0.875rem);
    color: var(--pr-text, #A2AFC3);
  }
  .pr-video-frame {
    inline-size: 100%;
    aspect-ratio: 16 / 9;
    border: 0;
    display: block;
  }

  /* --- 11.6b Bandcamp preview facade --------------------------------------- */
  /*
   * Same contract as the video facades above: what ships is a button, and the
   * player replaces it on click. Depth here comes from --pr-surface-raised, not
   * from a shadow — this is a dark theme, and a shadow on a dark ground reads as
   * smudge rather than elevation.
   */

  .pr-player { margin: 0 0 var(--pr-space-6, 2rem); }

  .pr-player-facade {
    display: flex;
    align-items: center;
    gap: var(--pr-space-4, 1rem);
    /* Comfortably past the 44px touch minimum; this is a real control. */
    min-block-size: 3.5rem;
    padding: var(--pr-space-3, 0.75rem) var(--pr-space-4, 1rem);
    max-inline-size: 25rem;
    background-color: var(--pr-surface-raised, #1E1830);
    border: var(--pr-border-width, 1px) solid var(--pr-border-hairline, #3A2F52);
    border-radius: var(--pr-radius-sm, 2px);
    color: var(--pr-text-strong, #F2F4F8);
    text-decoration: none;
  }

  .pr-player-facade:hover,
  .pr-player-facade:focus-visible {
    border-color: var(--pr-dragon-rose, #B84F6A);
  }

  .pr-player-play {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Icon box tracks the label's line-height rather than a magic number. */
    inline-size: 2rem;
    block-size: 2rem;
    border-radius: 50%;
    background-color: var(--pr-dragon-rose, #B84F6A);
    color: var(--pr-lightning, #F2F4F8);
  }

  .pr-player-body { display: grid; gap: var(--pr-space-1, 0.25rem); }
  .pr-player-label { color: var(--pr-text-strong, #F2F4F8); }
  .pr-player-note {
    font-size: var(--pr-size-sm, 0.875rem);
    color: var(--pr-text, #A2AFC3);
  }

  .pr-player-frame {
    inline-size: 100%;
    max-inline-size: 25rem;
    aspect-ratio: 1 / 1;
    border: 0;
    display: block;
    background-color: var(--pr-surface-sunken, #070510);
  }

  /* --- 11.7 Shows ---------------------------------------------------------- */

  .pr-show-list { margin: 0 0 var(--pr-space-6, 2rem); padding: 0; list-style: none; }
  .pr-show {
    display: grid;
    gap: var(--pr-space-1, 0.25rem) var(--pr-space-4, 1rem);
    padding: var(--pr-space-4, 1rem) 0;
    border-block-end: var(--pr-border-width, 1px) solid var(--pr-border-hairline, #3A2F52);
  }
  @media (min-width: 40rem) {
    .pr-show { grid-template-columns: 11rem minmax(0, 1fr); align-items: baseline; }
    .pr-show > :not(.pr-show-date) { grid-column: 2; }
  }
  .pr-show-date {
    font-variant-numeric: tabular-nums;
    font-size: var(--pr-size-sm, 0.875rem);
    color: var(--pr-text, #A2AFC3);
  }
  .pr-show-venue {
    font-family: var(--pr-font-display, Georgia, serif);
    font-size: var(--pr-size-lg, 1.125rem);
    color: var(--pr-text-strong, #F2F4F8);
  }
  .pr-show-with,
  .pr-show-note { font-size: var(--pr-size-sm, 0.875rem); color: var(--pr-text, #A2AFC3); }
  /* 🔴 A date sourced from a third party and not confirmed by her. Warning
   * colour, and the text says so too — never colour alone. */
  .pr-show-unconfirmed {
    font-size: var(--pr-size-sm, 0.875rem);
    color: var(--pr-text-warning, #BB7C77);
  }
  .pr-show-list-past .pr-show-venue { font-size: var(--pr-size-base, 1rem); }

  /* --- 11.8 Bio ------------------------------------------------------------ */

  .pr-bio-born-heading {
    margin: 0 0 var(--pr-space-2, 0.5rem);
    font-family: var(--pr-font-display, Georgia, serif);
    font-size: var(--pr-size-2xl, 1.75rem);
    letter-spacing: var(--pr-track-display, -0.02em);
    color: var(--pr-text-strong, #F2F4F8);
  }
  .pr-bio-born-body {
    max-inline-size: var(--pr-measure, 66ch);
    margin: 0 0 var(--pr-space-6, 2rem);
    font-size: var(--pr-size-lg, 1.125rem);
    color: var(--pr-text, #A2AFC3);
  }
  .pr-bio-prose > p {
    max-inline-size: var(--pr-measure, 66ch);
    margin: 0 0 var(--pr-space-4, 1rem);
    line-height: var(--pr-lead-body, 1.6);
  }

  .pr-stack {
    display: flex; flex-wrap: wrap;
    gap: var(--pr-space-2, 0.5rem);
    margin: var(--pr-space-5, 1.5rem) 0;
    padding: 0; list-style: none;
  }
  .pr-stack-item {
    padding: var(--pr-space-1, 0.25rem) var(--pr-space-3, 0.75rem);
    border: var(--pr-border-width, 1px) solid var(--pr-border-arcane, #7B5DC2);
    border-radius: var(--pr-radius-pill, 999px);
    font-size: var(--pr-size-2xs, 0.75rem);
    font-weight: var(--pr-weight-medium, 500);
    letter-spacing: var(--pr-track-caps, 0.16em);
    color: var(--pr-text-strong, #F2F4F8);
  }

  .pr-project-list { margin: 0 0 var(--pr-space-6, 2rem); padding: 0; list-style: none; }
  .pr-project-list > .pr-project {
    display: grid;
    gap: var(--pr-space-1, 0.25rem);
    padding: var(--pr-space-4, 1rem) 0;
    border-block-end: var(--pr-border-width, 1px) solid var(--pr-border-hairline, #3A2F52);
  }

  .pr-facts {
    display: grid;
    gap: var(--pr-space-2, 0.5rem) var(--pr-space-4, 1rem);
    margin: 0 0 var(--pr-space-6, 2rem);
  }
  @media (min-width: 40rem) {
    .pr-facts { grid-template-columns: 11rem minmax(0, 1fr); }
  }
  .pr-facts > dt {
    font-size: var(--pr-size-2xs, 0.75rem);
    font-weight: var(--pr-weight-medium, 500);
    letter-spacing: var(--pr-track-caps, 0.16em);
    text-transform: uppercase;
    color: var(--pr-text, #A2AFC3);
  }
  .pr-facts > dd { margin: 0; color: var(--pr-text-strong, #F2F4F8); }

  /* --- 11.9 Press ---------------------------------------------------------- */

  .pr-quote-list { margin: 0 0 var(--pr-space-6, 2rem); padding: 0; list-style: none; }
  .pr-quote { margin-block-end: var(--pr-space-6, 2rem); }
  .pr-quote-body {
    margin: 0;
    padding-inline-start: var(--pr-space-4, 1rem);
    border-inline-start: var(--pr-border-width-thick, 2px) solid var(--pr-border-arcane, #7B5DC2);
    max-inline-size: var(--pr-measure, 66ch);
  }
  .pr-quote-body > p {
    margin: 0;
    font-family: var(--pr-font-display, Georgia, serif);
    font-size: var(--pr-size-lg, 1.125rem);
    line-height: var(--pr-lead-body, 1.6);
    color: var(--pr-text-strong, #F2F4F8);
  }
  .pr-quote-cite {
    display: flex; flex-wrap: wrap;
    gap: var(--pr-space-2, 0.5rem);
    margin: var(--pr-space-2, 0.5rem) 0 0;
    padding-inline-start: var(--pr-space-4, 1rem);
    font-size: var(--pr-size-sm, 0.875rem);
    color: var(--pr-text, #A2AFC3);
  }
  .pr-quote-score {
    padding-inline: var(--pr-space-2, 0.5rem);
    border: var(--pr-border-width, 1px) solid var(--pr-border-hairline, #3A2F52);
    border-radius: var(--pr-radius-pill, 999px);
    font-variant-numeric: tabular-nums;
  }
  .pr-quote-byline::before { content: "— "; }
  .pr-quote-date { margin-inline-start: auto; }

  .pr-booking { margin-block-end: var(--pr-space-7, 3rem); }
  .pr-booking-headline {
    margin: 0 0 var(--pr-space-1, 0.25rem);
    font-family: var(--pr-font-display, Georgia, serif);
    font-size: var(--pr-size-xl, 1.375rem);
    color: var(--pr-text-strong, #F2F4F8);
  }
  .pr-booking-route { margin: var(--pr-space-3, 0.75rem) 0; }
  .pr-booking-alt   { margin: var(--pr-space-2, 0.5rem) 0 0; font-size: var(--pr-size-sm, 0.875rem); color: var(--pr-text, #A2AFC3); }

  /* --- 11.10 Links + shop --------------------------------------------------- */

  .pr-link-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 14rem), 1fr));
    gap: var(--pr-space-3, 0.75rem);
    margin: 0 0 var(--pr-space-5, 1.5rem);
    padding: 0; list-style: none;
  }
  .pr-link-row > .pr-button { inline-size: 100%; }

  .pr-shop-grid { grid-template-columns: repeat(auto-fill, minmax(min(100%, 18rem), 1fr)); }
  .pr-shop-card .pr-shop-link { gap: var(--pr-space-2, 0.5rem); padding: var(--pr-space-5, 1.5rem); }
  .pr-shop-card .pr-card-body { padding: 0; font-size: var(--pr-size-sm, 0.875rem); color: var(--pr-text, #A2AFC3); }

  /* --- 11.11 Forms ---------------------------------------------------------- */

  .pr-field { display: grid; gap: var(--pr-space-1, 0.25rem); margin-block-end: var(--pr-space-4, 1rem); }
  .pr-label {
    font-size: var(--pr-size-2xs, 0.75rem);
    font-weight: var(--pr-weight-medium, 500);
    letter-spacing: var(--pr-track-caps, 0.16em);
    text-transform: uppercase;
    color: var(--pr-text, #A2AFC3);
  }
  /* 🔴 This block is LATER than §7's .pr-input and therefore wins every property
   * it declares. It deliberately does NOT declare `border` or a focus
   * `border-color`. It used to declare both, and that was a real defect: the
   * resting border resolved to --pr-border-hairline (#3A2F52) against
   * --pr-surface-sunken (#070510) = 1.64:1, so the only visible edge on every
   * contact field was a hairline that §1.5 states is DECORATIVE and "NEVER the
   * sole signal that a boundary exists". Leaving the border undeclared here
   * lets §7's Arcane border and Lightning focus border apply, which clear the
   * 3:1 border floor. The radius/padding/font refinements below are this
   * session's and are kept. Measured 2026-08-10. */
  .pr-input {
    inline-size: 100%;
    min-block-size: var(--pr-tap, 44px);
    padding: var(--pr-space-2, 0.5rem) var(--pr-space-3, 0.75rem);
    border-radius: var(--pr-radius-md, 6px);
    background-color: var(--pr-surface-sunken, #070510);
    color: var(--pr-text-strong, #F2F4F8);
    font: inherit;
  }
  .pr-input:focus-visible {
    outline: var(--pr-focus-width, 2px) solid var(--pr-focus-ring, #F2F4F8);
    outline-offset: var(--pr-focus-offset, 2px);
  }
  /* Error state carries a MESSAGE, not just a colour — the JS sets both. */
  .pr-input[aria-invalid="true"] { border-color: var(--pr-border-warning, #BB7C77); }
  .pr-input:disabled {
    background-color: var(--pr-fill-disabled, rgba(162, 175, 195, 0.12));
    color: var(--pr-text-locked, #A2AFC3);
    cursor: not-allowed;
  }
  .pr-textarea { min-block-size: 8rem; resize: vertical; }
  .pr-form { max-inline-size: 34rem; }
  .pr-form-result { margin-block-start: var(--pr-space-4, 1rem); }

  /* --- 11.12 Footer --------------------------------------------------------- */

  .pr-footer-nav { margin-block-end: var(--pr-space-3, 0.75rem); }
  .pr-footer-links {
    display: flex; flex-wrap: wrap;
    gap: var(--pr-space-2, 0.5rem) var(--pr-space-4, 1rem);
    margin: 0; padding: 0; list-style: none;
    font-size: var(--pr-size-sm, 0.875rem);
  }
  /* These are standalone navigation targets, not links inside a sentence, so the
   * 44px floor applies to them (WCAG 2.5.8 exempts inline-in-prose links, which
   * is why .pr-inline-link is NOT given a height globally — doing that would
   * break line flow everywhere it appears mid-paragraph). Measured at 18px tall
   * on every route before this. */
  .pr-footer-links > li > .pr-inline-link {
    display: inline-flex;
    align-items: center;
    min-block-size: var(--pr-tap, 44px);
  }
  .pr-copyright-pub { color: var(--pr-text, #A2AFC3); }
}

/* =============================================================================
 * 14. SITE STYLE SWITCHER + THEME HOOKS (2026-09-26)
 * The three looks live in themes.css ([data-theme] blocks, unlayered). This
 * section holds only what every theme shares: the switcher's structure and the
 * decorative hooks' DEFAULT — hidden — so a theme opts IN to each one.
 * ============================================================================= */
@layer components {

  .pr-theme-switch {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: var(--pr-space-2, 0.5rem) var(--pr-space-3, 0.75rem);
    margin: 0;
    padding: var(--pr-space-1, 0.25rem) var(--pr-gutter, 1rem);
    font-family: var(--pr-font-sans, system-ui, sans-serif);
    font-size: var(--pr-size-2xs, 0.75rem);
    letter-spacing: var(--pr-track-caps, 0.16em);
    text-transform: uppercase;
    color: var(--pr-text, #A2AFC3);
    background-color: var(--pr-surface-sunken, #070510);
    border-block-end: var(--pr-border-width, 1px) solid var(--pr-border-hairline, #3A2F52);
  }
  .pr-theme-switch-label { white-space: nowrap; }
  .pr-theme-switch-options { display: inline-flex; flex-wrap: wrap; gap: var(--pr-space-1, 0.25rem); }

  .pr-theme-switch-option {
    min-block-size: var(--pr-tap, 44px);
    min-inline-size: var(--pr-tap, 44px);
    padding: 0 var(--pr-space-3, 0.75rem);
    border: var(--pr-border-width, 1px) solid transparent;
    border-radius: var(--pr-radius-sm, 2px);
    background: transparent;
    color: var(--pr-text, #A2AFC3);
    font: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    cursor: pointer;
    transition: color var(--pr-dur-fast, 140ms) var(--pr-ease, ease),
                border-color var(--pr-dur-fast, 140ms) var(--pr-ease, ease);
  }
  .pr-theme-switch-option:hover { color: var(--pr-text-strong, #F2F4F8); border-color: var(--pr-border-hairline, #3A2F52); }
  .pr-theme-switch-option:active { transform: translateY(1px); }
  .pr-theme-switch-option:focus-visible {
    outline: var(--pr-focus-width, 2px) solid var(--pr-focus-ring, #F2F4F8);
    outline-offset: var(--pr-focus-offset, 2px);
  }
  .pr-theme-switch-option[aria-pressed="true"] {
    color: var(--pr-text-strong, #F2F4F8);
    border-color: var(--pr-text-strong, #F2F4F8);
    cursor: default;
  }
  .pr-theme-switch-option:disabled { opacity: 0.5; cursor: not-allowed; }

  /* Phones: the bar was 128px tall at 320 (label, then options wrapping onto
   * two rows). One small label line + all three options in one row = ~70px. */
  @media (max-width: 39.99rem) {
    .pr-theme-switch {
      display: grid;
      grid-template-columns: minmax(0, 1fr);
      gap: 0;
      padding-block: var(--pr-space-1, 0.25rem);
      text-align: center;
    }
    .pr-theme-switch-options {
      display: grid;
      grid-template-columns: repeat(3, minmax(0, 1fr));
      gap: var(--pr-space-1, 0.25rem);
    }
    .pr-theme-switch-option {
      min-inline-size: 0;
      padding-inline: var(--pr-space-1, 0.25rem);
      letter-spacing: 0.04em;
    }
  }

  /* Decorative hooks: off unless a theme turns them on. */
  .pr-hero-moon,
  .pr-hero-collage,
  .pr-ticker { display: none; }
  .pr-hero-collage-img { display: block; }

  .pr-hero-meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--pr-space-1, 0.25rem) var(--pr-space-4, 1rem);
    margin: 0 0 var(--pr-space-4, 1rem);
    padding: 0;
    list-style: none;
    font-family: var(--pr-font-sans, system-ui, sans-serif);
    font-size: var(--pr-size-2xs, 0.75rem);
    letter-spacing: var(--pr-track-caps, 0.16em);
    text-transform: uppercase;
    color: var(--pr-text, #A2AFC3);
  }
}
