:root {
  /* bridge theme tokens to legacy variables */
  --color-bg: var(--theme-color-bg, #f5f5f5);
  --color-nodes: var(--theme-color-node, #555555);
  /* COLOR = DIRECTION OF TRAVEL (Howell 2026-07-23): ORBITAL movement (the
     focus ring's nodes and the magnifier) wears the band's own gray one
     step darker — applyTheme derives it from the active band, so every
     volume follows. RADIAL movement (the parent vessel, the pyramid stars)
     keeps --color-nodes. The fallback below only fires if applyTheme never
     ran. */
  --color-orbital: var(--theme-color-orbital, #5f5e5e);
  /* On-orbital label ink. DEFAULT = the volume's own text color (Howell
     2026-07-24: no label is ever white outside the search page — the paper
     ink was a relic of the demo red). A volume may still declare
     --theme-color-orbital-label if its orbital goes dark enough to need it;
     search mode's own rules keep search white regardless. */
  --color-orbital-label: var(--theme-color-orbital-label, var(--color-text));
  --color-text: var(--theme-color-text, #111111);
  --color-band: var(--theme-color-band, #7a7979);
  --color-accent: var(--theme-color-accent, #1f6feb);
  --color-magnifier-stroke: var(--theme-color-magnifier-stroke, #000000);
  --focus-node-radius: var(--theme-radius-node, 12px);
  --focus-node-stroke: var(--theme-stroke-node, 1px);
  --focus-node-font: var(--theme-font-label, 'Montserrat'), sans-serif;
  --magnifier-font: var(--theme-font-magnifier, 'Montserrat'), sans-serif;
  --focus-label-transform: uppercase;
  --space-2xs: var(--theme-space-2xs, 4px);
  --space-xs: var(--theme-space-xs, 6px);
  --space-sm: var(--theme-space-sm, 8px);
  --space-md: var(--theme-space-md, 12px);
  --space-lg: var(--theme-space-lg, 16px);
  --space-xl: var(--theme-space-xl, 20px);
  --space-2xl: var(--theme-space-2xl, 24px);
  --radius-panel: var(--theme-radius-panel, 14px);
  --radius-card: var(--theme-radius-card, 10px);
  --motion-fast: var(--theme-motion-duration-fast, 120ms);
  --motion-medium: var(--theme-motion-duration-medium, 200ms);
  --motion-slow: var(--theme-motion-duration-slow, 320ms);
  --motion-ease: var(--theme-motion-ease, cubic-bezier(0.33, 1, 0.68, 1));
}

* { box-sizing: border-box; }

/* Lock the page into a fixed, non-scrollable surface. A scrollable page is
   what lets a mobile browser (e.g. DuckDuckGo/Android) treat a vertical scrub
   as page-scroll and toggle its toolbar mid-gesture — our whole navigation is
   vertical swiping, so the page must never scroll. touch-action must sit on
   the SVG itself (the element the finger lands on), not just body — it is not
   inherited. overscroll-behavior kills the rubber-band/pull effects. */
html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  overscroll-behavior: none;
  touch-action: none;
  /* Pin text auto-inflation OFF. Chrome-on-iOS "font boosts" a wide text block
     — inflating the RENDERED verse beyond its set px size — while our off-screen
     measure span (absolute, nowrap) escapes the boost, so rendered ran wider
     than measured and crowded the right edge. Only Chrome iOS boosts; Safari and
     DuckDuckGo don't, which is why it was Chrome-iOS-only (Howell 2026-07-22). */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  /* Android Chromium (Chrome, DuckDuckGo) flashes a translucent highlight box
     over any element with a click handler on touch — on an SVG group that
     means its whole bounding rect (seen over the parent button). Not a link,
     not navigation: suppress it, and the text-selection long-press cousin. */
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  user-select: none;
}
body {
  position: fixed;
  inset: 0;
  background: var(--color-bg);
  font-family: var(--theme-font-body, var(--focus-node-font, 'Montserrat')), sans-serif;
}

svg {
  width: 100vw;
  /* 100vh is the toolbar-HIDDEN height on browsers with dynamic chrome, which
     over-sizes the canvas and crops the bottom; dvh is the dynamic (current)
     height. JS then pins the exact size (main.js). vh first as the fallback
     for browsers without dvh. */
  height: 100vh;
  height: 100dvh;
  touch-action: none;
}
/* Tapping can also FOCUS the handler-bearing element in some browsers, which
   paints a persistent outline box until the next tap. The instrument has no
   keyboard navigation; nothing inside the canvas should ever show one. */
svg :focus,
svg :focus-visible {
  outline: none;
}

/* Portrait gate: the wheel's geometry only works in portrait, so on a touch
   device held landscape we cover it with a rotate prompt rather than render it
   broken. Gated on pointer:coarse so desktop (landscape by nature) is never
   affected. Hidden by default. */
#portrait-gate {
  display: none;
}
@media (orientation: landscape) and (pointer: coarse) {
  #portrait-gate {
    display: flex;
    position: fixed;
    inset: 0;
    z-index: 99999;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 5vmin;
    padding: 8vmin;
    box-sizing: border-box;
    background: var(--color-bg, #868686);
    color: var(--color-text, #000);
    font-family: var(--focus-node-font, 'Montserrat'), sans-serif;
    text-align: center;
  }
  #portrait-gate .portrait-gate-icon {
    width: 22vmin;
    height: 22vmin;
  }
  #portrait-gate .portrait-gate-text {
    margin: 0;
    font-size: 4.5vmin;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    max-width: 80vw;
  }
}

/* Desktop gate: the instrument is a phone instrument — a mouse-driven
   screen gets a card pointing the reader at their phone instead of a
   broken-feeling wheel. The honest desktop signal is a fine primary
   pointer THAT HOVERS (screen size lies: a narrowed desktop window is
   still a desktop; a large phone is still a phone). Touch devices never
   match. Hidden by default, pure CSS like the portrait gate above. */
#desktop-gate {
  display: none;
}
@media (pointer: fine) and (hover: hover) {
  #desktop-gate {
    display: flex;
    position: fixed;
    inset: 0;
    z-index: 99999;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 18px;
    padding: 48px;
    box-sizing: border-box;
    /* The gate is the BRAND'S card, not the volume's: catalog gray on
       every page, matching the gray baked into the QR's quiet zone. */
    background: #868686;
    color: #000;
    font-family: var(--focus-node-font, 'Montserrat'), sans-serif;
    text-align: center;
  }
  #desktop-gate .desktop-gate-qr {
    width: min(280px, 40vmin);
    height: auto;
    /* The QR is a bitmap of hard squares — keep its edges square when scaled */
    image-rendering: pixelated;
  }
  #desktop-gate .desktop-gate-title {
    margin: 0 0 24px;
    font-size: 34px;
    letter-spacing: 0.1em;
    max-width: 90vw;
  }
  /* Case is set in the markup, never transformed: the mark is "MMdM",
     and "delle" rides lowercase inside the full name. */
  #desktop-gate .desktop-gate-text {
    margin: 12px 0 0;
    font-size: 21px;
    letter-spacing: 0.08em;
  }
  #desktop-gate .desktop-gate-sub {
    margin: 0;
    font-size: 15px;
    letter-spacing: 0.06em;
    opacity: 0.75;
  }
  #desktop-gate .desktop-gate-mail {
    margin: 24px 0 0;
    font-size: 14px;
    letter-spacing: 0.06em;
  }
  #desktop-gate .desktop-gate-mail a {
    color: inherit;
    opacity: 0.75;
    text-decoration: none;
    border-bottom: 1px solid currentColor;
    padding-bottom: 1px;
  }
  #desktop-gate .desktop-gate-mail a:hover {
    opacity: 1;
  }
}

