/* ============================================================================
   Dicelords — "The Hinge"

   One number a day turns into a story: someone types "1" in Discord and a
   Wookiee gets set on fire. That causal link is the product, so the roll is the
   loudest mark on every page (see .stamp).

   The saga changes genre per arc — fantasy, then Star Wars, with eight more
   systems staged — so the identity is split in two:

     THE APPARATUS is permanent. Cool graphite chassis, condensed industrial
     display type, machined numerals. It is the instrument that outlives the
     fictions it records, and it never changes.

     THE FICTION is dressed per arc. `--signal` is the only hue that moves,
     driven by body[data-system]. Crit gold and fumble red do NOT move: they
     belong to the die, not to the world.

   Long-form narrative sits on a light STOCK band, full-bleed, so the page
   changes material where the story starts.
   ========================================================================== */

:root {
  /* --- The apparatus: permanent ------------------------------------------ */
  --chassis:    #0e1116;  /* page — cool graphite */
  --chassis-2:  #161b23;  /* raised panels */
  --chassis-3:  #1e242e;  /* hover wash */
  --rule:       #2a323d;  /* hairline */
  --rule-soft:  #1c222b;
  --text:       #dfe4ea;  /* primary text on chassis */
  --text-dim:   #8b95a3;  /* muted */
  --text-faint: #5d6673;

  /* --- The stock: where the story is read -------------------------------- */
  --stock:      #f2ede3;  /* warm paper band */
  --stock-2:    #e6dfd1;  /* stock rules / insets */
  --stock-ink:  #191714;  /* text on stock */
  --stock-dim:  #6b6459;

  /* --- The die: permanent, semantic -------------------------------------- */
  --crit:       #f5d547;  /* 20 — the impossible shot */
  --crit-deep:  #a8901c;
  --fumble:     #e33b2e;  /* 1 — the coolant line lets go */
  --fumble-deep:#8f2018;

  /* --- The fiction: dressed per arc -------------------------------------- */
  --signal:     #c9a227;  /* default: no system set (the fantasy arcs) */
  --signal-lo:  #6d5713;
  --signal-tint:rgba(201,162,39,0.10);

  /* --- Type -------------------------------------------------------------- */
  --display: "Big Shoulders Display", "Haettenschweiler", "Arial Narrow", sans-serif;
  --body:    "Newsreader", Georgia, "Times New Roman", serif;
  --data:    "Martian Mono", ui-monospace, "SF Mono", Menlo, monospace;

  --measure: 39rem;   /* ~66 characters — the reading measure for the stock band */
  --gutter:  clamp(18px, 4vw, 44px);
}

/* ============================================================================
   Per-arc dressing. One hue, one tint — nothing structural changes. Each is
   picked from the system's own world, not from a ramp: ion-drive blue for the
   Rebellion, bone-and-rust for the 41st millennium, and so on.
   ========================================================================== */
body[data-system="Star Wars"] {
  --signal: #4ea3d6; --signal-lo: #1d5878; --signal-tint: rgba(78,163,214,0.11);
}
body[data-system="Warhammer 40,000"] {
  --signal: #b8a67e; --signal-lo: #6a5c3a; --signal-tint: rgba(184,166,126,0.11);
}
body[data-system="Warhammer Fantasy Roleplay"] {
  --signal: #9c7b4f; --signal-lo: #5a4227; --signal-tint: rgba(156,123,79,0.11);
}
body[data-system="Cyberpunk RED"] {
  --signal: #e8489b; --signal-lo: #841f53; --signal-tint: rgba(232,72,155,0.12);
}
body[data-system="Shadowrun"] {
  --signal: #5fbf9b; --signal-lo: #276851; --signal-tint: rgba(95,191,155,0.11);
}
body[data-system="Call of Cthulhu"] {
  --signal: #7f9c6d; --signal-lo: #42553a; --signal-tint: rgba(127,156,109,0.11);
}
body[data-system="Vampire: The Masquerade"] {
  --signal: #b2455c; --signal-lo: #641f2f; --signal-tint: rgba(178,69,92,0.12);
}
body[data-system="Teenage Mutant Ninja Turtles & Other Strangeness"] {
  --signal: #7bbf3f; --signal-lo: #3d661d; --signal-tint: rgba(123,191,63,0.12);
}
body[data-system="Talislanta"] {
  --signal: #a684cf; --signal-lo: #543a72; --signal-tint: rgba(166,132,207,0.12);
}
body[data-system="Dungeons & Dragons"] {
  --signal: #c9a227; --signal-lo: #6d5713; --signal-tint: rgba(201,162,39,0.10);
}

/* Legacy aliases — older rules in this file still name the first palette.
   Kept so nothing renders unstyled while the site is brought across.

   These sit on `body`, not `:root`: the per-arc `--signal` override is set by
   body[data-system], and a var() resolved at :root would never see it. Aliasing
   at the root would silently pin every legacy rule to the default gold. */
body {
  --ink: var(--chassis); --ink-2: var(--chassis-2); --ink-3: var(--chassis-3);
  --vellum: var(--text); --vellum-dim: var(--text-dim);
  --gold: var(--signal); --gold-bright: var(--crit); --gold-deep: var(--signal-lo);
  --oxblood: var(--fumble); --oxblood-dk: var(--fumble-deep);
  --violet: #a684cf; --teal: #5fbf9b; --ember: #cf7434;
  --umber: #8c5a3a; --moss: #7d955c;
  --line: var(--rule);
  --plate: var(--stock); --plate-edge: var(--stock-2); --plate-ink: var(--stock-dim);
  --display-sc: var(--display);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font: 17px/1.65 var(--body);
  color: var(--text);
  background: var(--chassis);
  min-height: 100vh;
}

a { color: var(--signal); text-decoration: none; }
a:hover {
  color: var(--gold-bright);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--gold-deep);
}
a:focus-visible, button:focus-visible, summary:focus-visible,
input:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

.muted { color: var(--vellum-dim); }
.hint  { font-size: 0.85em; color: var(--vellum-dim); margin-top: 4px; }
li::marker { color: var(--gold-deep); }
main li { margin: 0.25em 0; }

/* --- Type scale -----------------------------------------------------------
   Display is condensed and set tight and large; it is signage, not prose. The
   utility face (--data) carries every label, tag and numeral, because on this
   site the numbers are the content. */
h1, h2, h3 {
  font-family: var(--display);
  font-weight: 700;
  line-height: 0.94;
  color: var(--text);
  letter-spacing: 0.005em;
  text-transform: uppercase;
}
h1 { font-size: clamp(2.6rem, 7vw, 4.6rem); margin: 0.1em 0 0.35em; }
h2 { font-size: clamp(1.6rem, 3.2vw, 2.2rem); margin: 2em 0 0.7em; }
h3 { font-size: 1.3rem; margin: 1.6em 0 0.4em; }

/* The eyebrow: a machined label above a section. Used only where it states a
   fact the reader needs (a count, a system, a date range) — never as decoration. */
.eyebrow {
  font-family: var(--data);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.19em;
  text-transform: uppercase;
  color: var(--text-faint);
  display: block;
  margin-bottom: 0.9em;
}
.eyebrow .on { color: var(--signal); }

/* ============================================================================
   Masthead
   ========================================================================== */
header.masthead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px 32px;
  flex-wrap: wrap;
  max-width: 1180px;
  margin: 0 auto;
  padding: 20px var(--gutter) 14px;
}
.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 0.5ch;
  font-family: var(--display);
  font-weight: 800;
  font-size: 1.6rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--text);
}
.wordmark:hover { color: var(--text); text-decoration: none; }
.wordmark-die { width: 1.05em; height: 1.05em; display: block; }
.wordmark-die polygon, .wordmark-die path {
  fill: none; stroke: var(--signal); stroke-width: 6;
  stroke-linejoin: round; stroke-linecap: round;
}
.wordmark-die .face { fill: var(--signal); stroke: none; }

header.masthead nav {
  display: flex;
  gap: 26px;
  font-family: var(--data);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
header.masthead nav a { color: var(--text-dim); }
header.masthead nav a:hover { color: var(--signal); text-decoration: none; }
header.masthead nav a[aria-current="page"] { color: var(--text); }

.masthead-rule {
  height: 1px;
  max-width: 1180px;
  margin: 0 auto;
  background: var(--rule);
}

main { max-width: 1180px; margin: 0 auto; padding: 40px var(--gutter) 96px; }

/* ============================================================================
   THE STAMP — the signature element.

   A d20 face carrying the value that caused what you are reading about. It is
   the same object everywhere it appears (chapter events, party roster, the
   graveyard, the ledger), only resized, so the connection between a number
   someone typed and a thing that happened to a character is legible at a
   glance. 20 and 1 are the only values that get their own colour, because
   those are the only two the die itself treats as special.
   ========================================================================== */
.stamp {
  --stamp-size: 46px;
  --stamp-ink: var(--text);
  position: relative;
  flex: none;
  width: var(--stamp-size);
  height: var(--stamp-size);
  display: grid;
  place-items: center;
  font-family: var(--data);
  font-weight: 700;
  font-size: calc(var(--stamp-size) * 0.36);
  line-height: 1;
  letter-spacing: -0.04em;
  color: var(--stamp-ink);
}
.stamp svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.stamp .face { fill: none; stroke: currentColor; stroke-width: 5; opacity: 0.42; }
.stamp .plate { fill: currentColor; opacity: 0.10; }
.stamp span { position: relative; }

.stamp.is-crit   { --stamp-ink: var(--crit); }
.stamp.is-crit .face  { opacity: 1; stroke-width: 7; }
.stamp.is-crit .plate { opacity: 0.22; }
.stamp.is-fumble { --stamp-ink: var(--fumble); }
.stamp.is-fumble .face  { opacity: 1; stroke-width: 7; }
.stamp.is-fumble .plate { opacity: 0.22; }
/* No roll: the character lay low that day. An empty face, no numeral. */
.stamp.is-none { --stamp-ink: var(--text-faint); }
.stamp.is-none .plate { opacity: 0; }

/* On the light stock band the stamp keeps its shape but takes stock ink. */
.stock .stamp { --stamp-ink: var(--stock-ink); }
.stock .stamp.is-crit   { --stamp-ink: var(--crit-deep); }
.stock .stamp.is-fumble { --stamp-ink: var(--fumble-deep); }

ul.flashes {
  list-style: none; padding: 10px 14px; margin: 0 0 20px;
  border: 1px solid var(--gold-deep); border-radius: 3px;
  background: rgba(216,173,62,0.08); color: var(--gold);
}

/* ============================================================================
   SAGA — the running arc
   ========================================================================== */
.band { margin: 0 0 72px; }
.band > .eyebrow { margin-top: -0.6em; margin-bottom: 1.6em; }

.arc-head { margin: 8px 0 64px; }
.arc-head h1 { max-width: 15ch; }
.arc-line {
  font-size: 1.22rem;
  line-height: 1.5;
  color: var(--text-dim);
  max-width: 46rem;
  margin: 0.2em 0 1.6em;
}
.arc-facts {
  display: flex; flex-wrap: wrap; gap: 0 40px;
  margin: 0; padding: 18px 0 0; border-top: 1px solid var(--rule);
}
.arc-facts dt {
  font-family: var(--data); font-size: 0.62rem; font-weight: 600;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--text-faint);
}
.arc-facts dd {
  margin: 4px 0 0;
  font-family: var(--display); font-weight: 700; font-size: 2.1rem;
  line-height: 1; color: var(--text);
}
/* A date is data, not a headline — it takes the utility face so the counts
   beside it stay the things you read first. */
