/* ============================================================================
   BOMBERCUP SAGA — the campaign shell (docs/STORY_LINE.md §13).

   Layered OVER styles.css, never instead of it: the renderer's stage, the
   dialogue band, the touch pad, the boot splash and the loading cover are all
   the multiplayer client's and all unchanged. What is here is the five screens
   that build does not have.

   THE ONE RULE THIS FILE IS ORGANISED AROUND (§13.4):

       NOTHING SCROLLS, EXCEPT THE HALL OF FAME.

   Every screen below is a `100dvh` grid with `overflow: hidden`, and each one
   is laid out so its content cannot want more room than that — the menu is five
   items, the map is five nodes, a chapter is five levels ON A RAIL rather than
   in a column. The Hall is the exception and takes it deliberately: it is the
   one screen that is genuinely a list, its length is the point, and it keeps a
   fixed header and footer so only the rows ever move.
   ========================================================================== */

/* THE CLAMP IS ON THE ROOT, NOT ONLY THE BODY, and it has to be.
   `overflow: hidden` on <body> alone leaves the DOCUMENT free to grow: the
   stage's own absolutely-positioned children (`#tutdlg` sits below `#game3d`
   when it is closed) push `documentElement.scrollHeight` past the viewport, and
   what the reader gets is a page that is 168px taller than the screen with no
   way to see the bottom of it. Clamping both is the only version of "nothing
   scrolls" (§13.4) that is true of the thing being measured. */
html.saga,
body.saga {
  height: 100%;
  max-height: 100%;
  overflow: hidden;
  overscroll-behavior: none;
}

body.saga {
  /* The shell owns the viewport outright. A page that could scroll behind a
     full-bleed canvas is two coordinate systems arguing. */
  margin: 0;
  position: relative;
}

body.saga #saga-intro,
body.saga #saga-map,
body.saga #saga-hall,
body.saga #saga-settings,
body.saga #saga-extras {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: grid;
  overflow: hidden;
  color: #e8eef1;
  font-family: "Space Grotesk", system-ui, sans-serif;
  padding: max(18px, env(safe-area-inset-top)) max(18px, env(safe-area-inset-right))
           max(18px, env(safe-area-inset-bottom)) max(18px, env(safe-area-inset-left));
}

body.saga [hidden] { display: none !important; }

/* THE GROUND EVERY SCREEN BUT THE TITLE STANDS ON.
 *
 * `#saga-intro` is deliberately NOT in this list: the whole intro is a scrim
 * over the live pitch (§13.1), and it spent an afternoon rendering as a logo on
 * a void because it WAS in it. `body.saga #saga-intro` is specificity (1,1,1)
 * and the intro's own `#saga-intro { background: … }` is (1,0,0), so the opaque
 * radial in the shared block quietly won and painted over the board — a bug you
 * cannot see by reading either rule, only by reading both. Splitting the two
 * lists is the fix that cannot come back.
 */
body.saga #saga-map,
body.saga #saga-hall,
body.saga #saga-settings,
body.saga #saga-extras {
  background: radial-gradient(120% 90% at 50% 0%, #16222a 0%, #0b1114 62%, #070c0e 100%);
}

/* ---------------------------------------------------------------- intro ---
   Three rows: title, menu, footer. The stage drifts behind it, so everything
   here sits on nothing — no panel, no card, no box. */

/* THE COMPOSITION IS THREE BANDS, and the middle one belongs to the pitch.
   Centring the menu — the first arrangement — put it on the centre circle, on
   top of the one figure standing there, which is the whole photograph. Title in
   the dark at the top, ground in the middle with nothing on it, menu and footer
   in the dark at the bottom. */
#saga-intro {
  grid-template-rows: auto minmax(0, 1fr) auto auto;
  justify-items: center;
  align-content: stretch;
  padding-top: clamp(24px, 7vh, 78px);
  padding-bottom: max(14px, env(safe-area-inset-bottom));
  /* TRANSPARENT TO THE PITCH BEHIND IT. The scrim is the only paint on this
     screen and it has one job: hold the title legible over green without
     hiding the thing the title is standing on. The first version was heavy
     enough (.72 → .25 → .88) that the board read as black and the intro read
     as a logo on a void — the exact opposite of "one figure on a ground nobody
     came to", which is the whole shot.

     Darkest at the two ends, where the title and the menu sit, and almost
     nothing across the middle third, which is where the pitch is. */
  background:
    linear-gradient(180deg, rgba(6,10,12,.92) 0%, rgba(6,10,12,.84) 26%,
                    rgba(6,10,12,.46) 36%, rgba(6,10,12,.14) 48%,
                    rgba(6,10,12,.12) 58%, rgba(6,10,12,.80) 76%,
                    rgba(6,10,12,.95) 100%);
  pointer-events: none;
}
#saga-intro > * { pointer-events: auto; }

.saga-titleblock { align-self: start; text-align: center; }

/* ---- the mark -----------------------------------------------------------
   The artwork, cut out of its blue plate (`tools/src/cut-title.py`). It
   replaced a set-in-Orbitron wordmark, and the two constraints it inherits are
   the ones that title actually had:

     - IT MUST NOT WRAP OR SWALLOW THE SCREEN. Fluid width, clamped at both
       ends, and ALSO clamped by height — a 2.3:1 mark sized only by width is a
       mark that eats a short laptop viewport, and nothing on this screen is
       allowed to need a scrollbar (§13.4).
     - IT MUST READ OVER GRASS. The artwork brings its own white keyline, so
       the drop shadow here is doing what the old text-shadow did: lifting the
       block off a lit pitch without dimming it. */