.copyright-notice {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  padding: calc(6px * var(--iframe-scale, 1)) calc(10px * var(--iframe-scale, 1));
  color: rgba(255, 255, 255, 0.85);
  font-family: var(--focus-node-font, 'Montserrat'), sans-serif;
  font-size: calc(11px * var(--iframe-scale, 1));
  text-align: center;
  letter-spacing: 0.3px;
  pointer-events: none;
  z-index: 300;
}

.detail-version {
  color: rgba(255, 255, 255, 0.45);
  font-family: var(--focus-node-font, 'Montserrat'), sans-serif;
  font-size: calc(11px * var(--iframe-scale, 1));
  text-transform: uppercase;
  letter-spacing: 0.4px;
  text-align: center;
  padding-top: var(--space-xs, 6px);
}

.focus-ring-node {
  fill: var(--color-orbital); /* orbital travel */
  stroke: none;
}

.focus-ring-label {
  fill: var(--color-text);
  font-family: var(--focus-node-font, 'Montserrat'), sans-serif;
  font-size: clamp(calc(14px * var(--iframe-scale, 1)), 1.6vmin, calc(26px * var(--iframe-scale, 1)));
  text-transform: var(--focus-label-transform, uppercase);
  pointer-events: none;
}

/* The version footnote (Howell 2026-07-20): a factory stamp at the end of
   the manufacturers chain. Text alone — no circle at all (Howell's ruling);
   the numerals hold the chain seat themselves, in the line-work black of
   the boot splash, smaller than a working node's label. The circle element
   still anchors the geometry but never paints; taps pass straight through. */
