/* ---- Scroll rods (D18: custom-property geometry, aspect-correct caps,
   confined middle) ----
   D18 fixes D17's two defects:
   (1) #rodBottom's D17 offset `top: calc(100% - 88/300 * H)` put the box
       top at 100vh - 67.6 px with H = 230.4 px, so the roll's box rows
       88..211 mapped to box y 67.6..162.8 px and viewport y 100vh..100vh+95
       — the WHOLE roll sat below the viewport; the bottom rod was missing
       from the page. Correct expression: put the roll's LAST row (211) at
       the viewport bottom — 88 padding rows (rows 212..299) BELOW the roll
       are the box's overflow, not the roll's height. Written as
       `bottom: calc(-88/300 * H)` (equivalently `top: calc(100% - 212/300*H)`)
       so the SAME 88-row tail that D17 measured at the box TOP is now the
       box BOTTOM's overflow. Sign recovered.
   (2) D17's middle was the element's own full-width background: source
       columns 400..2000 (the roll) stretched across the MIDDLE REGION
       (W - 2*capW) but painted across the ENTIRE box, so under each cap
       it kept going — the roll's rounded end (source cols ~240..400) was
       painted under the cap's finial. The cap's pseudo-element paints on
       top, but the cap image is transparent left of the finial and above/
       below the thin rod, and there the middle's roll showed through
       (Jeff saw the cylinder continuing past the finial to the box edge).
       D18 confines the element's background to the region between the
       caps: horizontal padding of (capW - 4px) and
       background-clip / background-origin: content-box. Nothing is
       painted under the caps except the caps themselves; the 4px overlap
       is the cap's inner-edge columns 395..400 (opaque roll) covering the
       seam (D13's hairline-avoidance, carried over).
   (3) D17's cap width (155.6 px = 400/300 x H + 2 px) was D12's misread of
       D8's per-side ROW addition (200/300 x H = 153.6 px) as the cap width;
       D8 set the cap width to 400/300 x H = 307.2 px (correct aspect-neutral
       scale: 400 source cols at H/300 px per source col = same as the
       300 rows at H/300 px per source row vertically). D18 uses the DECISIONS
       block's custom properties: cap width = 4/3 x H = 307.2 px (desktop) /
       192 px (phone), so the 400-column cap renders at the same cols/px as
       rows/px — finials become round, not tall ovals (D17's ovals measured:
       left finial 338x258 px, ratio 1.310, vs 2.571 cols/px horizontal vs
       1.302 rows/px vertical — 97.5% mismatch, an artifact of the squish).
   The sheet width (80vw desktop / 108vw phone) and the row's per-side
   addition (2/3 H, unchanged from D8) are both custom properties now;
   only --rodH and --sheetW differ per breakpoint, so the ONE set of
   downstream expressions (rowAdd, capW, span, element width, top/bottom
   offsets, middle size/position, cap size/position) serves both.
   Load-bearing asset contract (D17, unchanged): rod.webp is the UNCROPPED
   2400x300 RGBA render (sha256 = 49da12306e5392d1b1fe298674a08e9e89612bdd
   f77994ae7765a94b91541480, identical to D12-D14; D15's crop was reverted
   in D17). Structure: transparent top rows 0..87, opaque roll rows 88..211
   (the cylinder), soft semi-transparent shadow fading out below row 211,
   transparent rows 212..299. The transparent top/bottom rows are LOAD-
   BEARING for the box's vertical offset (D17 FLUSH RULE, below) — do not
   crop, do not change background-size-y, do not change background-position-y.
   No background-color on the rod box (D17's removal stands — the rod's
   transparent regions must composite over the sheet / backdrop behind it;
   see CSS PITFALLS in NOTES.md).
   The image is never flipped: light comes from the upper left (D8). */
