/* ============================================================================
   HIEROPHANT — SHARED DESIGN LAYER
   ============================================================================
   Added 2026-09-04. Loaded LAST in <head>, after each page's inline <style>,
   so it wins the cascade at equal specificity.

   ! WHY THIS FILE EXISTS, AND IT IS THE STRUCTURAL FINDING OF THE AUDIT.
     `site/styles.css` has existed for months at 715 lines and NOT ONE of the 49
     pages links it. Every page carries its own inline <style> copy instead. So
     every design defect on this site is 49 defects, and every fix applied to a
     page leaves the other 48 loaded. That is OTR-1014's lesson at site scale:
     a wrong template is N bugs, not one.

     This layer is the single place a site-wide rule can now live. It does not
     restyle the site. It repairs what a measured audit proved is broken and
     adds the states nobody screenshots.

   MEASURED BASELINE (design-audit.js, 9 pages, 2026-09-04, self-test PASS):
     no-reduced-motion   9/9 pages     <- and this site is parallax-heavy
     display-too-heavy   8/9           <- see WAIVER below
     display-tracking    7/9           <- see WAIVER below
     no-focus-style      5/9           <- B3, the actual premium tell
     hero-no-cta         5/9
     layout-monotony     4/9
     body-contrast       2/9 CRITICAL  <- fixed here

   ============================================================================
   DECLARED WAIVER (visual-design-methodology-brain 17.4)
   ============================================================================
   The auditor reports `display-too-heavy` (weight 900) and
   `display-tracking-untuned` (0.100em) against thresholds measured from
   stripe.com and linear.app: h1 48-64px, weight 300-510, tracking -0.02em.

   NOT ACCEPTED, AND THE REASON IS THE FACE, NOT TASTE. Those references set
   grotesk UI faces. `--sacred` is Cinzel, an inscriptional serif drawn from
   Roman capital forms, where generous letter-spacing is the historically
   correct setting and negative tracking collapses the counters. Applying a
   -0.02em correction to Cinzel would produce a worse page that scores better.

   ! This is DECLARED, not silently exempted, which is the whole point of 17.4.
     The waiver covers `--sacred` (Cinzel) at EVERY size, which also accounts for
     `horseman-tracking` on 18 pages: that check exempts anything under 20px, so
     what it is counting is `.part-title` and `.arc-title`, both Cinzel. The face
     argument does not weaken at 24px. It does NOT cover `--sys` tracked labels,
     and the muted-lead pair (catalog 2.1) retired the worst of those.

   ! SECOND DECLARED WAIVER: `hero-no-trust-signal` (11 pages). That check passes
     when the hero contains a DIGIT, as a proxy for a number a buyer can trust.
     This canon numbers its chapters in Roman, so `Chapter III` contains no digit
     and never will. The proxy does not transfer to fiction. Refused, on the
     record, rather than satisfied by inserting an arabic numeral no reader wants.
   ============================================================================ */

:root {
  /* ---- CONTRAST REPAIR -----------------------------------------------------
     --muted was #5a5670, measuring 2.87:1 on --void. It carries the site's
     smallest text: card tags at 7px (56 instances on /codex alone), card
     subtitles at 10px, nav links, story meta. 2.87:1 at 7px is not a style,
     it is unreadable.

     ! HUE AND SATURATION ARE HELD EXACTLY. Only value is lifted, solved
       numerically for >= 5:1. HSV 249.2deg / 0.232 sat, unchanged. This is a
       correction, not a restyle: the colour is the same colour, lit properly. */
  --muted: #807ba0;                 /* was #5a5670 — 2.87:1 -> 5.03:1 */
  --muted-deep: #5a5670;            /* the original, for BORDERS and fills only */

  /* --blood at #8b2f2f measures 2.44:1 and is used as label text on side cards.
     Kept as-is for fills and rules; this is the text-safe sibling. */
  --blood-text: #bf5757;            /* 4.51:1 */

  /* A radius scale. The audit reported `single-radius`: every corner on the
     site was one value, which reads as a default rather than a system. */
  --r-sm: 2px;
  --r-md: 6px;
  --r-lg: 14px;

  --focus: var(--ember);
}

/* Elements that used --muted as a BORDER rather than text keep the dark value,
   so lifting the text colour does not brighten the site's rules and edges. */
.side-card, .story-nav a, .filter-btn { border-color: var(--edge); }