.focus-ring-node.is-placebo {
  display: none;
  pointer-events: none;
}

.focus-ring-label.is-placebo {
  fill: #000;
  font-size: clamp(calc(10px * var(--iframe-scale, 1)), 1.15vmin, calc(19px * var(--iframe-scale, 1)));
}

/* The dimension button (D.3) — the wireframe globe, parked in a corner.
   A neutral icon (it does not display the current value). Toggles the
   secondary strata; tap-sized for the thumb (Howell 2026-07-21). */
#dimension-button,
#search-button {
  position: fixed;
  right: calc(3% * var(--iframe-scale, 1));
  bottom: calc(13% * var(--iframe-scale, 1)); /* clear of the focus-ring band,
     lifted off it a touch further (Howell 2026-07-22) — still well inside
     the DSUA/CPUA boundaries */
  width: clamp(52px, 13vmin, 96px);
  height: clamp(52px, 13vmin, 96px);
  padding: 0;
  margin: 0;
  border: none;
  background: transparent;
  cursor: pointer;
  z-index: 260; /* above the strata layer so the globe stays tappable */
  opacity: 0.85;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  transition: opacity 0.15s ease, transform 0.15s ease;
}
#dimension-button[hidden],
#search-button[hidden] { display: none; }
#dimension-button svg,
#search-button svg {
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  user-select: none;
  /* The globe's line-work rides currentColor — dark grey, not ink: present
     when sought, quiet when not (Howell 2026-07-22, tuned by eye). */
  color: #363636;
}
#dimension-button:hover,
#search-button:hover { opacity: 1; }

/* ── Search mode: the same room under a different light ───────────────────
   The ground dims for close work (computed from the ACTIVE theme in
   search-mode.js, so every volume dims in its own hue) and the gold ring
   leaps forward against it — the nodes and their on-node labels are
   deliberately left alone, one token doing the work of a repaint.

   But everything styled DARK-ON-LIGHT vanishes on a dark ground, and a
   volume theme outranks a plain .class rule — so these carry
   [data-theme] to sit above the theme's own selectors. Inverted here: the
   completion labels and fan lines, the carriage, the hollow lens, and the
   dividers icon itself, which is the way OUT and must never disappear
   (Howell 2026-07-22). Pyramid NODES are left to the theme: gold stars on a
   dark ground read beautifully; the base rule below only catches volumes
   whose stars are dark-on-light. */
#search-button svg { transition: color 300ms ease; }
:root.search-mode #search-button svg { color: #cfcfcf; }
/* TEMPORARY (until Wilbur declares per-volume travel colors): the demo's
   brick-red orbital measures 1.0:1 against the search ground (the logo-disc
   blue) — invisible. Search keeps the gold for now. The CONTRACT constraint
   this proves: an orbital value must read on BOTH grounds, the theme bg AND
   the detail-sector color the search dims to. */