.saga-mark {
  display: block;
  width: min(76vw, 760px);
  max-height: 30vh;
  height: auto;
  object-fit: contain;
  margin: 0 auto .5em;
  filter: drop-shadow(0 6px 14px rgba(0, 0, 0, .55))
          drop-shadow(0 0 34px rgba(120, 200, 255, .22));
  /* IT ARRIVES FROM THE RIGHT, and that is the artwork's instruction rather
     than a preference: the speed streaks trail off the right-hand edge, so the
     mark is drawn as something that came from there and is still slowing down.
     Sliding it up from the bottom would put the streaks at ninety degrees to
     the motion and make the logo look pasted on. */
  animation: saga-mark-in 820ms cubic-bezier(.16, .84, .28, 1) both;
}

@keyframes saga-mark-in {
  from {
    opacity: 0;
    transform: translate3d(58vw, 0, 0) scale(1.06);
  }
  60% { opacity: 1; }
  to {
    opacity: 1;
    transform: translate3d(0, 0, 0) scale(1);
  }
}

/* The subtitle follows it in, a beat later — the mark lands, then the name it
   was given. Same curve, no horizontal travel: it is not the thing that moved. */
.saga-sub,
.saga-menu,
.saga-foot {
  animation: saga-settle 620ms ease-out both;
}
.saga-sub { animation-delay: 460ms; }
.saga-menu { animation-delay: 600ms; }
.saga-foot { animation-delay: 760ms; }

@keyframes saga-settle {
  from { opacity: 0; transform: translate3d(0, 10px, 0); }
  to { opacity: 1; transform: none; }
}

/* NOTHING MOVES IF THEY ASKED FOR THAT. Two switches and they mean the same
   thing: the OS preference, and the campaign's own Settings row (§13.3), which
   is the one a player reaches for when the OS is not theirs to change. */
@media (prefers-reduced-motion: reduce) {
  .saga-mark, .saga-sub, .saga-menu, .saga-foot { animation: none; }
}
#saga-intro[data-still] .saga-mark,
#saga-intro[data-still] .saga-sub,
#saga-intro[data-still] .saga-menu,
#saga-intro[data-still] .saga-foot { animation: none; }

.saga-sub {
  font-size: clamp(12px, 1.8vw, 19px);
  letter-spacing: .34em;
  text-transform: lowercase;
  color: #d3e0e8;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .95), 0 0 12px rgba(0, 0, 0, .9);
}

.saga-menu {
  grid-row: 3;
  display: grid;
  gap: 2px;
  justify-items: start;
  margin: 0 0 clamp(10px, 2.5vh, 26px);
}