/* `.scene-break` set --edge (#242230, 1.29:1) as TEXT. It is a divider glyph,
   but it is still rendered type at 10px with 0.5em tracking. */
.prose .scene-break { color: var(--muted-deep); }
.side-card-type { color: var(--blood-text); }

/* ============================================================================
   B3 — THE STATES NOBODY SCREENSHOTS
   ============================================================================
   `no-focus-style` fired on 5 of 9 pages, and on the other 4 only because they
   had no focusable element in the sample. The site had no authored focus ring
   anywhere. Per methodology 21.1, this is the difference that reads as
   expensive: a page that handles its own edge states was operated by whoever
   built it.

   ! :focus-visible, not :focus. A mouse click must not ring the element; a Tab
     press must. Using :focus alone produces a ring on every click, which is the
     cheap version of this fix and looks like a bug. */

a:focus-visible,
button:focus-visible,
[tabindex]:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

/* The hero title and nav sit on photographic grounds where a 2px ember ring can
   vanish. A dark halo underneath guarantees the ring survives any background. */
.nav a:focus-visible,
.story-hero a:focus-visible {
  outline-offset: 4px;
  box-shadow: 0 0 0 6px rgba(8, 7, 10, .85);
}

/* Keyboard users must be able to reach the prose without tabbing the whole nav. */
.skip-link {
  position: absolute;
  left: 16px;
  top: -60px;
  z-index: 10001;
  padding: 10px 18px;
  background: var(--surface);
  border: 1px solid var(--ember);
  border-radius: var(--r-sm);
  color: var(--white);
  font-family: var(--sys);
  font-size: 11px;
  letter-spacing: .12em;
  text-transform: uppercase;
  text-decoration: none;
  transition: top .18s ease;
}
/* ! :focus AND :focus-visible. A skip link is the one control that must reveal
     on ANY focus, including programmatic focus, because it is reached before
     the browser has classified the interaction as keyboard-driven.

   ⚠️ HONEST NOTE ON HOW THIS WAS VERIFIED, because the first attempt reported a
     defect that was not one. Driving focus from an automated browser pane and
     reading the computed `top` returned -60px, which looks exactly like the rule
     failing. It was the instrument: `document.hasFocus()` was false, so :focus
     could not match even though `document.activeElement` was correct.

     What IS verified: the rule is present in the loaded sheet with the right
     selector and specificity (0,2,0 beats the base 0,1,0). What is NOT verified
     is the rendered state, and it cannot be from a pane that does not hold
     system focus. This needs a human Tab press in a real window. */
.skip-link:focus,
.skip-link:focus-visible { top: 16px; }

/* Hit targets. Nav links at 10px had no padding, so the clickable area was the
   glyph box. A pointer that misses by 3px is a state too. */
.nav-links a { padding: 6px 2px; }

/* ============================================================================
   REDUCED MOTION — DISSOLVE THE DEVICE, DO NOT HALF-RUN IT
   ============================================================================
   methodology 12.7. This fired on 9 of 9 pages and it is the most systemic
   finding in the audit, because this site's whole identity is scroll-driven:
   fixed blurred `.section-bg` plates that cross-fade, `.reveal` elements that
   translate in, a progress bar, and per-story parallax.

   ! THE FAILURE MODE IS NOT "TOO MUCH MOTION". It is that a reader with
     prefers-reduced-motion set currently gets `.reveal` elements stuck at
     opacity:0 if the observer is gated, or a half-speed version of a device
     that only means anything at full speed. Neither is a design. The correct
     behaviour is that the device is ABSENT and the page is complete without it. */