:root[data-theme].search-mode .focus-ring-node { fill: var(--color-nodes); }
:root[data-theme].search-mode .focus-ring-magnifier-circle { fill: var(--color-nodes); }
:root.search-mode .child-pyramid-node {
  fill: rgba(255, 255, 255, 0.14);
  stroke: rgba(255, 255, 255, 0.30);
}
.child-pyramid-label { transition: fill 300ms ease; }
:root[data-theme].search-mode .child-pyramid-label { fill: rgba(255, 255, 255, 0.92); }
/* The fan lines out to the completions — ink connectors, lit for the dark. */
/* The carriage sits on the GROUND (not on a node), so it must be lit. */
:root[data-theme].search-mode .search-carriage { fill: rgba(255, 255, 255, 0.92); }
/* The scope label — the parent button's words without its vessel: what the
   search is filtered to. A statement, never a control. */
.search-scope-label { pointer-events: none; }
:root[data-theme].search-mode .search-scope-label { fill: rgba(255, 255, 255, 0.92); }
/* The lens goes hollow while the chain turns — its dark ring would vanish,
   and the lodestar is the one point that must never be lost. */
:root[data-theme].search-mode .focus-ring-magnifier.rotating .focus-ring-magnifier-circle {
  stroke: rgba(255, 255, 255, 0.78);
}
/* The watermark: the pressed tool, ghosted in the volume's own node colour. */
.search-watermark {
  color: var(--color-nodes);
  pointer-events: none;
}
#dimension-button[aria-pressed="true"] { opacity: 1; transform: scale(1.08); }
#dimension-button:focus-visible { outline: 2px solid var(--color-nodes, #f2b41c); outline-offset: 3px; border-radius: 50%; }

/* The strata layer sits above the primary and its detail panel, below the
   dimension button. It is click-through except on its nodes, so taps on
   empty space still fall to the (inert) primary. */
#strata-layer {
  position: fixed;
  inset: 0;
  /* Above the primary's detail panel (200) so a front stratum covers the
     receded primary text; below the copyright strip (300). */
  z-index: 250;
  pointer-events: none;
  overflow: visible;
}

/* The secondary strata (D.3) — identical colors, band, and font to the
   primary strata (Howell 2026-07-21). Color placeholder retired. */
.secondary-strata-band {
  fill: none;
  stroke: var(--color-band);
  stroke-linecap: round;
  opacity: 1;
}
.secondary-strata-node {
  fill: var(--color-orbital); /* the chooser rings travel orbitally too */
  stroke: none;
  cursor: pointer;
  pointer-events: auto; /* the layer is click-through; its nodes are not */
}
/* The magnified language wears a 1px black stroke (Howell 2026-07-21). */
.secondary-strata-node.is-magnified {
  stroke: #000;
  stroke-width: 1;
}
/* While the ring rotates the lodestar is a HOLLOW lens: no fill, so the nodes
   stream through it, exactly as the primary magnifier empties (Howell). */
.secondary-strata-node.is-magnified.lens-empty {
  fill: none;
}
.secondary-strata-label {
  fill: var(--color-text);
  font-family: var(--focus-node-font, 'Montserrat'), sans-serif;
  font-size: clamp(calc(14px * var(--iframe-scale, 1)), 1.6vmin, calc(26px * var(--iframe-scale, 1)));
  /* Casing is owned by displayCase() in JS — it uppercases Latin but leaves
     Greek/Cyrillic/Hebrew in their proper form. A CSS text-transform:uppercase
     here re-uppercased the native scripts (Русский → РУССКИЙ) — removed. */
  pointer-events: none;
}
/* The magnified language's label is larger, as in the primary strata. Its
   anchoring (offset + start) is set in the view so the name spans the node
   weighted inward, off the left edge — like the catalog's unselected nodes. */
.secondary-strata-label.is-magnified {
  font-size: clamp(calc(21px * var(--iframe-scale, 1)), 2.4vmin, calc(39px * var(--iframe-scale, 1)));
  font-weight: 700;
}

.focus-ring-band {
  /* A stroked centreline now (sprocket chain: arc + straight tangents), not a
     filled annulus. stroke-width is set per-render to the 0.02r band width. */
  fill: none;
  stroke: var(--color-band);
  opacity: 1;
}

/* ── The boot overture's ink (the splash's rotation beat) ─────────────────
   The LIVE wheel performs in wireframe: hollow nodes with black outlines and
   the grey band held back, while the splash's thin arc line stands in for the
   band. Fan lines stay at full strength throughout — their stubs inside the
   hollow circles are the rough-draft look (Howell 2026-07-22); the arriving
   fills obliterate them. Per frame the splash drives --boot-paint 0→1 (the
   colour arriving) and --boot-ink 1→0 (the outlines retiring); the class
   comes off at the settle and these rules vanish with it. */