:root {
  /* D23: rod custom properties are :root-scoped (were #rodTop,#rodBottom in D22) so
     #parchmentSheet can read --rodH / --sheetW / --rodShadow / --rodShadowH.
     Everything else is derived from --rodH / --sheetW via calc() and is UNCHANGED from D22. */
  --rodH:     230.4px;
  --sheetW:   80vw;
  --rowAdd:   calc(2/3 * var(--rodH));
  --capW:     calc(4/3 * var(--rodH));
  --span:     calc(var(--sheetW) - 2 * var(--rowAdd));
  /* D23 NEW: the shadow is a gradient LAYER on #parchmentSheet (not on the rods),
     half as tall as D22's 88-row shadow, softer stops (0.62/0.45/0.20/0.0),
     starts exactly at the roll's bottom edge (124/300 * H from the viewport top). */
  --rodShadowH: calc(44 / 300 * var(--rodH));
  --rodShadow:  linear-gradient(to bottom,
                    rgba(0,0,0,0.62) 0%,
                    rgba(0,0,0,0.45) 35%,
                    rgba(0,0,0,0.20) 70%,
                    rgba(0,0,0,0)    100%);
  /* D18 custom properties (unchanged D22 formulas, :root-scoped D23):
     --rodH   base-case rod height (box height, px). Desktop 230.4 (D=96),
              phone 144 (D=60). The SAME H the box's height uses, written
              here against which ALL the offsets / sizes / positions below
              are derived.
     --sheetW base-case sheet width (element width, px). Desktop 80vw,
              phone 108vw (unchanged from D7 / D13 / D17).
     --rowAdd per-side row addition (the rod's horizontal overflow past the
              sheet on each side, the roll's rounded end's overhang) — 400/300
              x H / 2 = 2/3 x H. UNCHANGED from D8.
     --capW   cap element width = 4/3 x H (D8's correct aspect-neutral scale).
     --span   the middle's stretched width = sheetW - 2 x rowAdd, i.e. the
              region between the caps' inner edges (where the roll lives).
     --rodShadow / --rodShadowH: D23 tunables (see above). */
}
#rodTop, #rodBottom {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;   /* above the sheet (z-index 1) and fringe (z-index 2), below startScreen */

  height: var(--rodH);
  width:  calc(var(--sheetW) + 2 * var(--rowAdd));
  box-sizing: border-box;

  /* D18 confined middle: the element's own background paints the roll
     (source cols 400..2000), stretched across the region between the caps'
     inner edges. Two requirements drive the two formulas below:
       (a) CONFINE it to that region — nothing painted under the caps:
           horizontal padding of (capW - 4) + background-clip/origin
           content-box. Content box width = elemW - 2*(capW - 4)
             = (sheetW + 2*rowAdd) - 2*capW + 8
             = sheetW + 2*(2/3 H) - 2*(4/3 H) + 8 = sheetW - 4/3 H + 8
             = span + 8   (span = sheetW - 2*rowAdd = sheetW - 4/3 H).
           VERIFIED: desktop 1920 contentW = 1236.8 = span(1228.8) + 8;
           phone 412 contentW = 260.96 = span(252.96) + 8.
       (b) LAND col 400 at x = capW (4px into the content box = under the
           cap's inner overhang; the cap's opaque columns 395..400 overpaint
           it — D13's hairline-avoidance). With background-origin/clip =
           content-box, background-position-x = P places the image's left
           edge at (content-box-left = pad) + P. col 400's offset within
           the image = (400/2400) * W = W/6 (W = background-size-x). So
             pad + P + W/6 = capW  =>  P = 4 - W/6.
           With W = 1.5*span (the DECISIONS block's choice): W/6 = 0.25*span,
           P = 4 - 0.25*span. col 400 lands at pad + P + W/6
             = (capW - 4) + (4 - 0.25*span) + 0.25*span = capW.  EXACT, for
           every span — the 0.25*span term cancels, so col 400 is at capW
           regardless of the value of span. VERIFIED: desktop 1920
           col400_x = 307.2 = capW; phone 412 col400_x = 192.0 = capW.
     The middle's stretch (2400 source cols over 1.5*span) is wider than a
     square pixel-per-source-pixel would give — that is the roll's cylindrical
     LENGTH being drawn across the sheet, and D8/D12/D13/D17 all stretched it
     this way too. The cols/px = rows/px invariant applies to the CAPS only
     (where the 400-col finial must render at the same scale as the 300-row
     box height so the finial is round), which capW = 4/3 H below provides. */