.arc-facts .is-date dd {
  font-family: var(--data); font-weight: 400; font-size: 1.02rem;
  letter-spacing: -0.02em; color: var(--text-dim); padding-top: 0.5em;
}
.arc-facts .is-loss dd { color: var(--fumble); }
.arc-facts .is-up dd { color: var(--crit); }

/* --- The party. A depleted HP track is the loudest thing on this page. --- */
/* Hairlines come from each card's own outline rather than a gap over a tinted
   container, so a partly-filled last row leaves no stray lit cell. */
.crew {
  list-style: none; margin: 0; padding: 1px 0 0 1px;
  display: grid; gap: 1px;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
.crew-card {
  display: flex; align-items: stretch; gap: 14px;
  padding: 15px 16px; margin: 0;
  background: var(--chassis);
  box-shadow: 0 0 0 1px var(--rule-soft);
  transition: background 0.15s;
}
.crew-card:hover { background: var(--chassis-2); }
.crew-face {
  flex: none; width: 58px; height: 58px; overflow: hidden;
  background: var(--chassis-3); display: grid; place-items: center;
  clip-path: polygon(50% 0%, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%);
}
/* Portraits are full scene illustrations, so bias the crop upward — that is
   where the face almost always is. */
.crew-face img {
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 28%;
  display: block;
}
.crew-initial {
  font-family: var(--display); font-weight: 700; font-size: 1.5rem;
  color: var(--text-faint);
}
/* The HP track pins to the bottom so every card in a row shares one baseline,
   whatever the length of the class name above it. */
.crew-body { min-width: 0; flex: 1; display: flex; flex-direction: column; }
.crew-name {
  display: block; font-family: var(--display); font-weight: 700;
  font-size: 1.35rem; line-height: 1.05; text-transform: uppercase;
  color: var(--text); letter-spacing: 0.01em;
}
.crew-card:hover .crew-name { color: var(--signal); text-decoration: none; }
.crew-class {
  display: block; font-size: 0.9rem; color: var(--text-dim);
  margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.crew-player {
  display: block; font-family: var(--data); font-size: 0.6rem; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-faint);
  margin-top: 3px;
}
.hp { display: flex; align-items: center; gap: 9px; margin-top: auto; padding-top: 11px; }
.hp-track {
  flex: 1; height: 4px; background: var(--chassis-3); overflow: hidden;
}
.hp-fill { height: 100%; background: var(--text-dim); }
.hp-num {
  font-family: var(--data); font-size: 0.78rem; font-weight: 700;
  color: var(--text); letter-spacing: -0.02em;
}
.hp-max { color: var(--text-faint); font-weight: 400; }
.crew-card.is-hurt .hp-fill { background: var(--crit); }
.crew-card.is-critical .hp-fill { background: var(--fumble); }
.crew-card.is-critical .hp-num { color: var(--fumble); }
/* A quarter health or less. A hard edge, not a wash — a wash reads as
   "selected", and this needs to read as "about to die". */
.crew-card.is-critical {
  box-shadow: 0 0 0 1px var(--rule-soft), inset 2px 0 0 var(--fumble);
}

/* --- Quests --------------------------------------------------------------- */
.quests {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 18px;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
.quest {
  margin: 0; padding: 18px 20px 20px;
  border: 1px solid var(--rule);
  border-left: 2px solid var(--rule);
}
.quest.is-active { border-left-color: var(--signal); background: var(--signal-tint); }
.quest-state {
  font-family: var(--data); font-size: 0.6rem; font-weight: 600;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--text-faint);
}
.quest.is-active .quest-state { color: var(--signal); }
.quest h3 { margin: 8px 0 8px; font-size: 1.35rem; }
.quest p { margin: 0; font-size: 0.95rem; line-height: 1.55; color: var(--text-dim); }
.quest.is-completed h3, .quest.is-archived h3 { color: var(--text-dim); }

/* --- The chapter rail ----------------------------------------------------- */
.past-arc { border-top: 1px solid var(--rule); }
.past-arc > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap;
  padding: 22px 0;
}
.past-arc > summary::-webkit-details-marker { display: none; }
.past-arc > summary::after {
  content: "Show"; margin-left: auto;
  font-family: var(--data); font-size: 0.62rem; font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--signal);
}
.past-arc[open] > summary::after { content: "Hide"; }
.past-arc > summary h2 { margin: 0; color: var(--text-dim); }
.past-arc > summary .eyebrow { margin: 0; }
.past-arc:hover > summary h2 { color: var(--text); }
.past-arc > .rail, .past-arc > .quests { margin-bottom: 30px; }
.epilogue {
  max-width: 46rem; margin: 0 0 26px;
  font-style: italic; line-height: 1.65; color: var(--text-dim);
}
.quests.is-past { margin-bottom: 26px; }
.quests.is-past .quest { padding-bottom: 16px; }
.quests.is-past h3 { margin-bottom: 0; font-size: 1.15rem; }

.rail {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 20px;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
}
.rail-item { margin: 0; }
.rail-item > a { display: block; color: inherit; }
.rail-item > a:hover { text-decoration: none; }
.rail-art {
  position: relative; display: block; aspect-ratio: 4 / 3; overflow: hidden;
  background: var(--chassis-2); border: 1px solid var(--rule);
}
.rail-art img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  filter: saturate(0.85) contrast(1.02);
  transition: transform 0.4s cubic-bezier(.2,.7,.2,1), filter 0.3s;
}
.rail-item > a:hover .rail-art img { transform: scale(1.045); filter: none; }
.rail-index {
  position: absolute; left: 0; bottom: 0;
  padding: 2px 10px 1px;
  font-family: var(--display); font-weight: 800; font-size: 1.5rem; line-height: 1.1;
  color: var(--chassis); background: var(--signal);
}
.rail-day {
  display: block; margin-top: 9px;
  font-family: var(--data); font-size: 0.62rem; font-weight: 600;
  letter-spacing: 0.14em; color: var(--text-faint);
}
.rail-recap {
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden;
  margin-top: 5px; font-size: 0.88rem; line-height: 1.45; color: var(--text-dim);
}
.rail-item > a:hover .rail-recap { color: var(--text); }

/* --- The fallen ----------------------------------------------------------- */
.fallen { list-style: none; margin: 0; padding: 0; max-width: 52rem; }
.fallen-row {
  display: flex; gap: 20px; align-items: flex-start;
  margin: 0; padding: 22px 0;
  border-top: 1px solid var(--rule);
}
.fallen-row:last-child { border-bottom: 1px solid var(--rule); }
.fallen-body { min-width: 0; }
.fallen-name {
  font-family: var(--display); font-weight: 700; font-size: 1.6rem;
  text-transform: uppercase; color: var(--text); line-height: 1.05;
}
.fallen-meta {
  display: block; margin-top: 3px;
  font-family: var(--data); font-size: 0.62rem; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-faint);
}
.fallen-cause {
  margin: 10px 0 0; font-size: 1.02rem; line-height: 1.55;
  font-style: italic; color: var(--text-dim); max-width: 44rem;
}

/* ============================================================================
   SAGA — one character
   ========================================================================== */
.figure-head { margin: 8px 0 34px; }
.figure-head h1 { max-width: 18ch; }
.figure-head.is-dead h1 { color: var(--text-dim); }
.figure-class {
  margin: 0 0 22px; font-size: 1.2rem; color: var(--text-dim);
}
.arc-facts .is-hp dd span { color: var(--text-faint); font-size: 0.62em; }
.arc-facts .is-hp.is-loss dd { color: var(--fumble); }
.death-line {
  max-width: 44rem; margin: 24px 0 0;
  padding-left: 18px; border-left: 2px solid var(--fumble);
  font-size: 1.1rem; line-height: 1.6; font-style: italic; color: var(--text-dim);
}
/* Retiring is not dying: same shape, the arc's own colour instead of the fumble red. */
.death-line.is-retired { border-left-color: var(--signal); }

.figure-art { max-width: 620px; margin: 0 0 8px; }
.figure-art .carousel { margin: 0; }
.figure-art .carousel-viewport { border: 1px solid var(--rule); border-radius: 0; }

/* A label set into the stock band, so a page can put more than one kind of
   prose on paper without a dark heading interrupting it. */