@media (prefers-reduced-motion: reduce) {

  /* Everything arrives already arrived. No exceptions, no partial states. */
  .reveal,
  .side-card,
  .story-hero-content,
  .part-header {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }

  /* The background plates stop cross-fading and settle to one steady ground.
     They do not fade at reduced speed; the mechanic is gone. */
  .section-bg {
    transition: none !important;
    opacity: 0 !important;
  }
  .section-bg:first-of-type { opacity: .28 !important; }

  /* Parallax translation is disagreement about motion. With motion reduced
     there is nothing to disagree about, so the layers simply register. */
  .story-hero-bg,
  .parallax,
  [data-parallax] {
    transform: none !important;
    transition: none !important;
  }

  .progress-bar { transition: none !important; }

  /* Catch-all for anything added later that forgets this block exists. */
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ============================================================================
   PRINT — a fiction site people will want on paper
   ============================================================================ */
@media print {
  .nav, .story-nav, .side-card-slot, .section-bg, .progress-bar,
  .skip-link, footer { display: none !important; }
  body { background: #fff; color: #111; font-size: 12pt; line-height: 1.6; }
  .prose { max-width: none; }
  .story-hero { min-height: auto; padding: 0 0 24px; }
  .story-hero-bg, .story-hero-overlay { display: none !important; }
  .story-hero h1 { color: #111; text-shadow: none; }
  a { color: #111; text-decoration: none; }
}

/* ============================================================================
   MODULE — THE HOLD RAIL   (fork of ENTERPRISE-MODULE-CATALOG 3.1, scored
   ⭐⭐⭐ FORK WHOLE, the highest-value import in the catalog)
   ============================================================================
   3.1's mechanic is a persistent margin rail that enumerates every figure with
   its source, and VOLUNTEERS which ones are weak, because self-flagging is the
   part that cannot be faked cheaply. On a commercial page it renders an honesty
   doctrine as a design feature.

   ⭐ Here it renders the canon's own two engines instead: the Iceberg (depth is
   controlled withholding) and perspectival factionalism (no omniscient
   narrator; every account is wrong in a characteristic way). The reader is told
   in the margin what kind of document they are holding and how far it can be
   trusted. That is lore delivered as apparatus, not decoration borrowed from a
   SaaS page.

   ⭐ THE GAP IS THE POINT (methodology §20.3). Unattested fields are drawn
   hatched and labelled, never omitted. A register that fills its own gaps is
   not a register, and in-world an incomplete Hold is characterisation. */

.hold-rail {
  max-width: 620px;
  margin: 0 auto 46px;
  padding: 20px 22px 16px;
  border: 1px solid var(--edge);
  border-left: 2px solid var(--ember);
  background: linear-gradient(180deg, rgba(24,22,31,.55), rgba(12,11,16,.30));
  border-radius: var(--r-sm);
  font-family: var(--sys);
}
.hold-head {
  font-size: 9px;
  letter-spacing: .34em;
  text-transform: uppercase;
  color: var(--ember);
  margin-bottom: 14px;
}
.hold-row {
  display: grid;
  grid-template-columns: 92px 1fr;
  gap: 14px;
  padding: 7px 0;
  border-top: 1px solid rgba(36,34,48,.75);
  align-items: baseline;
}
.hold-row:first-of-type { border-top: 0; }
/* A gap row is held slightly back so the attested rows carry the eye first.
   It must still be legible: an absence you cannot read is just a missing row,
   and the whole point of the gap device is that the absence is VISIBLE. */
.hold-row-gap .hold-key { color: var(--muted-deep); }
.hold-key {
  font-size: 8.5px;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--muted);
}
.hold-val {
  font-family: var(--prose);
  font-size: 1.02rem;
  line-height: 1.55;
  color: var(--light);
}

/* The gap device. Hatching, not a dash, because a dash reads as "none" while
   hatching reads as "this was looked for". */
.hold-gap {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--muted);
  font-style: italic;
  font-size: .95rem;
}
.hold-hatch {
  display: inline-block;
  width: 52px;
  height: 11px;
  border: 1px solid rgba(128,123,160,.45);
  border-radius: 1px;
  background-image: repeating-linear-gradient(
    -45deg, rgba(128,123,160,.40) 0 1px, transparent 1px 5px);
}
.hold-foot {
  margin-top: 14px;
  padding-top: 11px;
  border-top: 1px solid rgba(36,34,48,.75);
  font-size: 8.5px;
  letter-spacing: .12em;
  line-height: 1.8;
  color: var(--muted);
}

@media (min-width: 1280px) {
  /* On a wide viewport it becomes a true margin rail rather than a panel,
     sitting beside the opening of the text where a reader meets it before the
     first paragraph rather than instead of it. */
  .hold-rail {
    position: absolute;
    left: max(24px, calc(50% - 640px));
    width: 246px;
    max-width: none;
    margin: 0;
  }
  .hold-row { grid-template-columns: 1fr; gap: 4px; }
  .story-body { position: relative; }
}

/* ============================================================================
   MODULE — MUTED-LEAD PAIR  (catalog 2.1) replacing the tracked eyebrow
   ============================================================================
   The catalog lists "uppercase tracked eyebrow" as an ANTI-MODULE: it is one
   letter-spacing decision away from §20.1 horseman 1, and this site runs it on
   every page as `.story-hero-label` at 0.3em.

   2.1 gets the same two-level hierarchy out of COLOUR ALONE, so it cannot decay
   into fake elegance. Same size, same family, same measure; only colour changes.
   Applied here to the hero's bearing line, which keeps its meaning while losing
   the tracking. */
.story-hero-label {
  font-family: var(--prose);
  font-size: 1.06rem;
  font-style: italic;
  letter-spacing: 0;
  text-transform: none;
  color: rgba(160, 156, 176, .62);
}

/* ============================================================================
   AUDIT CLOSE-OUT — full 46-page sweep, 2026-09-04
   ============================================================================
   ⚠️ FIRST, AN INSTRUMENT TRAP WORTH MORE THAN ANY FIX BELOW.
   The sweep reported `no-reduced-motion` on 7 pages and `no-focus-style` on 5,
   all of them pages loaded earlier in the same session. Re-checked with a
   cache-buster: every one carries the link, the reduced-motion block and the
   focus-visible rules. **They were STALE CACHED LOADS, not defects.**
   design-audit.js run against iframes will invent MAJORs unless every src is
   cache-busted. Chasing those seven would have been an afternoon spent fixing
   nothing. */

/* ---- single-radius (21 pages) --------------------------------------------
   The finding is that only ONE distinct radius exists on the page, which reads
   as a default rather than a system. The scale was declared earlier in this
   file and then never applied to anything, which is its own small lesson. */
.side-card, .timeline-card, .card, .faction-card { border-radius: var(--r-md); }
.story-nav a, .filter-btn, .pill, .tag, .card-tag { border-radius: var(--r-sm); }
.hold-rail, .arc-section, .section-panel { border-radius: var(--r-lg); }

/* ---- display-line-height-loose (15 pages) --------------------------------
   Headings at 40px+ with line-height above 1.15. At display size a loose leading
   reads as a paragraph rather than a title, and the measured references sit at
   or below 1.0. Story heroes already set .95 and are untouched by this. */
h1, .arc-title, .page-title, .lore-title {
  line-height: 1.06;
}
@media (max-width: 640px) { h1, .arc-title, .page-title, .lore-title { line-height: 1.12; } }

/* ---- font-sprawl (atlas) --------------------------------------------------
   Five families measured: the house three plus Lucida Console and Arial, both
   arriving as FALLBACKS on elements that never declared a family. The fix is a
   declared default, not a narrower stack: an undeclared element is the actual
   defect and the stray face is only its symptom. */
body, div, span, li, td, button, input, select {
  font-family: var(--prose);
}
.nav-links a, .nav-logo, [class*="label"], [class*="tag"], [class*="meta"],
[class*="key"], code, kbd, pre { font-family: var(--sys); }
h1, h2, h3, .arc-title, .part-title, .card-title, .timeline-card-title,
.icon-label { font-family: var(--sacred); }

/* ---- hero-no-cta (27 pages) ----------------------------------------------
   An 80vh hero with no action asks the reader to scroll blind. The action here
   is diegetic and never addresses the audience (LORE-VIDEOS-SCOPING charter,
   carried site-wide): it names what the reader is about to do, in world. */
.hero-action {
  display: inline-block;
  margin-top: 26px;
  padding: 13px 30px;
  border: 1px solid var(--ember);
  border-radius: var(--r-sm);
  background: rgba(212, 160, 74, .07);
  color: var(--ember);
  font-family: var(--sys);
  font-size: 10px;
  letter-spacing: .26em;
  text-transform: uppercase;
  text-decoration: none;
  transition: background .22s ease, color .22s ease;
}
.hero-action:hover { background: var(--ember); color: var(--void); }

/* The atlas heading. It titles the map rather than sitting above it: the atlas
   is a full-bleed interactive surface, so the heading floats in its top-left
   with the same weight the map's own labels carry. */
.atlas-title {
  position: absolute;
  top: 84px;
  left: 32px;
  z-index: 20;
  margin: 0;
  font-family: var(--sacred);
  font-size: clamp(1.1rem, 2.4vw, 1.6rem);
  font-weight: 700;
  letter-spacing: .12em;
  color: var(--light);
  text-shadow: 0 2px 24px rgba(8,7,10,.9);
  pointer-events: none;
}
@media (max-width: 700px) { .atlas-title { top: 72px; left: 18px; font-size: 1rem; } }

/* ---- single-radius, second pass (12 lore pages) ---------------------------
   The first pass named classes from the story template. The lore pages use
   their own (`league-card`, `caste-card`, ...) and measured a single 4px value
   throughout. Matching on the SHAPE of the name rather than enumerating every
   page's vocabulary, so a page added later inherits the scale. */
[class$="-card"], [class*="-card "], .card { border-radius: var(--r-md); }
[class$="-grid"] > * { border-radius: var(--r-md); }
[class$="-label"], [class$="-tag"], [class$="-meta"], [class$="-btn"] { border-radius: var(--r-sm); }
[class$="-panel"], [class$="-section"] { border-radius: var(--r-lg); }

/* ---- atlas display-too-small + weak-type-contrast -------------------------
   The atlas heading landed at 25.6px against body text at 20px. That is not a
   type SCALE, it is two sizes that happen to differ, and at >=1100px the
   reference corpus puts a page's largest type at 40px or more. Raised so the
   map has a title rather than a caption. */
.atlas-title { font-size: clamp(1.6rem, 3.6vw, 2.9rem); }

/* ---- horseman-gradients (index, stories/index) ----------------------------
   The audit counted 17 gradients. Most are SCRIMS: `bg-overlay`, `nav` and
   `f-card-overlay` are legibility washes behind text over imagery, and removing
   them would make the page worse. Those are not the horseman.

   `f-card-bg` is: radial-gradient(circle at 50% 40%, rgba(212,160,74,.15),
   transparent 50%) on six faction cards. A decorative glow fill with no
   information in it. That IS §20.1 horseman 4.

   ⭐ Replaced with catalog module 2.6's mechanic: the same information with the
   ornament removed. A hairline and a flat wash instead of a glow. The catalog's
   claim is that this looks MORE expensive, not less, and the reason is that a
   gradient fill is what a tool reaches for when no decision was made. */
.f-card-bg {
  background-image: none !important;
  background-color: rgba(212, 160, 74, .035);
  border-top: 1px solid rgba(212, 160, 74, .22);
}
.f-card:hover .f-card-bg { background-color: rgba(212, 160, 74, .07); }

/* ============================================================================
   FINAL WAIVERS — declared with evidence, per methodology §17.4
   ============================================================================
   After the close-out pass the sweep of all 46 pages reports 0 CRITICAL. What
   remains is dominated by three checks that do not transfer to this property,
   and each is refused ON THE RECORD rather than satisfied by damaging the page.

   1. `layout-monotony` (28 pages) — ⭐ A PROXY MISS, VERIFIED.
      The check reads `section > :scope > div` and asks whether that element has
      more than one grid column. Measured directly: /lore/glass-coast has
      `league-grid` and `figure-grid`, /index has `media-grid` and
      `factions-grid`, all genuinely multi-column, all nested ONE LEVEL DEEPER
      than the selector reaches. The finding is about the selector's depth, not
      about the layouts.
      ⛔ Restructuring 28 pages' DOM to satisfy a proxy would be the definition
      of optimising the threshold instead of the thing.
      ⚠️ On story pages the prose column is single by design. Long-form reading
      wants one measure, and a grid there would be a defect, not a fix.

   2. `horseman-gradients` (2 pages) — SCRIMS, NOT ORNAMENT.
      17 gradients counted on the index. Six were a decorative radial glow on
      the faction cards and those ARE horseman 4; they were removed above and
      replaced with module 2.6's hairline. The remainder are `nav`, `bg-overlay`
      and `f-card-overlay`: legibility washes that put text on imagery. The
      check cannot distinguish a scrim from an ornament. Removing them would
      make the page objectively worse.

   3. `hero-no-trust-signal` (11 pages) — see the second waiver above. Roman
      numerals contain no digit.

   Plus the standing Cinzel waiver, which accounts for `display-too-heavy` (46),
   `display-tracking-untuned` (46) and `horseman-tracking` (18).

   ⚠️ WHAT IS NOT WAIVED AND IS SIMPLY DONE: body-contrast (2 CRITICAL -> 0),
   no-reduced-motion, no-focus-style, display-line-height-loose, font-sprawl,
   no-headings, hero-no-cta (27 -> 0), single-radius, display-too-small,
   weak-type-contrast. */

/* Last single-radius holdout: a page whose only rounded element is its nav. */
.divider-icon, .emblem, .icon-label { border-radius: var(--r-sm); }