#app.boot-colorize .focus-ring-band { opacity: var(--boot-paint, 1); }
#app.boot-colorize .focus-ring-node {
  fill-opacity: var(--boot-paint, 1);
  stroke: #000;
  stroke-opacity: var(--boot-ink, 0);
  /* 1px, matching the drawn wireframe beads (the splash inks them at the
     node's COMPUTED stroke-width — the 1px default — so anything heavier
     visibly fattens the outlines at the handoff; Moto G caught it). */
  stroke-width: 1px;
}
#app.boot-colorize .child-pyramid-node { fill-opacity: var(--boot-paint, 1); }
#app.boot-colorize .focus-ring-magnifier-circle { fill-opacity: var(--boot-paint, 1); }

/* diagnostic band removed — was debug scaffolding */

.focus-ring-magnifier-circle {
  fill: var(--color-orbital); /* the lens is the orbital fixed point */
}

/* Labels seated ON orbital surfaces wear the orbital ink: centered node
   numerals/characters, and the magnifier's own label (which sits on the
   lens). The parent label — despite sharing the magnifier label's class —
   sits on the GROUND in its corner and keeps the ground's text color. */
.focus-ring-label.label-on-node { fill: var(--color-orbital-label); }
.focus-ring-magnifier-label { fill: var(--color-orbital-label); }
.focus-ring-parent-label {
  fill: var(--color-text);
  /* The corner label is TAPPABLE (its class family disables pointer-events;
     historically the disc sat under it so taps landed anyway — the split
     seat exposed the truth, Howell 2026-07-23). Its onclick is nulled when
     the seat is inert, so this never makes a dead label swallow taps. */
  pointer-events: auto;
}

/* The countries ring's parent seat wears the world globe — the corner
   instruments' grey, tap-sized by its hit disc. */