.stock-label {
  display: block; margin-bottom: 1.4em;
  font-family: var(--data); font-size: 0.62rem; font-weight: 600;
  letter-spacing: 0.19em; text-transform: uppercase; color: var(--stock-dim);
}

.marks {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 8px;
}
.marks li {
  margin: 0; padding: 7px 13px;
  border: 1px solid var(--rule);
  font-size: 0.92rem; color: var(--text-dim);
}

/* The fortune ribbon: this character's rolls, one face each. The same stamp as
   everywhere else, small enough that a run of them reads as a pattern. */
.ribbon {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 6px;
}
.ribbon li { margin: 0; }

/* ============================================================================
   SAGA — one chapter
   ========================================================================== */
.chapter-head { margin: 8px 0 30px; }
.chapter-head h1 { display: flex; align-items: baseline; gap: 0.3em; flex-wrap: wrap; }
.chapter-word { color: var(--text-faint); font-weight: 400; }
.chapter-no { color: var(--signal); }
.chapter-recap {
  max-width: 44rem; margin: 0.6em 0 0;
  font-size: 1.15rem; line-height: 1.55; color: var(--text-dim); font-style: italic;
}

.chapter-art { margin: 0 0 0; }
/* On a chapter the art is the establishing shot, not an attachment: it spans the
   content column, loses the frame, and butts straight up against the stock band
   below it. */
.chapter-art { margin-top: 28px; }
.chapter-art .carousel { max-width: none; margin: 0; }
.chapter-art .carousel-viewport { border: 0; border-radius: 0; background: #000; }
.chapter-art .carousel-slide a { width: 100%; }
.chapter-art .carousel-slide img {
  width: 100%; max-width: none; height: auto; max-height: 62vh; object-fit: cover;
}
.chapter-art .carousel-slide figcaption { border: 0; }
.chapter-art + .stock { margin-top: 0; }

/* The stock band: full-bleed light paper. The page changes material where the
   fiction starts. */
.stock {
  position: relative; left: 50%; right: 50%;
  width: 100vw; margin-left: -50vw; margin-right: -50vw;
  background: var(--stock);
  color: var(--stock-ink);
  padding: clamp(44px, 7vw, 88px) var(--gutter);
  margin-top: 44px; margin-bottom: 72px;
}
.stock .chapter {
  max-width: var(--measure); margin: 0 auto;
  font-size: 1.16rem; line-height: 1.72;
}
.stock .chapter p { margin: 0 0 1.35em; }
.stock .chapter p:first-of-type::first-letter {
  float: left; font-family: var(--display); font-weight: 700;
  font-size: 3.6em; line-height: 0.78; padding: 0.06em 0.1em 0 0;
  color: var(--signal-lo);
}
.stock .chapter p:last-child { margin-bottom: 0; }

/* --- The hinge: roll → consequence --------------------------------------- */
.hinge { list-style: none; margin: 0; padding: 0; }
.hinge-row {
  display: grid; gap: 4px 20px; align-items: start;
  grid-template-columns: auto minmax(9rem, 13rem) 1fr auto;
  margin: 0; padding: 20px 0;
  border-top: 1px solid var(--rule);
}
.hinge-row:last-child { border-bottom: 1px solid var(--rule); }
.hinge-who { grid-row: span 1; }
.hinge-who a, .hinge-who span:first-child {
  font-family: var(--display); font-weight: 700; font-size: 1.3rem;
  text-transform: uppercase; color: var(--text); line-height: 1.1;
  display: block;
}
.hinge-who a:hover { color: var(--signal); text-decoration: none; }
.hinge-player {
  display: block; margin-top: 2px;
  font-family: var(--data); font-size: 0.58rem; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-faint);
}
.hinge-what {
  margin: 0; font-size: 1rem; line-height: 1.55; color: var(--text-dim);
  max-width: 42rem;
}
.hinge-hp { text-align: right; font-family: var(--data); white-space: nowrap; }
.hp-delta {
  display: block; font-size: 1.1rem; font-weight: 700; color: var(--text-faint);
}
.hinge-row.is-hit  .hp-delta { color: var(--fumble); }
.hinge-row.is-mend .hp-delta { color: var(--crit); }
.hp-delta.is-nil {
  font-size: 0.62rem; font-weight: 600; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--text-faint);
}
.hp-run {
  display: block; margin-top: 2px; font-size: 0.64rem; font-weight: 400;
  letter-spacing: 0.08em; color: var(--text-faint);
}

/* --- Read on -------------------------------------------------------------- */
.chapter-nav {
  display: flex; justify-content: space-between; gap: 20px;
  padding: 26px 0; margin: 0 0 48px;
  border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
}
.chapter-nav a { color: var(--text-dim); }
.chapter-nav a:hover { text-decoration: none; }
.chapter-nav .eyebrow { margin-bottom: 4px; }
.chapter-next { text-align: right; }
.chapter-nav-title {
  font-family: var(--display); font-weight: 700; font-size: 1.5rem;
  text-transform: uppercase; color: var(--text); display: block; line-height: 1;
}
.chapter-nav a:hover .chapter-nav-title { color: var(--signal); }

.chapter-extras { max-width: 46rem; }
.chapter-extras .image-prompt { margin: 0 0 14px; }
.chapter-extras summary {
  cursor: pointer; font-family: var(--data); font-size: 0.64rem; font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--text-faint);
}
.chapter-extras summary:hover { color: var(--signal); }
.chapter-extras details p {
  margin: 12px 0 0; font-style: italic; color: var(--text-dim); line-height: 1.6;
}

@media (max-width: 720px) {
  .hinge-row {
    grid-template-columns: auto 1fr;
    gap: 3px 16px;
  }
  .hinge-who  { grid-column: 2; }
  .hinge-what { grid-column: 2; margin-top: 6px; }
  .hinge-hp   { grid-column: 2; text-align: left; margin-top: 8px; }
  .hp-delta, .hp-run { display: inline; margin-right: 8px; }
}