/* D23: back to ONE background layer on #rodTop / #rodBottom — the middle image (rod-roll.webp)
     confined to the content box (the 4px overlap zone, x = capW-4 .. capW, where the roll's
     opaque columns overpaint the seam). The shadow layer is REMOVED from the rods (it was
     painting under the caps, over the dark backdrop past the sheet's torn edge — the defect
     Jeff flagged post-D22). The shadow now lives as a gradient LAYER on #parchmentSheet
     (see :root --rodShadow / --rodShadowH, and #parchmentSheet below), where the sheet's
     clip-path (the torn edges) clips it for free. The bottom rod gets NO shadow (its roll is
     flush at the viewport bottom; a shadow would fall below it, into nothing). */
  padding: 0 calc(var(--capW) - 4px);
  background-image: url("assets/rod-roll.webp?v=d23");
  background-repeat: no-repeat;
  background-origin: content-box;
  background-clip:   content-box;
  background-size:   calc(1.5 * var(--span)) 100%;
  background-position: calc(4px - 0.25 * var(--span)) 0;
}

/* ---- Game title (D28: "printed" on the top roll) ----
   One line of script centred on the roll's axis (the rod box's 50% row).
   Ink height <= half the roll diameter (D/2), verified in-session (STEP 2).
   --titleSize = font-size = 0.42 * D (D = 124/300 * H), so 40.0 px @1920 /
   25 px @412. No text-shadow, no background — ink on the roll only.
   Decorative duplicate of the page's (hidden) h1, so aria-hidden.
   D28: font is Dancing Script 700 (conventional capitals, real bold); the
   ornaments are tildes (~ U+007E) RENDERED IN THE TITLE FONT itself (no symbol
   fallback stack — D27's fleurons fell back to a hot-pepper and a pointing
   hand on Windows).
   fitTitle() in constitution.js shrinks the font-size if the line overflows
   the roll's visible span (never up); on phones it also repositions the
   element's left/right to the viewport-visible part of the rod. */
:root{
  --titleSize:   calc(0.42 * 124 / 300 * var(--rodH));
  --titleInk:    rgba(30, 18, 8, 0.88);
  --titleDy:     -0.04em;  /* optical centring nudge; D28 re-derives for Dancing Script */
}
#gameTitle{
  position: absolute; left: 0; right: 0; top: 50%;
  transform: translateY(calc(-50% + var(--titleDy)));
  z-index: 3;               /* above the ::before/::after caps */
  text-align: center; white-space: nowrap; pointer-events: none;
  user-select: none;
  font-family: "Dancing Script", cursive;
  font-weight: 700;
  font-size: var(--titleSize); line-height: 1;
  color: var(--titleInk); letter-spacing: 0.01em;
}
@font-face {
  font-family: "Dancing Script";
  font-style:  normal;
  font-weight: 700;
  font-display: swap;
  src: url("assets/fonts/DancingScript-700.woff2?v=d30") format("woff2");
}
@font-face {
  font-family: "Cinzel";
  font-style:  normal;
  font-weight: 600;
  font-display: swap;
  src: url("assets/fonts/Cinzel-600.woff2?v=d31") format("woff2");
}
/* ---- Credit line (D31: restored, centred on the BOTTOM roll) ----
   EB Garamond 500 (OFL, google/fonts). One line, same ink as the title.
   --creditSize  = clamp(11px, 0.72vw, 14px)  →  13.8 @1920, 11 px phones
   --creditInk   = var(--titleInk)            (rgba(30,18,8,0.88))
   --creditDy    = 0em (optical nudge; D32 = Jeff's to adjust)
   fitRodText() in constitution.js (shared with the title) shrinks the
   font-size to fit the roll's visible span; never grows past --creditSize. */
@font-face {
  font-family: "EB Garamond";
  font-style:  normal;
  font-weight: 500;
  font-display: swap;
  src: url("assets/fonts/EBGaramond-500.woff2?v=d31") format("woff2");
}
:root {
  --creditSize: clamp(11px, 0.72vw, 14px);
  --creditInk: var(--titleInk);
  --creditDy:  0em;
}
#creditLine {
  position: absolute; left: 0; right: 0; top: 50%;
  transform: translateY(calc(-50% + var(--creditDy)));
  z-index: 3;
  text-align: center; white-space: nowrap;
  pointer-events: none; user-select: none;
  font-family: "EB Garamond", serif;
  font-weight: 500;
  font-size: var(--creditSize); line-height: 1;
  color: var(--creditInk); letter-spacing: 0.01em; opacity: 0.9;
  /* D32: fade the credit out ONLY while the extended mode drawer covers it.
     The .covered class is toggled by updateCreditCover() in constitution.js
     (geometry: drawer rect intersects credit rect).  The .25s opacity ease is
     the ONLY motion added (reduced-motion users get an instant flip — the
     drawer's own top transition is also off under reduced motion). */
  transition: opacity .25s ease;
}
#creditLine.covered { opacity: 0; }
#gameTitle .orn{
  font-family: inherit; margin: 0 0.35em; opacity: 0.85;
}
#gameTitle .acr{
  font-family: "Cinzel", serif;
  font-weight: 600;
  font-size: 0.86em;
  letter-spacing: 0.06em;
  margin: -2px 0.04em 0 0.04em;
}