.saga-item {
  appearance: none;
  background: none;
  border: 0;
  color: #b9c9d2;
  font: inherit;
  font-size: clamp(16px, 2.4vw, 24px);
  letter-spacing: .08em;
  padding: .3em 1.1em .3em 0;
  display: flex;
  align-items: baseline;
  gap: .55em;
  cursor: pointer;
  transition: color .12s ease, transform .12s ease;
}
.saga-item i { opacity: 0; font-style: normal; transition: opacity .12s ease; }
.saga-item { text-shadow: 0 0 8px rgba(0, 0, 0, .9); }
.saga-item[data-sel] { color: #fff; transform: translateX(6px); }
.saga-item[data-sel] i { opacity: 1; color: #6fd3ff; }

.saga-foot {
  grid-row: 4;
  align-self: end;
  display: flex;
  gap: 1.4em;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: clamp(10px, 1.3vw, 13px);
  letter-spacing: .12em;
  color: #8ea3ad;
  text-transform: uppercase;
  text-shadow: 0 0 8px rgba(0, 0, 0, .9);
}
.saga-foot-run { color: #a9c3d0; }

/* ------------------------------------------------------------ world map ---
   Header, chart, card. The chart SCALES rather than reflows: same picture at
   every size, smaller. */

#saga-map { grid-template-rows: auto minmax(0, 1fr) auto; gap: clamp(10px, 2vh, 22px); }

.saga-maphead,
.saga-hallhead {
  display: flex;
  align-items: center;
  gap: 16px;
  min-height: 40px;
}
/* NOT AN <h1>. `styles.css` hangs the arena's own "ARENA" wordmark off every
   one of them (`h1::after`), which is the multiplayer product's mark and would
   sit under every heading in this one. The campaign titles its screens itself. */
.saga-hallhead .saga-h1 {
  font-family: Orbitron, "Space Grotesk", sans-serif;
  font-weight: 800;
  font-size: clamp(15px, 2.1vw, 21px);
  letter-spacing: .14em;
  text-transform: uppercase;
}
.saga-hallnote {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 11px;
  color: #5d707c;
  letter-spacing: .08em;
  margin-left: auto;
}

.saga-back {
  appearance: none;
  background: rgba(255, 255, 255, .05);
  border: 1px solid rgba(255, 255, 255, .12);
  border-radius: 999px;
  color: #b9c9d2;
  font: inherit;
  font-size: 13px;
  padding: .35em 1em;
  cursor: pointer;
}
.saga-back:hover { background: rgba(255, 255, 255, .1); color: #fff; }

/* The plinth: seven dents, and the same shape in the HUD and on a Hall row so
   nobody has to learn it twice. */
.saga-plinth { display: flex; gap: 5px; margin-left: auto; }
.saga-plinth i {
  width: 22px; height: 22px;
  display: grid; place-items: center;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .18);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 10px; font-style: normal;
  color: #45555e;
}
.saga-plinth i.on {
  border-color: #ffb648;
  color: #1a1206;
  background: radial-gradient(circle at 35% 30%, #ffd98a, #f0932b 70%);
  box-shadow: 0 0 12px rgba(240, 147, 43, .55);
}
.saga-plinth[data-small] { margin-left: 0; }
.saga-plinth[data-small] i { width: 15px; height: 15px; font-size: 8px; }

.saga-hearts { display: flex; gap: 3px; }
.saga-hearts i { font-style: normal; color: #3a464e; font-size: 16px; }
.saga-hearts i.on { color: #ff5b6e; text-shadow: 0 0 10px rgba(255, 91, 110, .5); }
.saga-hearts[data-small] i { font-size: 12px; }

.saga-chart {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: clamp(6px, 1vw, 14px);
  align-content: center;
  min-height: 0;
}

.saga-node {
  appearance: none;
  background: linear-gradient(180deg, rgba(255,255,255,.055), rgba(255,255,255,.02));
  border: 1px solid rgba(255, 255, 255, .1);
  border-radius: 14px;
  color: #cfdde4;
  font: inherit;
  padding: clamp(10px, 2vh, 20px) 10px;
  display: grid;
  gap: 6px;
  justify-items: center;
  cursor: pointer;
  transition: border-color .14s ease, transform .14s ease, background .14s ease;
}
.saga-node:hover { transform: translateY(-2px); }
.saga-node[data-sel] {
  border-color: #6fd3ff;
  background: linear-gradient(180deg, rgba(111,211,255,.16), rgba(111,211,255,.04));
}
.saga-node[data-done] .saga-numeral { color: #ffb648; }
.saga-node[data-locked] { opacity: .42; cursor: pointer; }
/* A locked node stays CLICKABLE on purpose: the card behind it says what the
   gate wants, and a place you can look at is not the same as a place you can
   go. Hiding the gate would make it read as arbitrary. */

.saga-numeral {
  font-family: Orbitron, sans-serif;
  font-size: clamp(20px, 3vw, 34px);
  line-height: 1;
  color: #7f939e;
}
.saga-place {
  font-size: clamp(11px, 1.2vw, 14px);
  text-align: center;
  line-height: 1.25;
}
.saga-nodemeta {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 10px;
  color: #64777f;
  letter-spacing: .06em;
}

.saga-card {
  display: grid;
  grid-template-columns: minmax(220px, 1fr) minmax(0, 2.2fr);
  gap: clamp(12px, 2vw, 28px);
  align-items: start;
  border-top: 1px solid rgba(255, 255, 255, .1);
  padding-top: clamp(10px, 2vh, 18px);
  min-height: 0;
}
.saga-card[data-locked] .saga-rail { opacity: .35; pointer-events: none; }
.saga-cardtext .saga-h2 {
  margin: 0 0 .4em;
  font-size: clamp(15px, 2vw, 21px);
  font-weight: 600;
  letter-spacing: .04em;
}
.saga-cardtext p { margin: 0; font-size: clamp(11px, 1.25vw, 14px); line-height: 1.5; color: #97aab4; }
.saga-gate { color: #ffb648 !important; margin-top: .6em !important; }

/* FIVE LEVELS ON A RAIL, NOT A COLUMN (§13.4). The rail is bounded by the
   chapter, so five is the ceiling by design and it never needs to scroll. */
.saga-rail {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 8px;
  min-width: 0;
}

.saga-level {
  appearance: none;
  text-align: left;
  background: rgba(255, 255, 255, .04);
  border: 1px solid rgba(255, 255, 255, .09);
  border-radius: 10px;
  color: #cfdde4;
  font: inherit;
  padding: 10px;
  display: grid;
  gap: 4px;
  align-content: start;
  cursor: pointer;
  min-width: 0;
  transition: border-color .14s ease, background .14s ease;
}
.saga-level:hover:not(:disabled) { border-color: #6fd3ff; background: rgba(111, 211, 255, .1); }
.saga-level:disabled { cursor: not-allowed; }
.saga-level[data-cleared] { border-color: rgba(255, 182, 72, .35); }
.saga-n {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 10px;
  color: #64777f;
}
.saga-ltitle { font-size: 13px; line-height: 1.2; }
.saga-lblurb {
  font-size: 10.5px;
  line-height: 1.35;
  color: #7c8f99;
  /* Clamped rather than scrolled: a blurb is one or two lines and the rail's
     height is the chapter card's, not the longest sentence somebody wrote. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.saga-lmarks { display: flex; gap: 6px; align-items: center; margin-top: 2px; }
.saga-lmarks i { font-style: normal; font-size: 11px; }
.mk-seed { color: #ffb648; text-shadow: 0 0 8px rgba(240, 147, 43, .6); }
.mk-egg { color: #6fd3ff; }
.mk-time { font-family: "JetBrains Mono", monospace; font-size: 9px; color: #5d707c; margin-left: auto; }

/* ------------------------------------------------------------- level HUD ---
   Three strips, all single-line, all fixed. */

#saga-hud {
  position: fixed;
  inset: 0;
  z-index: 30;
  pointer-events: none;
  display: grid;
  grid-template-rows: auto auto 1fr auto;
  gap: 8px;
  padding: max(10px, env(safe-area-inset-top)) 14px max(10px, env(safe-area-inset-bottom));
  font-family: "Space Grotesk", system-ui, sans-serif;
  color: #e8eef1;
}
#saga-hud button { pointer-events: auto; }

.saga-hudtop {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 12px;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .8);
}
.saga-hudn { font-family: "JetBrains Mono", monospace; font-size: 10px; color: #8fa6b2; }
.saga-hudtitle { letter-spacing: .05em; }
.saga-hudchapter { color: #7c8f99; font-size: 11px; }
.saga-hudtop .saga-plinth { margin-left: auto; }

.saga-goal {
  justify-self: center;
  background: rgba(10, 16, 19, .78);
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: 999px;
  padding: .38em 1.1em;
  font-size: 13px;
  letter-spacing: .03em;
  backdrop-filter: blur(4px);
}
.saga-goal[data-done] { border-color: #7ddc8a; color: #b8f0c0; }
/* THE EM DASH. Twenty-one levels of imperatives, and then this. It is set wide
   and quiet rather than loud — the ending is that nobody knows, not that
   something is happening. */
.saga-goal[data-blank] {
  letter-spacing: .6em;
  padding-left: 1.4em;
  color: #8fa6b2;
  border-color: rgba(255, 255, 255, .08);
}

.saga-items { grid-row: 4; display: flex; gap: 6px; justify-self: start; align-self: end; }
.saga-item-chip {
  display: flex; align-items: center; gap: 3px;
  background: rgba(10, 16, 19, .7);
  border-radius: 6px; padding: 3px 5px;
  font-size: 10px;
}
.saga-item-chip i { width: 18px; height: 18px; display: block; }

.saga-took {
  grid-row: 3;
  align-self: center;
  justify-self: center;
  display: grid;
  gap: 4px;
  justify-items: center;
  text-align: center;
  background: rgba(10, 16, 19, .86);
  border: 1px solid #ffb648;
  border-radius: 14px;
  padding: 18px 30px;
  animation: saga-pop .3s ease-out;
}
.saga-took b { font-family: Orbitron, sans-serif; letter-spacing: .2em; color: #ffb648; font-size: 14px; }
.saga-took span { font-size: 13px; }
.saga-took i { font-size: 11px; color: #8fa6b2; font-style: normal; }
@keyframes saga-pop { from { transform: scale(.9); opacity: 0; } to { transform: scale(1); opacity: 1; } }

.saga-end {
  grid-row: 3;
  align-self: center;
  justify-self: center;
  display: grid;
  gap: 12px;
  justify-items: center;
  background: rgba(8, 13, 16, .92);
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: 16px;
  padding: 26px 34px;
  max-width: min(560px, 88vw);
  text-align: center;
}
.saga-end p { margin: 0; font-size: 14px; line-height: 1.5; color: #cfdde4; }
.saga-end b { font-family: Orbitron, sans-serif; letter-spacing: .12em; }
.saga-endrow { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
.saga-endrow button,
.saga-skip {
  appearance: none;
  background: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: 999px;
  color: #e8eef1;
  font: inherit;
  font-size: 13px;
  padding: .45em 1.2em;
  cursor: pointer;
}
.saga-endrow button:hover, .saga-skip:hover { background: rgba(255, 255, 255, .14); }

.saga-hudbottom { grid-row: 4; display: flex; gap: 8px; justify-self: end; align-self: end; }
.saga-skip { font-size: 11px; padding: .3em .9em; opacity: .75; }

/* -------------------------------------------------------- hall of fame ---
   THE ONE EXCEPTION (§13.5). Fixed header, fixed footer, scrolling rows. */

#saga-hall { grid-template-rows: auto minmax(0, 1fr) auto; gap: 14px; }

.saga-hallbody {
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: 6px;
}

.saga-runs, .saga-bests {
  width: 100%;
  border-collapse: collapse;
  font-size: 12.5px;
}
.saga-runs th, .saga-bests th {
  text-align: left;
  font-family: "JetBrains Mono", monospace;
  font-size: 10px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: #64777f;
  font-weight: 500;
  padding: 6px 8px;
  border-bottom: 1px solid rgba(255, 255, 255, .12);
  /* The column heads ride with the rows rather than leaving with them: the
     header of the screen is fixed, and so is the header of the list. */
  position: sticky;
  top: 0;
  background: #0b1114;
}
.saga-runs td, .saga-bests td {
  padding: 7px 8px;
  border-bottom: 1px solid rgba(255, 255, 255, .05);
  color: #b9c9d2;
}
.saga-runs tr[data-live] td { color: #6fd3ff; }
.saga-bests tr:not([data-done]) td { color: #4c5c65; }
.saga-runs td:first-child { white-space: nowrap; }
.saga-runs td .saga-plinth { display: inline-flex; margin: 0 6px 0 0; vertical-align: middle; }
.saga-seedn { font-family: "JetBrains Mono", monospace; color: #ffb648; }
.saga-result { font-family: "JetBrains Mono", monospace; letter-spacing: .2em; }

.saga-halfway {
  margin: 26px 0 8px;
  font-size: 12px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: #64777f;
}
.saga-empty { color: #7c8f99; font-size: 13px; }
.saga-hallfoot {
  font-family: "JetBrains Mono", monospace;
  font-size: 10px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #4c5c65;
  border-top: 1px solid rgba(255, 255, 255, .08);
  padding-top: 10px;
}

/* ---------------------------------------------------------- settings ---- */

#saga-settings { grid-template-rows: auto minmax(0, 1fr); gap: 18px; }
.saga-set { display: grid; gap: 14px; align-content: start; max-width: 640px; }
.saga-row { display: flex; align-items: center; gap: 14px; font-size: 14px; }
.saga-row > span:first-child { min-width: 150px; color: #b9c9d2; }
.saga-seg { display: flex; gap: 4px; flex-wrap: wrap; }
.saga-seg button {
  appearance: none;
  background: rgba(255, 255, 255, .05);
  border: 1px solid rgba(255, 255, 255, .12);
  border-radius: 8px;
  color: #97aab4;
  font: inherit;
  font-size: 12px;
  padding: .35em .9em;
  cursor: pointer;
}
.saga-seg button[data-on] { background: rgba(111, 211, 255, .18); border-color: #6fd3ff; color: #fff; }
.saga-danger .saga-seg button { border-color: rgba(255, 91, 110, .4); color: #ff8f9c; }
.saga-note { font-size: 12px; color: #64777f; line-height: 1.6; margin-top: 8px; }

/* ------------------------------------------------------------ extras ---- */

#saga-extras { grid-template-rows: auto minmax(0, 1fr) auto; gap: 16px; }
.saga-extrabody {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 18px;
  align-content: start;
  min-height: 0;
}
.saga-dents { display: grid; gap: 5px; align-content: start; }
.saga-dent {
  display: flex; gap: 10px; align-items: baseline;
  border: 1px solid rgba(255, 255, 255, .08);
  border-radius: 8px; padding: 6px 10px;
  opacity: .35; font-size: 12px;
}
.saga-dent[data-on] { opacity: 1; border-color: rgba(255, 182, 72, .5); }
.saga-dent b { color: #ffb648; letter-spacing: -1px; }
.saga-pets { display: grid; gap: 8px; align-content: start; }
.saga-pet {
  border-left: 3px solid var(--lc, #445);
  padding: 4px 0 4px 10px;
  opacity: .35;
  display: grid; gap: 2px;
}
.saga-pet[data-on] { opacity: 1; }
.saga-pet b { font-size: 14px; color: var(--lc); }
.saga-pet span { font-size: 12px; color: #b9c9d2; }
.saga-pet i { font-size: 11px; color: #64777f; }
.saga-table { display: grid; gap: 8px; align-content: start; }
.saga-table b { font-family: Orbitron, sans-serif; letter-spacing: .2em; font-size: 12px; color: #64777f; }
.saga-table ol { list-style: none; margin: 0; padding: 0; display: grid; gap: 3px; }
.saga-table li {
  display: grid; grid-template-columns: 24px 1fr auto;
  gap: 8px; font-size: 12px;
  font-family: "JetBrains Mono", monospace;
  padding: 5px 8px;
  border-bottom: 1px solid rgba(255, 255, 255, .06);
  color: #7c8f99;
}
.saga-table li:first-child { color: #6fd3ff; border-color: rgba(111, 211, 255, .35); }
.saga-table p { font-size: 12px; line-height: 1.6; color: #97aab4; margin: 6px 0 0; }

.saga-louies { display: flex; gap: 8px; }
.saga-louies span { font-size: 11px; color: #445; }
.saga-louies span.on { color: var(--lc); }

/* ------------------------------------------------------------ transit ---
   THE FIXTURE CARD, between two levels (docs/STORY_LINE.md §13.6).

   Presented as THE ORACLE'S OWN BROADCAST GRAPHIC, which is what licenses a
   panel this dense on a shell whose rule is that nothing scrolls: a match-day
   card with a route, a form line and a price on it is a thing the player has
   seen a thousand of, so it is read at a glance rather than parsed. The joke is
   that it is put out before a fixture nobody will watch, by the thing that
   already published the result.

   ONE CLOCK, HANDED DOWN. Every duration below is `--ms`, written on the root
   by React from the driver's own phase length (campaign/transit.ts). Nothing in
   this file may hard-code a number that a `setTimeout` in that one also knows —
   a wipe that finishes after the level behind it has already changed is the
   single failure mode this whole screen has, and sharing the variable is what
   makes it unreachable. */

#saga-transit {
  position: fixed; inset: 0;
  /* Above the saga screens (40), the level HUD (30) and the client's own
     loading cover (15) — the next level flashes GET READY under this and must
     not be seen doing it. Below the boot splash (100), which outranks the
     entire app by design. */
  z-index: 70;
  display: grid; place-items: center;
  overflow: hidden;
  color: #e8eef1;
  font-family: "Space Grotesk", system-ui, sans-serif;
  padding: max(16px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right))
           max(16px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left));
  /* The whole cover is one big button. A player who wants on with it should not
     have to find the small one in the corner. */
  cursor: pointer;
}

/* THE SHUTTERS. Two panels closing on the horizon and opening off it, rather
   than one opacity fade: a cross-fade between two boards is a smear, and two
   hard edges meeting in the middle read as a cut that took its time — which is
   the one thing a cut cannot do. They carry the ground the card is read on, so
   there is no third element painting a background nobody can see move. */
.tr-shut {
  position: absolute; left: 0; right: 0;
  /* A PIXEL OF OVERLAP RATHER THAN AN EXACT HALF. Two panels at 50% meet on a
     boundary that subpixel rounding puts a hairline of live board through at
     some viewport heights, and a one-pixel window onto the level you are
     leaving is the only thing that could give this away as two elements. The
     later panel wins the seam, so what shows is one amber line and not two. */
  height: calc(50% + 1px);
  will-change: transform;
  /* THE SEAM IS LIT WHILE THE SHUTTERS ARE MOVING AND DARK WHILE THEY ARE NOT.
     A hairline that stays up under the card runs straight through the gaps
     between its rows and reads as a rule somebody forgot to remove — it is
     scenery for the wipe, not part of the graphic. */
  border-color: transparent;
  transition: border-color .28s ease;
}
/* Each half is lit at the seam and falls away from it, so the two panels read
   as one ground with the light in the middle rather than as two gradients that
   happen to be touching. */
.tr-shut-a {
  top: 0; border-bottom: 1px solid transparent;
  background: radial-gradient(120% 140% at 50% 100%, #16222a 0%, #0b1114 60%, #05090b 100%);
}
.tr-shut-b {
  bottom: 0; border-top: 1px solid transparent;
  background: radial-gradient(120% 140% at 50% 0%, #16222a 0%, #0b1114 60%, #05090b 100%);
}
#saga-transit[data-phase="seal"] .tr-shut,
#saga-transit[data-phase="board"] .tr-shut { border-color: rgba(255, 182, 72, .55); }

#saga-transit[data-phase="seal"] .tr-shut-a { animation: tr-close-a var(--ms) cubic-bezier(.66, 0, .18, 1) both; }
#saga-transit[data-phase="seal"] .tr-shut-b { animation: tr-close-b var(--ms) cubic-bezier(.66, 0, .18, 1) both; }
#saga-transit[data-phase="board"] .tr-shut-a { animation: tr-open-a var(--ms) cubic-bezier(.7, 0, .2, 1) both; }
#saga-transit[data-phase="board"] .tr-shut-b { animation: tr-open-b var(--ms) cubic-bezier(.7, 0, .2, 1) both; }

/* A VENUE CHANGE IS ANNOUNCED ON THE SEAM, before the plate below is read. The
   shutters meet on an amber hairline every crossing; on the four that change
   chapter it flares as they close. That is the whole of the announcement — a
   sting and a bigger typeface would be two things saying the same thing. */
#saga-transit[data-chapter][data-phase="seal"] .tr-shut,
#saga-transit[data-chapter][data-phase="board"] .tr-shut {
  border-color: #ffe27a;
  box-shadow: 0 0 46px rgba(255, 226, 122, .35);
}

@keyframes tr-close-a { from { transform: translateY(-101%); } to { transform: none; } }
@keyframes tr-close-b { from { transform: translateY(101%); } to { transform: none; } }
@keyframes tr-open-a { from { transform: none; } to { transform: translateY(-101%); } }
@keyframes tr-open-b { from { transform: none; } to { transform: translateY(101%); } }

/* ---- the card itself ---- */

.tr-body {
  position: relative;
  width: min(1000px, 100%);
  max-height: 100%;
  display: grid;
  gap: clamp(8px, 1.5vh, 16px);
  align-content: center;
  /* §13.4 applies here too. The card is built to fit rather than to reflow, and
     the two media queries at the bottom drop the least load-bearing rows on a
     short viewport instead of letting it grow a scrollbar. */
  overflow: hidden;
}

#saga-transit[data-phase="seal"] .tr-body { opacity: 0; pointer-events: none; }
#saga-transit[data-phase="card"] .tr-body { animation: tr-rise .42s cubic-bezier(.2, .8, .3, 1) both; }
#saga-transit[data-phase="board"] .tr-body { animation: tr-sink var(--ms) ease-in both; }

@keyframes tr-rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@keyframes tr-sink { from { opacity: 1; } 60%, to { opacity: 0; transform: translateY(-10px); } }

/* THE STAGGER IS ON THE CARD'S CHILDREN and it is the difference between a
   panel that appeared and a panel that was BUILT in front of you. Six rows,
   70 ms apart, done inside half a second — long enough to read as assembly and
   short enough that nobody waiting on it notices they are waiting. */
#saga-transit[data-phase="card"] .tr-body > * { animation: tr-rise .38s cubic-bezier(.2, .8, .3, 1) both; }
#saga-transit[data-phase="card"] .tr-body > :nth-child(1) { animation-delay: .04s; }
#saga-transit[data-phase="card"] .tr-body > :nth-child(2) { animation-delay: .11s; }
#saga-transit[data-phase="card"] .tr-body > :nth-child(3) { animation-delay: .18s; }
#saga-transit[data-phase="card"] .tr-body > :nth-child(4) { animation-delay: .25s; }
#saga-transit[data-phase="card"] .tr-body > :nth-child(5) { animation-delay: .32s; }
#saga-transit[data-phase="card"] .tr-body > :nth-child(6) { animation-delay: .39s; }
#saga-transit[data-phase="card"] .tr-body > :nth-child(n + 7) { animation-delay: .46s; }

/* ---- the chapter plate, only when the venue changes ---- */

.tr-arrival {
  display: grid; grid-template-columns: auto 1fr; gap: clamp(12px, 2vw, 22px);
  align-items: center;
  border-left: 3px solid #ffb648;
  padding: clamp(6px, 1.4vh, 14px) 0 clamp(6px, 1.4vh, 14px) clamp(12px, 2vw, 20px);
  background: linear-gradient(90deg, rgba(255, 182, 72, .12), transparent 62%);
}
.tr-arrival > b {
  font-family: Orbitron, sans-serif; font-weight: 900;
  font-size: clamp(30px, 6vw, 62px); line-height: .9; color: #ffb648;
  text-shadow: 0 0 30px rgba(255, 182, 72, .45);
}
.tr-arrival strong {
  display: block;
  font-family: Orbitron, sans-serif; letter-spacing: .1em;
  font-size: clamp(13px, 2vw, 20px);
}
.tr-arrival p {
  margin: .45em 0 0; font-size: clamp(11px, 1.2vw, 13px);
  line-height: 1.55; color: #97aab4;
}

/* ---- the route ---- */

.tr-route {
  display: grid; grid-template-columns: 1fr minmax(120px, 1.15fr) 1fr;
  gap: clamp(10px, 2vw, 24px); align-items: center;
  border-block: 1px solid rgba(255, 255, 255, .1);
  padding-block: clamp(8px, 1.6vh, 16px);
}
.tr-side { display: grid; gap: 2px; min-width: 0; }
.tr-side[data-side="to"] { text-align: right; }
.tr-kicker {
  font-family: Orbitron, sans-serif; font-size: 9px; letter-spacing: .3em;
  color: #64777f;
}
.tr-side[data-side="to"] .tr-kicker { color: #6fd3ff; }
.tr-n {
  font-family: "JetBrains Mono", monospace; font-weight: 700;
  font-size: clamp(20px, 3.4vw, 34px); line-height: 1; color: #55666f;
}
.tr-side[data-side="to"] .tr-n { color: #6fd3ff; text-shadow: 0 0 22px rgba(111, 211, 255, .4); }
/* A destination you cannot reach yet is not the accent colour of "next". The
   whole right-hand side goes over to the colour of the sentence underneath it,
   so the card says one thing rather than two. */
#saga-transit[data-blocked] .tr-side[data-side="to"] .tr-kicker,
#saga-transit[data-blocked] .tr-side[data-side="to"] .tr-n { color: #ffb648; }
#saga-transit[data-blocked] .tr-side[data-side="to"] .tr-n { text-shadow: 0 0 22px rgba(255, 182, 72, .35); }
.tr-title {
  font-family: Orbitron, sans-serif; font-size: clamp(12px, 1.7vw, 17px);
  letter-spacing: .04em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tr-side[data-side="from"] .tr-title { color: #9db0ba; }
.tr-place { font-size: 11px; color: #64777f; }

/* THE RAIL IS THE WHOLE CAMPAIGN, twenty-two pips, and it is the same rail
   every crossing. That is the point of drawing it rather than printing "8 / 22":
   mostly unlit in chapter I and mostly lit in chapter V is a sentence about how
   far you have come that nobody has to read. */
.tr-rail { display: grid; gap: 6px; justify-items: center; position: relative; }
.tr-pips { display: flex; gap: 3px; align-items: flex-end; width: 100%; justify-content: center; }
.tr-pips i {
  width: 3px; height: 9px; border-radius: 1px; background: #26323a;
  transition: background .3s ease, height .3s ease;
}
.tr-pips i[data-on] { background: #ffb648; }
.tr-pips i[data-next] {
  height: 15px; background: #6fd3ff;
  box-shadow: 0 0 10px rgba(111, 211, 255, .8);
  animation: tr-pip 1.1s ease-in-out infinite;
}
@keyframes tr-pip { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
.tr-railn {
  font-family: "JetBrains Mono", monospace; font-size: 10px; color: #64777f;
  letter-spacing: .1em;
}
/* One spark crossing the rail, timed to the dwell. It is the only thing on the
   card that says which direction the journey goes. */
#saga-transit[data-phase="card"] .tr-rail::after {
  content: ""; position: absolute; left: 0; right: 0; top: 3px; height: 1px;
  background: linear-gradient(90deg, transparent, #6fd3ff, transparent);
  background-size: 28% 100%;
  background-repeat: no-repeat;
  /* MOVED BY BACKGROUND-POSITION AND NOT BY TRANSFORM, because the travel has
     to be the width of the RAIL and a transform percentage is the width of the
     spark — the first version crossed 56 pixels of a 132-pixel rail and read as
     a flicker at the left-hand end. */
  animation: tr-spark var(--ms) cubic-bezier(.5, 0, .5, 1) both;
}
@keyframes tr-spark {
  from { background-position: -40% 0; opacity: 0; }
  15% { opacity: 1; }
  75%, to { background-position: 140% 0; opacity: 0; }
}

/* ---- what the next one is, and what the last one cost ---- */

.tr-blurb {
  margin: 0; font-size: clamp(12px, 1.4vw, 15px); line-height: 1.5; color: #cfdde4;
}
.tr-blocked {
  margin: 0; font-size: clamp(12px, 1.4vw, 15px); line-height: 1.5;
  color: #ffb648;
  border-left: 3px solid rgba(255, 182, 72, .55); padding-left: 12px;
}

.tr-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.tr-chip {
  display: flex; align-items: baseline; gap: 7px;
  border: 1px solid rgba(255, 255, 255, .1); border-radius: 6px;
  padding: 4px 9px;
  background: rgba(255, 255, 255, .03);
}
.tr-chip i {
  font-style: normal; font-family: Orbitron, sans-serif;
  font-size: 8px; letter-spacing: .24em; color: #64777f;
}
.tr-chip b { font-family: "JetBrains Mono", monospace; font-size: 12px; }
.tr-chip[data-warn] b { color: #ff8f9c; }
.tr-chip[data-good] { border-color: rgba(125, 220, 138, .45); }
.tr-chip[data-good] b { color: #7ddc8a; }
.tr-chip[data-quest] { border-color: rgba(255, 182, 72, .5); }
.tr-chip[data-quest] b { color: #ffb648; }

/* ---- the official on duty at the destination ---- */

.tr-auditor {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  font-size: 12px; color: #97aab4;
}
.tr-auditor i {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--ink, #ffb648);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ink, #ffb648) 22%, transparent);
  flex: none;
}
.tr-auditor b { color: var(--ink, #ffb648); font-family: Orbitron, sans-serif; font-size: 11px; letter-spacing: .1em; }
.tr-auditor em {
  font-style: normal; font-family: Orbitron, sans-serif; font-size: 8px;
  letter-spacing: .26em; color: #05090b; background: var(--ink, #ffb648);
  border-radius: 3px; padding: 2px 6px;
}
/* Only the FIRST venue that sends a given official gets the flash. Five
   Auditors across twenty-two levels means the badge would otherwise be on
   seventeen cards that had nothing new to announce. */
.tr-auditor:not([data-fresh]) em { display: none; }
#saga-transit[data-phase="card"] .tr-auditor[data-fresh] i { animation: tr-ping 1.4s ease-out infinite; }
@keyframes tr-ping {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--ink, #ffb648) 55%, transparent); }
  70%, 100% { box-shadow: 0 0 0 12px transparent; }
}

/* ---- the two voices (docs/STORY_LINE.md §12) ---- */

.tr-who {
  font-family: Orbitron, sans-serif; font-size: 8.5px; letter-spacing: .3em;
  color: #64777f; display: block; margin-bottom: 3px;
}
.tr-say { border-left: 2px solid rgba(111, 211, 255, .5); padding-left: 12px; }
.tr-say p { margin: 0; font-size: clamp(12px, 1.45vw, 16px); line-height: 1.5; }

/* THE ORACLE IS A PRINTOUT AND IT IS TYPESET AS ONE. Mono, uppercase, on a
   ground with a hairline round it — it is not speaking to you and the styling
   has to say so before the words do. There is no portrait here for the same
   reason `ORACLE_HEAD` is null in story.ts. */
.tr-file {
  border: 1px solid rgba(255, 255, 255, .09);
  border-radius: 6px;
  padding: 8px 12px;
  background:
    repeating-linear-gradient(180deg, rgba(255, 255, 255, .022) 0 1px, transparent 1px 3px),
    rgba(0, 0, 0, .28);
}
.tr-file p {
  margin: 0;
  font-family: "JetBrains Mono", monospace;
  font-size: clamp(10px, 1.1vw, 12px); line-height: 1.65;
  letter-spacing: .02em; color: #8fa6b2;
}

/* ---- the foot: the price, the button, the clock ---- */

.tr-foot {
  display: flex; align-items: center; gap: clamp(10px, 2vw, 20px); flex-wrap: wrap;
  border-top: 1px solid rgba(255, 255, 255, .1);
  padding-top: clamp(8px, 1.4vh, 14px);
  position: relative;
}
.tr-price { display: grid; grid-template-columns: auto auto; gap: 0 10px; align-items: baseline; }
.tr-price i {
  grid-column: 1 / -1;
  font-style: normal; font-family: Orbitron, sans-serif;
  font-size: 8px; letter-spacing: .3em; color: #64777f;
}
.tr-price b {
  font-family: "JetBrains Mono", monospace; font-weight: 700;
  font-size: clamp(18px, 2.6vw, 26px); line-height: 1; color: #e8eef1;
}
.tr-price span { font-family: Orbitron, sans-serif; font-size: 9px; letter-spacing: .2em; color: #8fa6b2; }
/* THE TABLE STOPS BEING ABLE TO ANSWER, and the card marks the moment. Every
   other price on every other crossing is printed in the same colour as the rest
   of the readout, because a forecast is not news; the dash at seven Seeds is
   the one number on this screen that takes the accent, because it is the whole
   campaign in one character. */
.tr-price[data-drift] b, .tr-price[data-drift] span { color: #ffb648; }

.tr-go {
  margin-left: auto;
  font: inherit; font-size: clamp(12px, 1.3vw, 14px);
  color: #05090b; background: #6fd3ff; border: 0;
  border-radius: 999px; padding: .55em 1.5em; cursor: pointer;
  letter-spacing: .02em; font-weight: 600;
  transition: transform .12s ease, box-shadow .2s ease;
  box-shadow: 0 0 0 0 rgba(111, 211, 255, .5);
}
.tr-go:hover { transform: translateY(-1px); box-shadow: 0 6px 26px rgba(111, 211, 255, .28); }
#saga-transit[data-blocked] .tr-go { background: #ffb648; }

/* THE AUTO-ADVANCE BAR IS THE DRIVER'S CLOCK DRAWN, not a second one: its
   duration is `--ms` and React keys it on the phase, so a press that cuts the
   dwell short restarts the bar rather than leaving it stranded mid-run. It is
   also the whole of the promise this screen makes — you do not have to do
   anything, and you can see that you do not have to do anything. */
.tr-auto {
  position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: rgba(255, 255, 255, .07); overflow: hidden;
}
.tr-auto i { display: block; height: 100%; background: #6fd3ff; transform-origin: 0 50%; }
#saga-transit[data-phase="card"] .tr-auto i { animation: tr-bar var(--ms) linear both; }
#saga-transit:not([data-phase="card"]) .tr-auto { display: none; }
@keyframes tr-bar { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* REDUCED MOTION TAKES THE MOTION AND LEAVES THE READING. The shutters still
   move — a cover that appears in one frame is a flash, which is the other thing
   this preference exists to prevent — but the driver has already shortened
   them (`STILL_SEAL_MS`), so what is left is a wipe and nothing that pulses,
   pings, sparks or assembles itself. The dwell is untouched: a paragraph is
   content, and taking it away faster is a smaller game, not an accommodation. */
@media (prefers-reduced-motion: reduce) {
  #saga-transit .tr-body,
  #saga-transit .tr-body > *,
  #saga-transit .tr-pips i[data-next],
  #saga-transit .tr-auditor[data-fresh] i { animation: none !important; }
  #saga-transit[data-phase="card"] .tr-rail::after { display: none; }
  .tr-go { transition: none; }
}

/* --------------------------------------------------------------- phone ---
   The compact shell already gives the pad everything below --stage-pct, so the
   screens shrink INTO the space above it rather than sliding under it. */

@media (max-width: 720px) {
  .saga-chart { grid-template-columns: repeat(5, 1fr); gap: 4px; }
  .saga-place { font-size: 9.5px; }
  .saga-nodemeta { display: none; }
  .saga-card { grid-template-columns: 1fr; gap: 10px; }
  .saga-rail { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 4px; }
  .saga-lblurb { display: none; }
  .saga-ltitle { font-size: 11px; }
  .saga-hudchapter { display: none; }
  .saga-row { flex-wrap: wrap; }

  /* The card is the one screen with more rows than a phone has room for. The
     order it sheds them in is the order they matter least: the venue's own
     paragraph (the map says it too), then the Auditor's line, then the
     destination blurb. The route, the two voices and the price never go —
     those are what the crossing IS. */
  .tr-route { grid-template-columns: 1fr auto 1fr; gap: 8px; }
  .tr-arrival p { display: none; }
  .tr-auditor span { display: none; }
  .tr-pips { gap: 2px; }
  .tr-pips i { width: 2px; }
  .tr-foot { gap: 10px; }
  .tr-go { margin-left: 0; width: 100%; text-align: center; }
}

/* A LANDSCAPE PHONE is the hard case: 380 logical pixels of height for a card
   with eight rows in it. Same order of sacrifice as above, one step further —
   and the two voices survive, because a transit with no dispatch on it is a
   loading screen with statistics. */
@media (max-height: 560px) {
  .tr-blurb, .tr-auditor, .tr-arrival p { display: none; }
  .tr-body { gap: 7px; }
  .tr-chips { gap: 4px; }
  .tr-file p { font-size: 10px; line-height: 1.5; }
}