@media (prefers-reduced-motion: reduce) {
  .rail-item > a:hover .rail-art img { transform: none; }
  * { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}

/* ============================================================================
   Saga teaser (home)
   ========================================================================== */
.latest-chapter {
  border: 1px solid var(--line);
  border-left: 3px solid var(--gold);
  border-radius: 0 3px 3px 0;
  padding: 16px 20px;
  margin: 6px 0 40px;
  background: linear-gradient(180deg, var(--ink-2), var(--ink));
}
.latest-chapter h2 { margin: 0 0 8px; font-size: 1.1rem; font-family: var(--display); letter-spacing: 0; }
.latest-chapter h2 a { color: var(--vellum); }
.latest-chapter h2 a:hover { color: var(--gold); text-decoration: none; }
.chapter-teaser { margin: 0 0 10px; color: var(--vellum-dim); font-style: italic; }
.chapter-teaser::first-line { font-style: normal; color: var(--vellum); }

/* ============================================================================
   The Night's Sigil (home) — today's rolls traced into a constellation.
   A dark "star chart" panel, deliberately NOT an illuminated plate: the drawing
   is hand-rendered on canvas and wants the night sky behind it.
   ========================================================================== */
.sigil-folio {
  position: relative; left: 50%; transform: translateX(-50%);
  width: min(1100px, 94vw); margin: 6px 0 48px;
}
.sigil-card {
  margin: 0;
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 16px 18px 14px;
  background:
    radial-gradient(900px 360px at 50% -20%, rgba(161,127,192,0.12), transparent 70%),
    linear-gradient(180deg, var(--chassis-2), var(--chassis));
  box-shadow: 0 10px 26px rgba(0,0,0,0.5);
}
.sigil-card figcaption {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 12px;
  margin-bottom: 10px;
}
.sigil-title {
  font-family: var(--display-sc); font-size: 1.3rem; letter-spacing: 0.06em;
  color: var(--gold);
}
.sigil-sub { font-family: var(--body); font-style: italic; color: var(--vellum-dim); font-size: 0.92rem; }
.sigil-canvas { display: block; width: 100%; height: 380px; }
.sigil-note { margin: 8px 0 0; font-size: 0.82rem; color: var(--vellum-dim); font-style: italic; min-height: 1em; }
.sigil-nav { display: flex; align-items: center; gap: 6px; }
.sigil-nav-btn {
  background: none; border: 1px solid var(--gold-deep); color: var(--gold);
  font-size: 1.15rem; line-height: 1; padding: 1px 8px 3px; border-radius: 2px;
  cursor: pointer; transition: background 0.15s, color 0.15s;
}
.sigil-nav-btn:hover:not(:disabled) { background: rgba(216,173,62,0.12); color: var(--gold-bright); }
.sigil-nav-btn:disabled { opacity: 0.25; cursor: default; }
.sigil-nav-date { font-family: var(--display-sc); font-size: 0.95rem; color: var(--gold); letter-spacing: 0.05em; min-width: 7ch; text-align: center; }
@media (max-width: 720px) { .sigil-canvas { height: 300px; } }

/* Plain-language "how this is made" note under the generated images. */
.sigil-how {
  margin: 12px auto 0; max-width: 64ch;
  font-family: var(--body); font-size: 0.86rem; line-height: 1.5;
  color: var(--vellum-dim);
}
.sigil-how strong { color: var(--gold); font-weight: 600; }
.sigil-card + .sigil-card { margin-top: 22px; }
.dye-bad  { color: #b3403a; font-style: italic; }
.dye-mid  { color: #9a948c; font-style: italic; }
.dye-good { color: var(--gold); font-style: italic; }

/* The Loom — same dark folio as the Sigil, but a tall woven panel. */
.loom-folio {
  position: relative; left: 50%; transform: translateX(-50%);
  width: min(1100px, 94vw); margin: 6px 0 48px;
}
.loom-canvas { display: block; width: 100%; height: 560px; }
@media (max-width: 720px) { .loom-canvas { height: 460px; } }

/* Star hover plaque — a small fate-card raised above the constellation. */
.sigil-tip {
  position: fixed; z-index: 50; display: none; pointer-events: none;
  transform: translate(-50%, -100%);
  padding: 6px 11px; border-radius: 3px;
  border: 1px solid var(--gold-deep);
  background: linear-gradient(180deg, var(--ink-2), var(--ink));
  box-shadow: 0 6px 18px rgba(0,0,0,0.6);
  white-space: nowrap; text-align: center;
}
.sigil-tip strong {
  display: block; font-family: var(--display-sc); letter-spacing: 0.04em;
  font-weight: 400; color: var(--gold);
}
.sigil-tip span { font-family: var(--body); font-size: 0.82rem; color: var(--vellum-dim); }

/* ============================================================================
   The Fates' Ledger dashboard (home) — breaks out of the reading column
   ========================================================================== */
.dashboard {
  position: relative; left: 50%; transform: translateX(-50%);
  width: min(1100px, 94vw); margin-bottom: 52px;
}
.dashboard > h1 {
  font-family: var(--display);
  text-align: center;
  font-size: clamp(2.4rem, 5.4vw, 3.5rem);
  margin: 6px 0 2px;
}
/* a small centered flourish under the title */
.dashboard > h1::after {
  content: "";
  display: block;
  width: 160px; height: 1px; margin: 12px auto 0;
  background: linear-gradient(90deg, transparent, var(--gold-deep), transparent);
}

/* Omen cards — dark fate-plaques with a struck top bar in the omen's color. */
.omen-cards { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin: 24px 0 30px; }
.omen-card {
  position: relative; overflow: hidden;
  border: 1px solid var(--line); border-radius: 3px;
  padding: 15px 15px 13px;
  background: linear-gradient(180deg, var(--ink-2), var(--ink));
  display: flex; flex-direction: column; gap: 3px;
}
.omen-card::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: var(--vellum-dim);
}
.omen-card.blessed::before { background: var(--gold); }
.omen-card.doomed::before  { background: var(--oxblood); }
.omen-card.crowned::before { background: var(--gold-bright); }
.omen-card.wretch::before  { background: var(--oxblood-dk); }
.omen-card.fortune::before { background: linear-gradient(90deg, var(--gold), var(--teal)); }
.omen-card.cursed::before  { background: var(--violet); }
.omen-card.bound::before   { background: var(--teal); }
.omen-card.whiplash::before { background: linear-gradient(90deg, var(--oxblood), var(--gold)); }
.omen-card.phoenix::before  { background: var(--ember); }
.omen-card.parched::before  { background: var(--umber); }
.omen-card.devoted::before  { background: var(--moss); }
.omen-card.sundered::before { background: linear-gradient(90deg, var(--violet), var(--oxblood)); }

/* The Soul Sheet — one player's thread, a character sheet of small plaques. */
.soul-sheet { margin: 20px 0 4px; }
.soul-sheet h2 { margin: 0 0 12px; }
.soul-stats {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: 10px;
}
.soul-stat {
  border: 1px solid var(--line); border-radius: 3px;
  padding: 10px 12px 9px;
  background: linear-gradient(180deg, var(--ink-2), var(--ink));
  display: flex; flex-direction: column; gap: 1px;
}
.soul-stat .soul-label {
  font-family: var(--display-sc); font-size: 0.72rem; letter-spacing: 0.05em;
  color: var(--vellum-dim);
}
.soul-stat strong {
  font-family: var(--display); font-size: 1.25rem; font-weight: 400;
  line-height: 1.2; color: var(--gold);
}
.soul-stat .soul-note { font-size: 0.75rem; color: var(--vellum-dim); }

/* Ledger heading + year selector (the All-Time / per-year switch) */
.ledger-head {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: 10px 18px;
}
.ledger-head h1 { margin: 0; }
.year-picker { display: flex; align-items: center; gap: 8px; }
.year-picker label {
  font-family: var(--display-sc); font-size: 0.82rem; letter-spacing: 0.05em;
  color: var(--vellum-dim);
}
.year-picker select {
  font: inherit; color: var(--vellum); background: var(--ink-2);
  border: 1px solid var(--gold-deep); border-radius: 3px; padding: 4px 8px;
}
.omen-card .omen-label {
  font-family: var(--display-sc); font-size: 0.82rem; letter-spacing: 0.05em;
  color: var(--vellum-dim);
}
.omen-card strong { font-family: var(--display); font-size: 1.3rem; font-weight: 400; line-height: 1.2; color: var(--vellum); }
.omen-card .omen-detail { font-size: 0.85rem; color: var(--vellum-dim); }

/* ============================================================================
   Omens — the day's portents (home call-out) + a player's preserved report.
   Struck plaques with a left bar in the omen's colour: gold for fortune,
   oxblood for doom, violet for the cursed, teal for the bound.
   ========================================================================== */
.todays-omens {
  border: 1px solid var(--line);
  border-top: 3px solid var(--gold);
  border-radius: 3px;
  padding: 14px 18px 16px;
  margin: 6px 0 34px;
  background:
    radial-gradient(700px 200px at 50% -40%, rgba(216,173,62,0.10), transparent 70%),
    linear-gradient(180deg, var(--ink-2), var(--ink));
}
.omens-heading {
  margin: 0 0 12px;
  font-family: var(--display-sc); font-size: 1.35rem; letter-spacing: 0.06em;
  color: var(--gold);
}
.omens-eye { font-style: normal; }

.portent-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.portent {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 10px 13px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--vellum-dim);
  border-radius: 0 3px 3px 0;
  background: linear-gradient(180deg, rgba(236,225,200,0.03), transparent);
}
.portent.accent-gold    { border-left-color: var(--gold); }
.portent.accent-oxblood { border-left-color: var(--oxblood); }
.portent.accent-violet  { border-left-color: var(--violet); }
.portent.accent-teal    { border-left-color: var(--teal); }
.portent-emoji { font-size: 1.5rem; line-height: 1.25; flex: none; }
.portent-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.portent-title {
  font-family: var(--display-sc); letter-spacing: 0.04em;
  color: var(--vellum); font-size: 1rem;
}
.portent-date {
  margin-left: 6px; font-family: var(--body); letter-spacing: 0;
  font-size: 0.8rem; color: var(--vellum-dim);
}
.portent-who { font-family: var(--display); color: var(--gold-bright); font-size: 1.1rem; line-height: 1.2; }
.portent-detail { color: var(--vellum-dim); font-style: italic; font-size: 0.92rem; }
.player-portents { max-width: 680px; margin-bottom: 26px; }

/* Chart cards sit on the same stock as the narrative — the one light material
   in the system, reused rather than invented. Charts are dark-on-light, so this
   also keeps Chart.js legible without fighting it. */
.chart-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
.chart-card {
  background: var(--stock);
  border: 0;
  border-radius: 0;
  padding: 18px 18px 14px;
  margin: 0;
}
.chart-card.wide { grid-column: 1 / -1; }
/* The caption is the chart's title, so it takes the utility face like every
   other label, in stock ink. */
.chart-card figcaption {
  font-family: var(--data); font-size: 0.6rem; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--stock-dim); margin-bottom: 14px;
}
@media (max-width: 720px) {
  .omen-cards { grid-template-columns: repeat(2, 1fr); }
  .chart-grid { grid-template-columns: 1fr; }
  .soul-stats { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 721px) and (max-width: 1000px) {
  .soul-stats { grid-template-columns: repeat(4, 1fr); }
}

/* The Hour of Fate — value-vs-time-of-day analysis (home). Dark folio framing a
   single illuminated plate, with a verdict line in the Saga's voice above it. */
.daypart-folio { margin: 34px 0; }
.daypart-folio > h1 {
  font-family: var(--display);
  font-size: 1.7rem; font-weight: 400; letter-spacing: 0.01em;
  margin: 0 0 4px; color: var(--vellum);
}
.daypart-verdict {
  margin: 0 0 14px; max-width: 60ch;
  font-family: var(--body); font-style: italic; font-size: 1.02rem;
  color: var(--gold-bright);
}
.daypart-stats {
  display: flex; flex-wrap: wrap; gap: 6px 22px;
  margin: 10px 0 0; font-size: 0.86rem; color: var(--vellum-dim);
}
.daypart-stats strong { color: var(--vellum); font-weight: 600; }

/* Filterable player charts reuse .chart-card outside the dashboard grid. */
main > .chart-card { margin: 10px 0 26px; }
/* Time-range filters sit on the stock, so they take stock ink rather than the
   dark-surface button style. */
.chart-windows { display: flex; gap: 4px; margin-bottom: 14px; }
.chart-windows button {
  background: none; color: var(--stock-dim);
  border: 1px solid var(--stock-2); padding: 5px 12px; border-radius: 0;
  font-family: var(--data); font-size: 0.58rem; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
}
.chart-windows button:hover { color: var(--stock-ink); border-color: var(--stock-dim); }
.chart-windows button.active {
  background: var(--stock-ink); color: var(--stock); border-color: var(--stock-ink);
}

/* "vs party" chips (player header) */
.vs-party { font-family: var(--body); font-size: 0.7em; padding: 1px 7px; border-radius: 3px; margin-left: 4px; }
.vs-party.favored  { color: var(--chassis); background: var(--crit); }
.vs-party.forsaken { color: #fff; background: var(--fumble); }

/* ============================================================================
   Tables — the ledger. Gold small-caps heads, hairline rules.
   ========================================================================== */
/* Any table too wide for the viewport scrolls inside its own box; the page body
   never scrolls sideways. */
.table-scroll { overflow-x: auto; }
.table-scroll > table { min-width: 34rem; }

table { border-collapse: collapse; width: 100%; }
th, td { border-bottom: 1px solid var(--rule); padding: 10px 12px; text-align: left; }
/* Column headings are labels, so they take the utility face like every other
   label on the site. */
thead th {
  font-family: var(--data); font-weight: 600; font-size: 0.62rem;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--text-faint); border-bottom: 1px solid var(--rule);
  padding-bottom: 8px;
}
tbody tr:hover td { background: var(--chassis-2); }
tr.corrected td { color: var(--text-faint); font-style: italic; }

td.source-msg { max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.9em; color: var(--text-dim); }
td.source-msg span { cursor: help; }
td.source-msg .gif-roll { font-style: italic; }

ul.summary {
  list-style: none; padding: 0; display: flex; gap: 28px; flex-wrap: wrap;
  font-family: var(--data); font-size: 0.8rem; letter-spacing: 0.02em;
}

/* Forms (corrections) */
form label {
  display: inline-block; margin-right: 14px; color: var(--text-faint);
  font-family: var(--data); font-size: 0.62rem; font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase;
}
input[type="date"], input[type="number"], input[type="text"],
input[type="password"], textarea, select {
  background: var(--chassis-2); border: 1px solid var(--rule); color: var(--text);
  padding: 9px 11px; border-radius: 0; font: inherit; font-size: 0.95rem;
}
/* Controls sized by cols/size attributes must never widen the page on a phone. */
input, textarea, select { max-width: 100%; }
form label { max-width: 100%; }
input:focus, textarea:focus, select:focus {
  border-color: var(--signal); outline: none;
}
button, input[type="submit"] {
  background: none;
  color: var(--signal); border: 1px solid var(--signal-lo);
  padding: 10px 18px; border-radius: 0; cursor: pointer;
  font-family: var(--data); font-size: 0.66rem; font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
button:hover, input[type="submit"]:hover {
  background: var(--signal); color: var(--chassis); border-color: var(--signal);
}

/* ============================================================================
   The roll grid — every player's last ten days at once.

   Same semantics as the stamp (20 gold, 1 red, no roll faint), rendered as flat
   cells rather than die faces: at this density the faces would fight each other,
   and the grid's job is the shape of a fortnight, not one number.
   ========================================================================== */
.roll-history-wrap {
  overflow-x: auto; margin: 6px 0 14px;
  border: 1px solid var(--rule);
}
table.player-roll-table { width: auto; border-collapse: separate; border-spacing: 0; }
table.player-roll-table th {
  font-family: var(--data); font-size: 0.58rem; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--text-faint); padding: 12px 7px 10px; text-align: center;
  white-space: nowrap; border-bottom: 1px solid var(--rule);
}
table.player-roll-table th.name-col { text-align: left; padding-left: 16px; }
table.player-roll-table th.today-col,
table.player-roll-table th.oracle-col { color: var(--signal); }
table.player-roll-table td { border-bottom: 1px solid var(--rule-soft); }
table.player-roll-table tbody tr:hover td { background: var(--chassis-2); }
table.player-roll-table td.name-col { padding: 7px 18px 7px 16px; white-space: nowrap; }
table.player-roll-table td.name-col a { color: var(--text); }
table.player-roll-table td.name-col a:hover { color: var(--signal); text-decoration: none; }
table.player-roll-table td.roll-cell {
  text-align: center; font-family: var(--data); font-size: 0.78rem; font-weight: 400;
  letter-spacing: -0.04em; min-width: 38px; padding: 8px 7px; color: var(--text);
}
table.player-roll-table td.roll-cell.no-roll { color: var(--text-faint); }
/* the crit */
table.player-roll-table td.roll-cell.val-20 {
  color: var(--chassis); font-weight: 700; background: var(--crit);
}
/* the fumble */
table.player-roll-table td.roll-cell.val-1 {
  color: #fff; font-weight: 700; background: var(--fumble);
}
/* the Oracle's guess — outlined, never filled, so it can't read as a real roll */
table.player-roll-table td.roll-cell.oracle {
  color: var(--signal);
  box-shadow: inset 0 0 0 1px var(--signal-lo);
}
/* the Oracle foresaw something — a mark under a roll it had called */
table.player-roll-table td.roll-cell.has-pred {
  border-bottom: 2px solid var(--signal-lo);
}
/* the Oracle was right */
table.player-roll-table td.roll-cell.predicted-match {
  box-shadow: inset 0 0 0 2px var(--signal);
}
/* hide oracle column when browsing historical windows */
table.player-roll-table.hide-oracle th.oracle-col,
table.player-roll-table.hide-oracle td.oracle { display: none; }

/* calendar nav for the players grid */
.roll-grid-nav {
  display: flex; align-items: center; gap: 10px; margin: 0 0 8px;
}
.grid-nav-btn {
  background: var(--ink-3); border: 1px solid var(--line); border-radius: 3px;
  color: var(--vellum-dim); font-size: 0.82rem; padding: 3px 9px; cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}
.grid-nav-btn:hover:not(:disabled) { color: var(--gold); border-color: var(--gold-deep); }
.grid-nav-btn:disabled { opacity: 0.38; cursor: default; }
.grid-nav-label {
  font-size: 0.82rem; font-family: var(--display-sc); color: var(--vellum-dim);
  letter-spacing: 0.03em; min-width: 120px; text-align: center;
}

.oracle-ledger { margin: 28px 0; }
.oracle-ledger h2 { margin-bottom: 4px; }

/* legacy inline roll badge (kept for any template still using it) */
.today-roll { display: inline-block; min-width: 28px; text-align: center; font-size: 0.85em; font-variant-numeric: tabular-nums; border-radius: 3px; padding: 1px 6px; font-weight: 600; }
.today-roll.rolled  { background: var(--signal); color: var(--chassis); }
.today-roll.pending { background: var(--ink-3); color: var(--vellum-dim); border: 1px solid var(--line); }

/* ============================================================================
   Saga chapter — illuminated prose
   ========================================================================== */
article.chapter { font-size: 1.1rem; line-height: 1.85; max-width: 64ch; }
article.chapter p { margin: 0 0 1.1em; }
article.chapter p:first-of-type::first-letter {
  font-family: var(--display); float: left; font-size: 3.4em; line-height: 0.78;
  padding: 0.04em 0.12em 0 0; color: var(--gold);
}

/* ============================================================================
   Trophy Case
   ========================================================================== */
table.trophy { width: auto; min-width: 320px; margin-bottom: 30px; }
table.trophy th, table.trophy td { text-align: center; min-width: 84px; }
/* The roll column is the row's identity — a number, so it takes the utility
   face like every other figure on the site. */
table.trophy th.roll {
  text-align: right; min-width: 52px;
  font-family: var(--data); font-size: 0.8rem; font-weight: 400;
  letter-spacing: -0.03em; color: var(--text-dim);
}
table.trophy thead th { vertical-align: bottom; }
/* A completed column is the whole point of the page, so it gets the crit gold. */
table.trophy thead th.won { color: var(--crit); }
table.trophy .progress {
  display: block; margin-top: 3px;
  font-size: 0.9em; font-weight: 400; color: var(--text-faint);
  letter-spacing: 0.06em;
}
table.trophy td.got { color: var(--crit); font-size: 1.15em; cursor: help; }
table.trophy td.todo::after { content: "·"; color: var(--text-faint); }

/* ============================================================================
   Admin console (behind login — not public, but inherits base.html)

   A tabbed "mission control": pure-CSS radio tabs (no JS — works under the
   plain-CGI bridge). One tab each for the saga, audit trail, players, Oracle.

   Same materials as the public site, deliberately quieter: this is a working
   tool, so headings step down, rhythm tightens, and the only colour that
   appears is the one carrying a state — a pending count, a destructive action.
   Nothing here should compete with the thing being edited.
   ========================================================================== */
.admin-head { margin-bottom: 24px; }
.admin-head h1 { margin-bottom: 0.12em; font-size: clamp(2rem, 4vw, 2.8rem); }

/* Section headings inside the console sit a full step below the public
   site's — a panel is a form, not a page. */
.admin-panel h2 {
  font-size: 1.15rem; letter-spacing: 0.06em;
  margin: 2.4em 0 0.5em; padding-bottom: 8px;
  border-bottom: 1px solid var(--rule-soft); color: var(--text-dim);
}
.admin-panel h2:first-of-type { margin-top: 0.8em; }
.admin-panel h3 { font-size: 1rem; letter-spacing: 0.04em; color: var(--text-dim); }
.admin-panel .panel-intro,
.admin-panel p { font-size: 0.94rem; }
.admin-panel table { font-size: 0.88rem; }
.admin-panel th, .admin-panel td { padding: 7px 10px; }

/* A pending state the admin needs to act on. Carries its own words, so the
   colour is reinforcement rather than the whole message. */
.admin-warn {
  border-left: 2px solid var(--crit);
  padding: 10px 0 10px 14px;
  background: linear-gradient(90deg, rgba(245,213,71,0.06), transparent 60%);
}
.admin-warn strong { color: var(--crit); }

.admin-gate { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.admin-gate label { margin: 0; }
.login-error { color: var(--fumble); }

/* The console's data tables (seeds, costs, characters, mutes) are wider than a
   phone. Making each one its own scroll container on narrow screens keeps the
   page from scrolling sideways, without wrapping six tables in the markup. */
@media (max-width: 720px) {
  /* The explicit min-widths these tables carry are what push the page wide, so
     they are dropped here and re-applied to the scrolling content instead.
     Selectors are qualified past `table.seed-table` etc., which set those
     min-widths later in this file at equal specificity. */
  .admin-panel table,
  .admin-panel table.seed-table,
  .admin-panel table.mute-table,
  .admin-panel table.delve-snap-table {
    display: block; overflow-x: auto; white-space: nowrap; min-width: 0; width: 100%;
  }
  .admin-panel table > * { min-width: 540px; }
  .admin-panel table.mute-table > * { min-width: 360px; }
}

.admin-console { position: relative; }
/* Visually hidden, but kept focusable so the tabs are keyboard-operable.
   Shared by the admin console and the home dashboard tabs below. */
.admin-console > .tab-radio,
.home-console > .tab-radio,
.player-console > .tab-radio {
  position: absolute; width: 1px; height: 1px; opacity: 0;
  clip: rect(0 0 0 0); overflow: hidden;
}

nav.admin-tabs {
  display: flex; flex-wrap: wrap; gap: 4px;
  border-bottom: 1px solid var(--rule);
  margin: 0 0 30px;
}
.admin-tab {
  font-family: var(--data); font-size: 0.66rem; font-weight: 600;
  letter-spacing: 0.15em; text-transform: uppercase;
  color: var(--text-faint); cursor: pointer; user-select: none;
  padding: 12px 14px; margin-bottom: -1px;
  border-bottom: 2px solid transparent;
  display: inline-flex; align-items: center; gap: 7px;
}
.admin-tab:hover { color: var(--text); }
.admin-tab .tab-ico { display: none; }

.admin-panel { display: none; }
.admin-panel > :first-child { margin-top: 0; }
.panel-intro { margin-top: 0; max-width: 620px; }

#tab-saga:checked       ~ #panel-saga,
#tab-audit:checked      ~ #panel-audit,
#tab-players:checked    ~ #panel-players,
#tab-oracle:checked     ~ #panel-oracle,
#tab-undercroft:checked ~ #panel-undercroft,
#tab-gifs:checked       ~ #panel-gifs,
#tab-photos:checked     ~ #panel-photos { display: block; }

/* Active tab: lifts out of the rule and joins the panel below it. */
#tab-saga:checked       ~ nav.admin-tabs label[for="tab-saga"],
#tab-audit:checked      ~ nav.admin-tabs label[for="tab-audit"],
#tab-players:checked    ~ nav.admin-tabs label[for="tab-players"],
#tab-oracle:checked     ~ nav.admin-tabs label[for="tab-oracle"],
#tab-undercroft:checked ~ nav.admin-tabs label[for="tab-undercroft"],
#tab-gifs:checked       ~ nav.admin-tabs label[for="tab-gifs"],
#tab-photos:checked     ~ nav.admin-tabs label[for="tab-photos"] {
  color: var(--text); border-bottom-color: var(--signal);
}
/* Keyboard focus ring tracks whichever tab is focused. */
#tab-saga:focus-visible       ~ nav.admin-tabs label[for="tab-saga"],
#tab-audit:focus-visible      ~ nav.admin-tabs label[for="tab-audit"],
#tab-players:focus-visible    ~ nav.admin-tabs label[for="tab-players"],
#tab-oracle:focus-visible     ~ nav.admin-tabs label[for="tab-oracle"],
#tab-undercroft:focus-visible ~ nav.admin-tabs label[for="tab-undercroft"],
#tab-gifs:focus-visible       ~ nav.admin-tabs label[for="tab-gifs"],
#tab-photos:focus-visible     ~ nav.admin-tabs label[for="tab-photos"] {
  outline: 2px solid var(--gold); outline-offset: 2px;
}