/* ---- The two caps: aspect-correct (D8's 4/3 H width, fixed D12-D17's
   2/3 H squish). Same asset, same 100% vertical size, no-repeat. Left
   cap = source cols 0..400 (cols 0..~99 are the transparent left of the
   finial, ~100..220 are the finial + thin rod, ~220..400 are the roll's
   rounded end). Right cap = cols 2000..2400 (mirror-symmetric). Image
   never flipped (light from upper-left, D8). background-size = 6 x capW
   (2400/400 = 6); left cap at position 0; right cap at position
   calc(-5 * capW) (col 2000 at its left edge, cols 2000..2400 shown). */
#rodTop::before, #rodTop::after,
#rodBottom::before, #rodBottom::after {
  content: "";
  position: absolute;
  top: 0;
  width: var(--capW);
  height: 100%;
  background-image: url("assets/rod-roll.webp?v=d23");
  background-repeat: no-repeat;
  background-size: calc(6 * var(--capW)) 100%;
}
#rodTop::before, #rodBottom::before {   /* left cap: source cols 0..400 */
  left: 0;
  background-position: 0 0;
}
#rodTop::after, #rodBottom::after {     /* right cap: cols 2000..2400 */
  right: 0;
  background-position: calc(-5 * var(--capW)) 0;
}

/* ---- D17 FLUSH RULE (unchanged from D17; D18 only fixes the sign on
   #rodBottom). The roll's top edge sits AT the viewport top (y = 0) and
   the bottom roll's bottom edge AT the viewport bottom (y = 100vh).
   Derived from the asset (D17): R_TOP = 88, R_BOT = 211 (alpha >= 240 at
   the roll's centre column x = 1200 of the 2400x300 asset; the soft
   shadow below row 211 stays under 240 alpha). Both rods use the SAME
   unflipped asset (light from upper-left, D8), so the SAME 88-row
   transparent tail (rows 212..299) is the box's overflow — for #rodTop it
   overflows the TOP (the roll's top 88 rows of transparent padding above
   the roll are the box's top overflow, box top at -88/300 * H so that the
   roll's first opaque row (row 88) lands at box y 0 = viewport y 0); for
   #rodBottom the SAME 88-row tail is the box's BOTTOM overflow (box bottom
   at 100vh + 88/300 * H, so the roll's last opaque row (row 211) lands at
   viewport y 100vh). The D17 bug was writing both as `top:` with a sign
   that assumed the row was at the box's top; for #rodBottom it is at the
   box's bottom. D18 expresses it as `top` (for #rodTop) / `bottom`
   (for #rodBottom) so the sign is carried by the property, not by a
   negative literal. */
#rodTop    { top:    calc(-88 / 300 * var(--rodH)); }  /* roll's first opaque row at y = 0 */
#rodBottom { bottom: calc(-88 / 300 * var(--rodH)); }  /* roll's last opaque row at y = 100vh */

/* ---- Phone: smaller roll. Only --rodH and --sheetW differ from desktop;
   everything else (rowAdd, capW, span, element width, top/bottom offsets,
   middle size/position, cap size/position) recomputes from the variables,
   so there is NOTHING else to override here. D13/D14's per-breakpoint
   literal size/position copies are GONE — they are superseded by the
   custom properties above (the point of the D18 refactor). */
@media (max-width: 700px) {
  :root {
    --rodH:   calc(60px * 2.4);   /* D=60px, H=144px (unchanged from D7-D17) */
    --sheetW: 108vw;             /* sheet width, phone (unchanged from D7)   */
  }
}  /* (D23: --rodH/--sheetW are :root-scoped; everything else recomputes from them.) */