.world-glyph { color: #363636; }

/* The parent vessel moves data RADIALLY: it keeps the radial color (the
   historic node yellow), splitting from the magnifier class it shares. */
.focus-ring-parent-circle {
  fill: var(--color-nodes);
  stroke: var(--color-magnifier-stroke, #000);
  stroke-width: 1px;
}

.focus-ring-magnifier-label {
  fill: var(--color-text);
  font-family: var(--magnifier-font, 'Montserrat'), sans-serif;
  font-size: clamp(calc(21px * var(--iframe-scale, 1)), 2.4vmin, calc(39px * var(--iframe-scale, 1)));
  text-transform: var(--focus-label-transform, uppercase);
  pointer-events: none;
}

/* Calendar and bible parent-button labels arrive display-ready ("MMdM
   CATALOGO", "753 BC", "CAPITULUM XVI") — render them verbatim; the
   lowercase d in MMdM must survive. The catalog's parent labels still rely
   on the uppercase transform (mixed-case manufacturer data). */
:root[data-theme="calendar"] .focus-ring-parent-label,
:root[data-theme="bible"] .focus-ring-parent-label {
  text-transform: none;
}

.focus-ring-magnifier.rotating .focus-ring-magnifier-circle {
  fill: none;
  stroke: var(--color-magnifier-stroke, #000);
  stroke-width: 1px;
}

.focus-ring-magnifier.rotating .focus-ring-magnifier-label {
  display: none;
}

/* Child pyramid preview rendering */
.child-pyramid {
  pointer-events: none;
}

.child-pyramid-node {
  fill: rgba(0, 0, 0, 0.14);
  stroke: rgba(0, 0, 0, 0.32);
  stroke-width: 1px;
  pointer-events: auto;
  transition: fill var(--motion-fast, 120ms) var(--motion-ease, ease),
    stroke var(--motion-fast, 120ms) var(--motion-ease, ease);
}

.child-pyramid-node:hover {
  fill: rgba(0, 0, 0, 0.22);
  stroke: rgba(0, 0, 0, 0.46);
}

.child-pyramid-label {
  fill: var(--color-text);
  font-family: var(--focus-node-font, 'Montserrat'), sans-serif;
  font-size: clamp(calc(14px * var(--iframe-scale, 1)), 1.6vmin, calc(26px * var(--iframe-scale, 1)));
  /* Everything in the instrument is ALL CAPS except the d in MMdM (Howell);
     pyramid labels only looked capitalized before because abbreviations and
     numerals already were — full-named Favorites exposed the gap. */
  text-transform: uppercase;
  pointer-events: none;
}
/* The cartographer's stroke (2026-07-19 – 2026-07-25, in memoriam): a halo
   that carved Favorites' names a channel through the fan-line bundles.
   Retired with the fan lines themselves — the Tufte erasure (Howell:
   "if it's too busy for the dance, how is it not too busy when the
   dancers take a seat?"). */
/* The REAL pyramid labels (inside the live pyramid group) are tappable
   exactly like their circles — they carry data-index, and a tap on the word
   used to fall through to the background and get read as a ring near-miss
   (the iOS multi-tap gateway bug). Animation clones share the class but sit
   outside .child-pyramid, so they stay inert. */
.child-pyramid .child-pyramid-label {
  pointer-events: auto;
}

/* Fade child pyramid nodes/labels while the Focus Ring is rotating.
   Blur is applied via SVG filter attribute for iOS WebKit compatibility. */
.child-pyramid.is-rotating .child-pyramid-node,
.child-pyramid.is-rotating .child-pyramid-label {
  opacity: 0.35;
  transition: opacity 80ms linear;
}

/* Migration animation overlay — cloned nodes that travel from pyramid to ring */
.migration-animation-overlay {
  pointer-events: none;
}

.migration-node {
  will-change: transform;
}

@media (prefers-reduced-motion: reduce) {
  .migration-node {
    transition: none !important;
  }
}

/* Detail panel — full-screen transparent overlay.
   Arc-following text is positioned in viewport coordinates;
   cards centre themselves within the full-screen container. */
.detail-panel {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: transparent;
  border: none;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.35s ease-in;
  z-index: 200;
}

.detail-panel.detail-panel--visible {
  display: block;
  opacity: 1;
  /* pointer-events intentionally remain 'none' — the panel is a transparent
     display overlay.  Only specific interactive children opt-in below. */
}

.detail-content {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  /* Pass events through to the SVG; only opt-in children capture them. */
  pointer-events: none;
  color: rgba(255, 255, 255, 0.92);
  font-family: var(--theme-font-detail, var(--magnifier-font, 'Montserrat')), sans-serif;
  line-height: 1.4;
}

.detail-sector-content {
  color: rgba(255, 255, 255, 0.92);
}

/* ── Plain text fallback ─────────────────────────────────────────────── */
.detail-text {
  white-space: pre-wrap;
  padding: calc(16px * var(--iframe-scale, 1)) calc(24px * var(--iframe-scale, 1));
  font-size: calc(15px * var(--iframe-scale, 1));
}

/* ── Arc-following text layout ───────────────────────────────────────── */
/* Container covers the full detail-content area; lines are absolutely
   positioned using viewport coordinates from the line table. */
.detail-text--arc {
  position: absolute;
  inset: 0;
  white-space: nowrap;
  overflow: hidden;
}

/* Individual arc-positioned text lines */
.detail-text-line {
  display: block;
  position: absolute;
  white-space: nowrap;
  /* NEVER clip or ellipsise a verse line — the wrap is sized so the text fits,
     and a hair of overhang beats truncating scripture (Howell 2026-07-22). */
  overflow: visible;
}

/*
  Font-size tiers expressed as a fraction of SSd.
  --detail-SSd is set by TextDetailPlugin on the container element.
  line-height is unitless so it scales with computed font-size.
*/
.font-tier-1 { font-size: calc(10.5 / 100 * var(--detail-SSd, 650px)); line-height: 1.3;  }
.font-tier-2 { font-size: calc( 8.0 / 100 * var(--detail-SSd, 650px)); line-height: 1.3;  }
.font-tier-3 { font-size: calc( 6.0 / 100 * var(--detail-SSd, 650px)); line-height: 1.35; }
.font-tier-4 { font-size: calc( 4.5 / 100 * var(--detail-SSd, 650px)); line-height: 1.4;  }
.font-tier-5 { font-size: calc( 3.5 / 100 * var(--detail-SSd, 650px)); line-height: 1.4;  }
.font-tier-6 { font-size: calc( 3.0 / 100 * var(--detail-SSd, 650px)); line-height: 1.4;  }

/* W-6, the ruled substitution mark (Howell, bench 2026-07-27): a stood-in
   verse speaks in italic; a small upright right-aligned footer names the
   substitution in the reader's chosen language. Sizes in em ride the
   verse's uniform size. */
.detail-substituted--italic .detail-text-line {
  font-style: italic;
}
.detail-sub-footer {
  font-size: 0.75em; /* 1.5× the first sketch — Howell 2026-07-27 */
  letter-spacing: 0.12em;
  opacity: 0.65;
  font-style: normal; /* the notice speaks upright under the italic verse */
}

/* RTL language support */
.detail-text--arc[lang="he"],
.detail-text--arc[lang="ar"] {
  direction: rtl;
}
.detail-text--arc[lang="he"] .detail-text-line,
.detail-text--arc[lang="ar"] .detail-text-line {
  text-align: right;
}

/* ── Arc-following card layout ───────────────────────────────────────── */
/* Container identical to .detail-text--arc: full-layer, lines are spans
   absolutely positioned by the JS line table. */
.detail-card--arc {
  position: absolute;
  inset: 0;
  white-space: nowrap;
  overflow: hidden;
}

/* Title line — bold, inherits font-tier class for size */
.detail-card-title {
  font-weight: 700;
  color: rgba(255, 255, 255, 0.95);
}

/* Body subtitle line — muted */
.detail-card-body {
  color: rgba(255, 255, 255, 0.65);
}

/* Description lines — italic */
.detail-card-description {
  color: rgba(255, 255, 255, 0.88);
  font-style: italic;
}

/* ── The ephemeris day card (the wall calendar's day cell) ──────────── */
/* The weekday wears the parent button's font (Howell 2026-07-20) — same
   clamp as .focus-ring-magnifier-label. */
.detail-ephemeris-title {
  font-weight: 700;
  color: rgba(255, 255, 255, 0.95);
  font-size: clamp(calc(21px * var(--iframe-scale, 1)), 2.4vmin, calc(39px * var(--iframe-scale, 1)));
}

/* Feast days wear the print's red numeral color on the weekday. */
.detail-ephemeris-title--festivo {
  color: #d10000;
}

/* The numbers: twice the old tier-6 (Howell 2026-07-20). */
.detail-ephemeris-row {
  color: rgba(255, 255, 255, 0.88);
  letter-spacing: 0.5px;
  font-variant-numeric: tabular-nums;
  font-size: calc(6.0 / 100 * var(--detail-SSd, 650px));
  line-height: 1.4;
}

/* The dossier's inline media (the Twin 6 HP film, and photos to come) */
.detail-card-video {
  display: block;
  background: #000;
  border-radius: 2px;
  max-width: 100%;
}

/* ── Fallback card (no line table) ──────────────────────────────────── */
.detail-card {
  position: absolute;
  top: 10%;
  left: 5%;
  width: 90%;
  max-height: 80%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  text-align: left;
  overflow: hidden;
}

.detail-card > * + * {
  margin-top: var(--space-sm, 8px);
}

.detail-card .detail-card-title {
  font-weight: 700;
  font-size: calc(19px * var(--iframe-scale, 1));
  color: rgba(255, 255, 255, 0.95);
}

.detail-card .detail-card-body {
  font-size: calc(13px * var(--iframe-scale, 1));
  color: rgba(255, 255, 255, 0.65);
}

.detail-card .detail-card-description {
  margin-top: var(--space-md, 12px);
  font-size: calc(14px * var(--iframe-scale, 1));
  line-height: 1.65;
  color: rgba(255, 255, 255, 0.88);
  font-style: italic;
  text-align: left;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  pointer-events: auto;
  max-height: 100%;
}

.detail-card-image {
  width: calc(84px * var(--iframe-scale, 1));
  height: calc(84px * var(--iframe-scale, 1));
  object-fit: contain;
  border-radius: var(--radius-card, 10px);
  background: rgba(255, 255, 255, 0.08);
}