/* Gif library: labeling queue + known-gif grid. */
/* A count of things waiting on you — the one badge that earns a colour. */
.gif-badge {
  display: inline-block; min-width: 1.5em; padding: 1px 5px; margin-left: 2px;
  font-family: var(--data); font-size: 0.85em; font-weight: 700;
  line-height: 1.4; text-align: center; letter-spacing: 0;
  background: var(--fumble); color: #fff; border-radius: 0;
}
.gif-grid {
  display: grid; gap: 14px; margin: 12px 0;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
}
.gif-card {
  border: 1px solid var(--rule); border-radius: 0; padding: 8px;
  background: var(--chassis-2); display: flex; flex-direction: column; gap: 6px;
}
.gif-thumb {
  width: 100%; height: 140px; object-fit: contain;
  background: #000; border-radius: 0;
}
.gif-thumb--missing {
  display: flex; align-items: center; justify-content: center;
  color: var(--muted); font-size: 0.8em;
}
.gif-meta { font-size: 0.78em; word-break: break-word; }
.gif-label-row { display: flex; gap: 6px; align-items: center; }
.gif-label-row input[type="number"] { width: 5em; }
.gif-archive-row { margin-top: 2px; }
.btn-sm.btn-muted {
  background: transparent; color: var(--muted);
  border-color: var(--line); width: 100%;
}
.btn-sm.btn-muted:hover { color: var(--vellum); border-color: var(--muted); }