/* =============================================================
   scroll.css — Session D7 (states-lab)
   D7: The sheet is SMOOTH VELLUM — calm, quiet, mottled tan.
       No crumple, no creases, no crack network.
       Mottle noise + age multiply + gentle cockle bump.
       Key light far upper-left; Standard view transform.

   Texture: assets/parchment.webp (2304x1296, cwebp q88)
     Generated by tools/render_parchment.py (Blender 4.3, Cycles CPU)
     Base colour: mottle (noise scale 7, ramp dark/light tan,
     80%/20% mix) × age (edge darken 0.62 at edge, 1.0 from 22% in).
     Normal: cockle (scale 1.8) + tooth (scale 900, 0.008 weight).
     No Voronoi. No creases. No craters.

   Torn edges: SVG clipPath (#parchmentClip) in index.html (D6).
   Fringe: SVG clipPath (#parchmentFringeClip) — pale tan lip, ~50% alpha.

   No compass shadow. No filter on #compass.

   Hidden while #gameScreen is shown: body.in-game → visibility:hidden.
   Z-order: parchmentLayer z 0, #startScreen z 1, header z 2.
   ============================================================= */

/* ---- Backdrop (unchanged from D1) ---- */
body {
  background: radial-gradient(
    ellipse at 50% 40%,
    #201811 0%,
    #14100b 55%,
    #0c0a07 100%
  ) fixed;
}
/* D49: stop iOS "pull to refresh" — the page itself is not the scroll
   container (the tray/map live inside a fixed-height flex layout, so the
   only overscroll that ever reached the top-of-viewport rubber band was
   the document's). overscroll-behavior:none on both html and body kills
   the gesture at its source; it cannot hide real (in-content) scroll
   because nothing on this page is meant to scroll the document. */
html, body {
  overscroll-behavior: none;
}

/* ---- Parchment layer: fixed, full-viewport, clips sheet + fringe + shadow ---- */
#parchmentLayer {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: clip;
  contain: paint;
  pointer-events: none;
}

/* ---- The parchment sheet (main texture) ----
   D23: the rod shadow is now a FIRST background layer on the sheet, listed
   BEFORE the parchment image (it paints on top of the parchment). Full-width
   gradient, height = var(--rodShadowH), starting exactly at the roll's bottom
   edge (roll's last opaque row 211 + 1 row padding = row 212/300... wait —
   the SHADOW layer starts at the roll's bottom edge = the FIRST row of the
   shadow in the render, row 212 → 124/300 * H from the viewport top, since
   the rod box's top = -88/300 * H (the roll's transparent top padding)).
   The sheet's clip-path (the torn edge) clips the gradient for free: it
   cannot exist where there is no sheet. */
/* D24: shadow moves from the sheet's background layers to a pseudo-element
   (#parchmentSheet::after at z-index 2) so it lies OVER ANY text (z-index 1)
   inserted inside the sheet. The sheet's background returns to parchment only. */
#parchmentSheet {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 80vw;
  height: 100%;
  background-image: url("assets/parchment.webp?v=d7");
  background-size:  cover;
  background-position: center;
  background-repeat: no-repeat;
  clip-path: url("#parchmentClip");
}
/* D24: roll shadow as a pseudo-element ABOVE the sheet's content (z 2).
   Same gradient, same height, same top offset as before (D23) — the only
   change is which element paints it and its z-index relative to #constitution
   (z 1, added by D24). Nothing about the rods changes. */
#parchmentSheet::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top:  calc(124 / 300 * var(--rodH));
  height: var(--rodShadowH);
  background: var(--rodShadow);
  z-index: 2;
  pointer-events: none;
}

/* ---- Fibre fringe: pale lip along the torn edge ----
   Slightly wider clip, pale tan colour, 50% alpha, under the sheet.
   D7: re-tinted to match the smooth vellum (RGB near 200,175,135). */
#parchmentFringe {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 80vw;
  height: 100%;
  background-color: rgb(200, 175, 135);
  opacity: 0.50;
  clip-path: url("#parchmentFringeClip");
}

/* ---- Keep #startScreen and header above the parchment ---- */
#startScreen { position: relative; z-index: 1; }
header       { position: relative; z-index: 2; }

/* ---- Phone: sheet wider than the viewport (torn edges off-screen) ---- */
@media (max-width: 700px) {
  #parchmentSheet,
  #parchmentFringe {
    width: 108vw;
  }
}

/* ---- Hidden while the game map is showing ---- */
body.in-game #parchmentLayer {
  visibility: hidden;
}