/* --------------------------------------------------------------------------
   Home dashboard tabs — the same pure-CSS radio-tab pattern as the admin
   console, reused on the public home page so the Today / Fates' Ledger /
   Oracle / Sky & Hour sections each get a pane instead of one long scroll.
   -------------------------------------------------------------------------- */
.home-console { position: relative; }

/* Tabs are labels, so they take the utility face and sit on a rule; the active
   one is marked by a signal underline rather than a raised folder tab. */
nav.home-tabs {
  display: flex; flex-wrap: wrap; gap: 4px;
  border-bottom: 1px solid var(--rule);
  margin: 0 0 34px;
}
.home-tab {
  font-family: var(--data); font-size: 0.68rem; font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--text-faint); cursor: pointer; user-select: none;
  padding: 12px 16px; margin-bottom: -1px;
  border-bottom: 2px solid transparent;
}
.home-tab:hover { color: var(--text); }
.home-tab .tab-ico { display: none; }

.home-panel { display: none; }
.home-panel > :first-child { margin-top: 0; }

#htab-today:checked  ~ #hpanel-today,
#htab-ledger:checked ~ #hpanel-ledger,
#htab-oracle:checked ~ #hpanel-oracle,
#htab-sky:checked    ~ #hpanel-sky { display: block; }

/* Active tab: lifts out of the rule and joins the panel below it. */
#htab-today:checked  ~ nav.home-tabs label[for="htab-today"],
#htab-ledger:checked ~ nav.home-tabs label[for="htab-ledger"],
#htab-oracle:checked ~ nav.home-tabs label[for="htab-oracle"],
#htab-sky:checked    ~ nav.home-tabs label[for="htab-sky"] {
  color: var(--text); border-bottom-color: var(--signal);
}
/* Keyboard focus ring tracks whichever tab is focused. */
#htab-today:focus-visible  ~ nav.home-tabs label[for="htab-today"],
#htab-ledger:focus-visible ~ nav.home-tabs label[for="htab-ledger"],
#htab-oracle:focus-visible ~ nav.home-tabs label[for="htab-oracle"],
#htab-sky:focus-visible    ~ nav.home-tabs label[for="htab-sky"] {
  outline: 2px solid var(--gold); outline-offset: 2px;
}

/* --------------------------------------------------------------------------
   Player page tabs — the same pure-CSS radio-tab pattern, so a player's
   Rolls & Corrections / Fortune / Omens each get a pane. History is the
   default tab: fixing a roll and reading the history is the usual visit.
   -------------------------------------------------------------------------- */
.player-console { position: relative; }

nav.player-tabs {
  display: flex; flex-wrap: wrap; gap: 4px;
  border-bottom: 1px solid var(--rule);
  margin: 0 0 34px;
}
.player-tab {
  font-family: var(--data); font-size: 0.68rem; font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--text-faint); cursor: pointer; user-select: none;
  padding: 12px 16px; margin-bottom: -1px;
  border-bottom: 2px solid transparent;
}
.player-tab:hover { color: var(--text); }
.player-tab .tab-ico { display: none; }

.player-panel { display: none; }
.player-panel > :first-child { margin-top: 0; }

#ptab-history:checked ~ #ppanel-history,
#ptab-fortune:checked ~ #ppanel-fortune,
#ptab-omens:checked   ~ #ppanel-omens { display: block; }

/* Active tab: lifts out of the rule and joins the panel below it. */
#ptab-history:checked ~ nav.player-tabs label[for="ptab-history"],
#ptab-fortune:checked ~ nav.player-tabs label[for="ptab-fortune"],
#ptab-omens:checked   ~ nav.player-tabs label[for="ptab-omens"] {
  color: var(--text); border-bottom-color: var(--signal);
}
/* Keyboard focus ring tracks whichever tab is focused. */
#ptab-history:focus-visible ~ nav.player-tabs label[for="ptab-history"],
#ptab-fortune:focus-visible ~ nav.player-tabs label[for="ptab-fortune"],
#ptab-omens:focus-visible   ~ nav.player-tabs label[for="ptab-omens"] {
  outline: 2px solid var(--gold); outline-offset: 2px;
}

.panel-lead { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.panel-lead h2 { margin: 0; }
.badge-soon {
  font-family: var(--display-sc); letter-spacing: 0.05em; font-size: 0.7rem;
  text-transform: uppercase; color: var(--gold);
  border: 1px solid var(--gold-deep); border-radius: 3px; padding: 2px 8px;
}

fieldset.oracle-config {
  border: 1px solid var(--line); border-radius: 5px;
  background: var(--ink-2); padding: 16px 20px 20px; margin: 12px 0 0;
  max-width: 520px;
}
fieldset.oracle-config[disabled] { opacity: 0.78; }
fieldset.oracle-config legend {
  font-family: var(--display-sc); letter-spacing: 0.04em; padding: 0 8px;
}
.oracle-config .field-row { margin-bottom: 16px; }
.oracle-config .field-row:last-of-type { margin-bottom: 18px; }
.oracle-config label { color: var(--vellum); display: block; margin: 0; }
.oracle-config label.check { display: flex; align-items: center; gap: 8px; }
.oracle-config input[type="text"],
.oracle-config input[type="number"] { width: 170px; margin-top: 5px; display: block; }
.oracle-config .hint { margin-top: 4px; }
.soon-note { margin-top: 12px; font-size: 0.85em; }

code {
  font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  font-size: 0.88em; color: var(--gold-bright);
}

table.seed-table { width: auto; min-width: 540px; margin-top: 8px; }
table.seed-table td, table.seed-table th { padding: 6px 10px; vertical-align: top; }
table.seed-table details summary { cursor: pointer; color: var(--gold); }
p.seed-extract { margin: 6px 0 0; font-size: 0.85em; color: var(--vellum-dim); max-width: 480px; white-space: pre-wrap; }
table.seed-table th.tok, table.seed-table td.tok { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
tr.tok-total td { border-top: 2px solid var(--line); }

/* Qwythos's roll review. Wider than the mute tables because each row carries the
   quoted evidence — the whole point is that a proposal can be judged without
   leaving the page. Outcome tints stay faint: the eye should be drawn to what is
   waiting, not to a wall of refusals. */
table.audit-table { min-width: 640px; }
table.audit-table td { vertical-align: top; max-width: 34em; }
table.audit-table .btn-sm.btn-muted { width: auto; }
.audit-gates { font-size: 0.82em; margin: 2px 0 14px; }
.audit-verdict { font-family: var(--data); font-size: 0.9em; letter-spacing: 0.06em; }
tr.audit-applied td  { border-left: 2px solid var(--gold); }
tr.audit-proposed td { border-left: 2px solid var(--muted); }
tr.audit-rejected td { color: var(--vellum-dim); }
/* The checks table is mostly agreement, and agreement is the boring outcome you
   want to be able to skim past — it is there to prove the reviewer is running,
   not to be read row by row. So the quiet rows recede and only a verdict that
   questioned something carries the marker. */
tr.audit-agreed td  { color: var(--vellum-dim); }
tr.audit-flagged td { border-left: 2px solid var(--muted); }

table.mute-table { width: auto; min-width: 360px; margin-top: 8px; }
table.mute-table td, table.mute-table th { padding: 5px 10px; }
tr.muted-row td { color: var(--vellum-dim); }
.btn-mute   { color: var(--fumble); border-color: var(--fumble-deep); padding: 5px 11px; font-size: 0.58rem; }
.btn-mute:hover { background: var(--fumble); color: #fff; border-color: var(--fumble); }
.btn-unmute { color: #5fbf9b; border-color: #276851; padding: 5px 11px; font-size: 0.58rem; }
.btn-unmute:hover { background: #5fbf9b; color: var(--chassis); border-color: #5fbf9b; }
.btn-sm { font-size: 0.56rem; padding: 5px 9px; letter-spacing: 0.1em; flex: none; }
.btn-sm.btn-danger { padding: 5px 9px; font-size: 0.56rem; }

/* Undercroft management panel. */
details.delve-danger { margin: 4px 0 16px; border: 1px solid var(--fumble-deep); border-radius: 0; background: rgba(227,59,46,0.05); }
details.delve-danger > summary { cursor: pointer; user-select: none; padding: 10px 12px; color: var(--fumble); font-family: var(--data); font-weight: 600; font-size: 0.62rem; letter-spacing: 0.15em; text-transform: uppercase; }
details.delve-danger > summary::-webkit-details-marker { color: var(--fumble); }
.delve-danger-body { padding: 0 12px 12px; }
.delve-danger-body p { max-width: 560px; }
.delve-danger-body code { background: rgba(227,59,46,0.14); color: var(--text); padding: 1px 5px; font-family: var(--data); font-size: 0.85em; }
.delve-confirm-label { display: flex; gap: 8px; align-items: center; font-size: 0.86em; color: var(--vellum-dim); margin-bottom: 10px; flex-wrap: wrap; }
.delve-confirm-input { width: 8em; font-family: var(--body); font-size: 0.9em; padding: 4px 8px; letter-spacing: 0.08em; }
.btn-danger { background: none; color: var(--fumble); border: 1px solid var(--fumble); padding: 9px 15px; border-radius: 0; font-size: 0.62rem; }
.btn-danger:hover { background: var(--fumble); color: #fff; border-color: var(--fumble); }
.btn-danger-soft { color: var(--fumble); border-color: var(--fumble-deep); padding: 5px 11px; font-size: 0.56rem; }
.btn-danger-soft:hover { background: var(--fumble); color: #fff; border-color: var(--fumble); }
.delve-hero { border: 1px solid var(--rule); border-radius: 0; padding: 11px 13px; margin-bottom: 10px; background: var(--chassis-2); }
.delve-hero-head { display: flex; gap: 12px; align-items: center; justify-content: space-between; flex-wrap: wrap; }
.delve-hero-head form { margin: 0; }
.delve-nohistory { margin: 8px 0 0; font-size: 0.82em; }
details.delve-history { margin-top: 8px; border-top: 1px solid var(--line); padding-top: 6px; }
details.delve-history summary { cursor: pointer; user-select: none; font-size: 0.86em; color: var(--gold); }
details.delve-history summary::-webkit-details-marker { color: var(--gold-deep); }
table.delve-snap-table { width: 100%; margin-top: 8px; font-size: 0.82em; border-collapse: collapse; }
table.delve-snap-table th, table.delve-snap-table td { padding: 4px 8px; text-align: left; border-bottom: 1px solid var(--line); }
table.delve-snap-table th { color: var(--vellum-dim); font-weight: 600; }
table.delve-snap-table form { margin: 0; }
.snap-tag { font-size: 0.82em; padding: 1px 6px; border-radius: 3px; border: 1px solid var(--line); color: var(--vellum-dim); }
.snap-tag.snap-pre-reset, .snap-tag.snap-pre-restore { color: var(--oxblood); border-color: var(--oxblood); }
.btn-restore { background: none; color: var(--gold); border: 1px solid var(--gold-deep); padding: 2px 9px; border-radius: 3px; cursor: pointer; font-size: 0.82em; font-family: var(--body); letter-spacing: 0; }
.btn-restore:hover { background: rgba(216,173,62,0.12); color: var(--gold-bright); }

details.phrase-section { margin: 10px 0; border: 1px solid var(--line); border-radius: 4px; }
details.phrase-section summary { cursor: pointer; padding: 7px 12px; font-size: 0.9em; user-select: none; }
details.phrase-section summary::-webkit-details-marker { color: var(--gold-deep); }
.phrase-pool { padding: 8px 12px 12px; border-top: 1px solid var(--line); }
.phrase-row { display: flex; gap: 5px; margin-bottom: 4px; align-items: stretch; }
.phrase-row form { display: contents; }
.phrase-input { flex: 1; min-width: 0; font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace; font-size: 0.83em; padding: 3px 6px; }
.phrase-add-row { display: flex; gap: 5px; margin-top: 10px; padding-top: 8px; border-top: 1px dashed var(--line); }
.phrase-add-row .phrase-input { flex: 1; }

/* Saga "Stories" — each arc is a collapsible section; current one open. */
details.saga-arc { margin: 14px 0; }
details.saga-arc > summary {
  cursor: pointer;
  user-select: none;
  font-size: 1.17em;
  font-weight: 700;
  margin-bottom: 6px;
}
details.saga-arc > summary::-webkit-details-marker { color: var(--gold-deep); }
details.saga-arc > summary:hover { color: var(--gold); }

/* ============================================================================
   The Day's Omen — a right-margin column of living d20s (home page)
   Exists only once the first roll is cast. Strung on a faint thread down the
   page gutter; good fortune (>10.5) swells warm and glows, bad fortune flickers
   and wanes. pointer-events:none and gutter-pinned so it never covers content.
   ========================================================================== */
.dice-omen {
  position: fixed;
  top: 50%;
  right: max(12px, calc(50vw - 452px));
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(6px, 1.3vh, 14px);
  z-index: 5;
  pointer-events: none;
}
.dice-omen::before {  /* the thread the dice hang on */
  content: "";
  position: absolute;
  top: -12px; bottom: -12px; left: 50%;
  width: 1px; transform: translateX(-50%);
  background: linear-gradient(180deg, transparent, var(--line) 14%,
              var(--line) 86%, transparent);
  opacity: .7;
}
.dice-defs { position: absolute; width: 0; height: 0; }

/* Hide the gutter dice on the wide panels (Ledger, Sky & Hour): their images
   reach past the centred content column and would slide under the dice. The
   aside sits right after the tab radios so these sibling rules can reach it. */
#htab-ledger:checked ~ .dice-omen,
#htab-sky:checked ~ .dice-omen { display: none; }

.d20 {
  --size: clamp(40px, 4.6vw, 56px);
  width: var(--size); height: var(--size);
  display: block;
  color: var(--gold);
  animation: diceEnter 640ms cubic-bezier(.2,.8,.25,1.35) backwards;
  animation-delay: calc(var(--i) * 95ms);
}
.d20-svg { width: 100%; height: 100%; display: block; overflow: visible; }
.d20-edge {
  fill: var(--ink-2); stroke: currentColor;
  stroke-width: 3; stroke-linejoin: round;
}
.d20-plate { fill: currentColor; fill-opacity: .14; }
.d20-facet {
  fill: none; stroke: currentColor;
  stroke-width: 1.1; stroke-opacity: .5; stroke-linecap: round;
}
.d20-num {
  fill: var(--vellum); font-family: var(--display); font-size: 28px;
  text-anchor: middle; dominant-baseline: middle;
}

/* tiers — colour the whole die by the roll's fortune ----------------------- */
.tier-high   { color: var(--gold); }
.tier-crit   { color: var(--gold-bright); }
.tier-low    { color: var(--vellum-dim); }
.tier-fumble { color: var(--oxblood); }
.tier-high .d20-num, .tier-crit .d20-num { fill: var(--gold-bright); }
.tier-low .d20-num    { fill: var(--vellum-dim); }
.tier-fumble .d20-num { fill: var(--oxblood); }

/* good fortune swells & glows; strength rides --g (0..1) */
.tier-high .d20-svg {
  animation: emberSwell calc(2700ms - var(--g) * 1000ms) ease-in-out infinite;
  filter: drop-shadow(0 0 calc(2px + var(--g) * 9px)
          rgba(216, 173, 62, calc(.25 + var(--g) * .5)));
}
.tier-crit .d20-svg {
  animation: emberSwell 1500ms ease-in-out infinite;
  filter: drop-shadow(0 0 11px rgba(234, 198, 90, .85))
          drop-shadow(0 0 24px rgba(234, 198, 90, .45));
}
/* bad fortune flickers & wanes; deeper as --g grows */
.tier-low .d20-svg {
  animation: flickerWane calc(3400ms - var(--g) * 1500ms) steps(1, end) infinite;
}
.tier-fumble .d20-svg {
  animation: flickerWane 1700ms steps(1, end) infinite;
  filter: drop-shadow(0 0 calc(3px + var(--g) * 7px) rgba(182, 64, 58, .55));
}

/* the column takes on the day's overall mood */
.dice-omen.mood-cursed::before {
  background: linear-gradient(180deg, transparent, var(--oxblood-dk) 14%,
              var(--oxblood-dk) 86%, transparent);
}

@keyframes diceEnter {
  from { opacity: 0; transform: translateX(18px) scale(.55) rotate(-10deg); }
  to   { opacity: 1; transform: none; }
}
@keyframes emberSwell {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(calc(1 + var(--g) * 0.07)); }
}
@keyframes flickerWane {
  0%, 100% { opacity: calc(.9 - var(--g) * .3); }
  9%       { opacity: calc(.45 - var(--g) * .25); }
  11%      { opacity: calc(.85 - var(--g) * .3); }
  21%      { opacity: calc(.3  - var(--g) * .15); }
  24%      { opacity: calc(.8  - var(--g) * .3); }
  62%      { opacity: calc(.9  - var(--g) * .3); }
  65%      { opacity: calc(.4  - var(--g) * .22); }
  69%      { opacity: calc(.88 - var(--g) * .3); }
}

@media (max-width: 960px) { .dice-omen { display: none; } }
@media (prefers-reduced-motion: reduce) {
  .d20, .d20-svg { animation: none !important; }
  .tier-low .d20-svg, .tier-fumble .d20-svg { opacity: .7; }
}

/* ============================================================================
   Motion / accessibility floor
   ========================================================================== */
.omen-card, .latest-chapter, .chart-card, table.player-roll-table td { transition: background-color 140ms ease, transform 140ms ease; }
@media (prefers-reduced-motion: reduce) {
  .masthead-rule { animation: none; }
  * { transition: none !important; }
}

/* --- Uploaded images: carousel + lightbox (characters + chapters) ---------- */
section.images { margin: 1.5rem 0; }

/* Carousel: a viewport that clips a sliding flex track. Each slide is one image
   sized to never overflow the column width OR the viewport height. */
.carousel { position: relative; max-width: 640px; margin: 1.25rem auto 1.5rem; }
.carousel-viewport {
  overflow: hidden;
  border: 1px solid var(--gold-deep);
  border-radius: 3px;
  background: var(--ink);
}
.carousel-track { display: flex; transition: transform 320ms ease; }
.carousel-slide {
  flex: 0 0 100%;
  min-width: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.carousel-slide a { display: block; max-width: 100%; line-height: 0; }
.carousel-slide img {
  display: block;
  max-width: 100%;
  max-height: 68vh;
  width: auto;             /* keep aspect ratio; never upscale past intrinsic size */
  height: auto;
  cursor: zoom-in;
}
.carousel-slide figcaption {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.4rem 0.1rem 0.55rem;
  font-family: var(--body);
  font-style: italic;
  font-size: 0.9rem;
  color: var(--vellum-dim);
}
.carousel-slide .cap-text { flex: 1; }

/* Prev/next arrows + a counter overlay the viewport (only when >1 image). */
.carousel-btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.6rem;
  line-height: 1;
  color: var(--text);
  background: rgba(8, 10, 14, 0.62);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 50%;
  cursor: pointer;
}
.carousel-btn:hover { background: rgba(8, 10, 14, 0.85); color: var(--signal); }
.carousel-btn.prev { left: 12px; }
.carousel-btn.next { right: 12px; }
.carousel-counter {
  position: absolute;
  bottom: 10px;
  right: 12px;
  font-family: var(--data);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  color: var(--text);
  background: rgba(8, 10, 14, 0.66);
  padding: 3px 9px;
  border-radius: 0;
}

/* Admin-only delete control that sits on an image. */
form.img-del { display: inline; margin: 0; }
form.img-del button {
  background: none;
  border: 1px solid var(--oxblood-dk);
  color: var(--oxblood);
  border-radius: 3px;
  font-size: 0.75rem;
  line-height: 1;
  padding: 2px 6px;
  cursor: pointer;
}
form.img-del button:hover { background: var(--oxblood-dk); color: var(--vellum); }

/* Upload form. */
.image-upload { margin: 0.75rem auto 0; max-width: 640px; }
.image-upload-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}
.image-upload input[type="text"] { flex: 1 1 12rem; min-width: 0; }

/* Lightbox popout — clicking a carousel image opens it here instead of leaving. */
body.lb-lock { overflow: hidden; }
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(8, 6, 3, 0.92);
}
.lightbox.open { display: flex; }
.lb-figure {
  margin: 0;
  max-width: 92vw;
  max-height: 92vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
}
.lb-figure img {
  max-width: 92vw;
  max-height: 84vh;
  width: auto;
  height: auto;
  border: 1px solid var(--gold-deep);
  border-radius: 2px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.6);
}
.lb-figure figcaption {
  font-family: var(--body);
  font-style: italic;
  color: var(--vellum);
  text-align: center;
  max-width: 60ch;
}
.lb-close, .lb-nav {
  position: absolute;
  background: rgba(10, 8, 4, 0.55);
  border: 1px solid var(--gold-deep);
  color: var(--gold);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.lb-close:hover, .lb-nav:hover { background: rgba(10, 8, 4, 0.85); color: var(--gold-bright); }
.lb-close {
  top: 16px; right: 20px;
  width: 42px; height: 42px;
  font-size: 1.1rem;
  border-radius: 50%;
}
.lb-nav {
  top: 50%; transform: translateY(-50%);
  width: 52px; height: 52px;
  font-size: 2rem; line-height: 1;
  border-radius: 50%;
}
.lb-prev { left: 16px; }
.lb-next { right: 16px; }

@media (max-width: 720px) {
  .lb-close { top: 10px; right: 10px; width: 38px; height: 38px; }
  .lb-nav { width: 42px; height: 42px; font-size: 1.6rem; }
  .lb-prev { left: 6px; }
  .lb-next { right: 6px; }
}

/* ==========================================================================
   DICE P0RN — photographs of the actual dice.

   The one page on the site where the picture outranks the number. Everything
   else here is an instrument reading; this is the evidence. So the chassis gets
   out of the way: no panels, no borders, no cards — just the photographs on the
   graphite, with their metadata set small and quiet underneath.

   The stamp still appears, because a photo of a die that rolled a 20 is a
   different object from a photo of a die that rolled a 7.
   ========================================================================== */

.porn-head { margin: 8px 0 34px; max-width: 46rem; }
.porn-sub { margin: 6px 0 0; color: var(--text-dim); }
.porn-count {
  display: inline-block; margin-left: 6px;
  font-family: var(--data); font-size: 0.72rem; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-faint);
}
.porn-howto { font-size: 0.86rem; margin: 10px 0 0; }
.cam { font-style: normal; }

/* Section rule: a hairline with the label sitting in a notch cut out of it. */
.porn-rule {
  display: flex; align-items: center; gap: 14px;
  margin: 40px 0 20px; font-family: var(--data);
  font-size: 0.7rem; font-weight: 600; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--text-faint);
}
.porn-rule::after {
  content: ""; flex: 1; height: 1px; background: var(--rule);
}

/* --- Spotlight: what the channel voted up ------------------------------- */
.spot-row {
  display: grid; gap: 20px;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.spot { margin: 0; }
.spot img {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
  background: #000; display: block;
  /* A hairline in the accent hue is the only decoration a spotlit shot gets. */
  border: 1px solid var(--signal-lo);
}
.spot a:hover img { border-color: var(--signal); }
.spot figcaption {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px;
  margin-top: 8px; font-size: 0.82rem; color: var(--text-dim);
}
.spot-votes {
  font-family: var(--data); font-weight: 700; font-size: 0.8rem;
  color: var(--signal); white-space: nowrap;
}
.spot-cap { flex: 1 1 100%; order: 3; color: var(--text-dim); font-style: italic; }
.spot-by { white-space: nowrap; }
.spot-by a { color: var(--text); }

/* --- The archive: everything published, newest first -------------------- */
.porn-grid {
  display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
}
.porn-cell { margin: 0; position: relative; }
.porn-cell img {
  width: 100%; aspect-ratio: 1 / 1; object-fit: cover;
  background: #000; display: block; border: 1px solid var(--rule-soft);
}
.porn-cell a:hover img { border-color: var(--text-faint); }
.porn-cell figcaption {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 6px;
  margin-top: 6px; font-size: 0.75rem; color: var(--text-faint);
}
.cell-by a { color: var(--text-dim); }
.cell-by a:hover { color: var(--text); }
.cell-meta { display: flex; align-items: center; gap: 6px; font-family: var(--data); }
.cell-votes { color: var(--signal); font-weight: 600; }

/* Admin's pull-from-gallery control: invisible until the cell is hovered. */
.cell-admin { position: absolute; top: 4px; right: 4px; opacity: 0; }
.porn-cell:hover .cell-admin, .cell-admin:focus-within { opacity: 1; }
.cell-admin button {
  padding: 1px 6px; font-size: 0.8em; line-height: 1.4;
  background: rgba(14,17,22,0.85); color: var(--text-dim);
  border: 1px solid var(--rule); cursor: pointer;
}
.cell-admin button:hover { color: var(--fumble); border-color: var(--fumble-deep); }

.porn-empty { max-width: 34rem; margin-top: 30px; }

/* Admin review queue extras (the cards themselves reuse .gif-card). */
.photo-totals { font-size: 0.85em; margin-top: -8px; }
.photo-claimed { color: var(--signal); }
