/* ============================================================
   INJAZ — "2,480"
   rail.css — the three ?rail= modes. See rail.js for why these three.

   Loaded always, active never unless ?rail= names a mode: every rule
   here hangs off body[data-rail="…"] or a class rail.js adds. With no
   argument this file changes nothing, and Saurabh's rail is exactly
   Saurabh's rail. That is the control, and it has to stay untouched to
   be worth anything as one.

   All three inherit the existing .rail contract — .active for the
   current chapter, .on-light over the pale chapters — because main.js
   already sets both and neither is worth reimplementing three times.
   ============================================================ */

/* ============================================================
   ATLAS — navigate by picture. Lives in the margin that already exists.
   ============================================================ */

/* 44px wide against the chips' 30. The left edge moves 35 → 22, so the
   right edge lands at 66 and the copy column at x=86 is untouched: the
   atlas costs the layout nothing, which is its whole argument. */
.rail--atlas{ left:clamp(10px, 1.6vw, 22px); gap:10px; }
.rail--atlas .rail-item{ width:44px; flex-direction:column; align-items:center; gap:2px; }

.rail-mini{
  width:44px; height:34px; display:block; flex:none;
  border-radius:3px;
  background:rgba(255,255,255,.045);
  outline:1px solid rgba(255,255,255,.10); outline-offset:0;
  transition:background .4s ease, outline-color .4s ease, transform .3s ease;
}
.rail--atlas .rail-item.active .rail-mini{
  background:rgba(255,255,255,.14);
  outline-color:rgba(255,255,255,.55);
}
.rail--atlas .rail-item:hover .rail-mini{ transform:translateX(2px); }

/* The number stops being the thing and becomes the caption for it —
   still there, because "03" is the language the client's ask was in. */
.rail-num--cap{
  width:auto !important; height:auto !important; flex:none;
  border:0 !important; background:none !important; border-radius:0 !important;
  font-family:var(--sans); font-size:8.5px; font-weight:700; letter-spacing:.1em;
  color:rgba(255,255,255,.6) !important;
  line-height:1;
}
.rail--atlas .rail-item.active .rail-num--cap{ color:#fff !important; }
.rail--atlas .rail-label{ left:calc(100% + 8px); }

/* the pale chapters */
.rail--atlas.on-light .rail-mini{ background:rgba(34,64,77,.04); outline-color:rgba(34,64,77,.14); }
.rail--atlas.on-light .rail-item.active .rail-mini{ background:rgba(34,64,77,.10); outline-color:rgba(34,64,77,.5); }
.rail--atlas.on-light .rail-num--cap{ color:rgba(34,64,77,.6) !important; }
.rail--atlas.on-light .rail-item.active .rail-num--cap{ color:var(--ink) !important; }

/* ============================================================
   STEPPER — the PDF's five-step bar. The top becomes a header.

   The plan was "take the 108px across the top", and the 108px was not
   free: it was measured as clearance above the COPY column, and the band
   has furniture in it. The brand (.scene-logos, top:48px) sat exactly
   where "01 BELIEF" landed.

   But the brand is only on screen at T≈0 — it fades with the title card
   by T=0.1 and never comes back. So the collision is a title-card
   problem, not a story-long one, and it must be paid for like one:
   indenting the whole bar past the brand would buy 10% of the story a
   clean corner and charge the other 90% a dead 215px at the head of the
   axis, which is the part of the bar that means "the beginning". The bar
   runs the full width; the brand steps below it while it is visible.

   The mode's real price is still the top band — the field loses its top
   edge, permanently, to a header. That is the convention the client's own
   report uses (brand, five steps across, progress underneath), which is
   the whole argument for this direction, and it is the thing to judge.

   Below the bar the first real content is at y=112 (the NEET labels), so
   the header gets ~56px and still clears everything.
   ============================================================ */

.rail--hidden{ display:none; }

.stepper{
  position:fixed; top:0; left:0; right:0; z-index:40;
  padding:18px clamp(16px,2.4vw,38px) 0;
  opacity:0; transition:opacity .6s ease;
  pointer-events:none;
}
body.motion .stepper{ opacity:1; }
.stepper > *{ pointer-events:auto; }

/* The brand steps down under the bar's rule, rather than being run over
   by it. Only ever seen on the title card, so this costs one screen. */
body[data-rail="stepper"] .scene-logos{ top:76px; }
/* Demo chrome, not the product — but it owns the top-right corner this
   bar now runs into, so it steps down out of the header too. */
body[data-rail="stepper"] .concept-badge{ top:66px; }

/* Indented past the brand (which ends at x=187), so the row and the rule
   under it start clear of it and read as the header's second column. */
/* NOTHING HORIZONTAL ON THE STEP. Not gap, not padding.

   Both steal from the axis in the same way, and both are easy to miss.
   A gap belongs to no chapter, so 78px of it pushed the start of "03"
   13px past the point where chapter 3 actually begins. Padding is worse
   because it looks safe: with flex-basis:0 and border-box, each item is
   floored at its own padding, so 14px × 7 came off the top before the
   shares were distributed and act3 drew 29.2% of a story it owns 30.6%
   of. The fill is at the true T either way, so both make the bar lie.

   Zero gap, zero horizontal padding: the step boxes tile the track
   exactly, each one spanning its chapter and nothing else. Separation is
   free anyway — the labels are left-aligned and every one of them is far
   narrower than its region, so the tightest air between two names is
   ~60px. Only the clip backstop needs padding, and that goes on the
   label inside, where the axis cannot feel it. */
.stepper-row{
  display:flex; align-items:center; gap:0;
}
.step{
  display:flex; align-items:baseline; gap:7px;
  background:none; border:0; padding:5px 0; cursor:pointer;
  font-family:var(--sans); white-space:nowrap;
  opacity:.42; transition:opacity .35s ease;
  /* flex-grow is set per-step in rail.js = the chapter's share of T.
     basis 0 so the widths are that share and nothing else. */
  flex-shrink:1; flex-basis:0; min-width:0;
  justify-content:flex-start;      /* the left edge IS the chapter's start */
}
.step:hover{ opacity:.85; }
.step.done{ opacity:.62; }
.step.active{ opacity:1; }

/* Progress under the steps, not inside them: T is continuous and the
   steps are not. Same left/right edges as the row above it, so the
   fill's leading edge always lands inside the step it names. */
.stepper-track{
  height:1px; background:rgba(255,255,255,.16); position:relative; overflow:hidden;
  transition:background .4s ease;
  margin:12px 0 0;
}
.stepper-fill{
  position:absolute; inset:0; background:#fff;
  transform-origin:0 50%; transform:scaleX(0);
  transition:background .4s ease;
}
.step-num{
  font-size:10px; font-weight:700; letter-spacing:.1em;
  color:rgba(255,255,255,.75); transition:color .4s ease;
}
.step-name{
  font-size:11px; font-weight:700; letter-spacing:.09em; text-transform:uppercase;
  color:#fff; transition:color .4s ease;
}
.step.active .step-num{ color:var(--gold); }

/* The five factors get names; Arrival and the Epilogue get a tick. They
   are reachable, and they are not dressed as things the client asked to
   navigate, because they are not those things. They still hold their own
   share of the axis — they are 8.3% and 5.6% of the story and the bar is
   not allowed to round that to nothing. */
.step--end{ padding:5px 0; }
.step-dot{
  display:block; width:5px; height:5px; border-radius:50%;
  background:rgba(255,255,255,.6); transition:background .4s ease;
}
.step--end.active .step-dot{ background:var(--gold); }
/* Backstop, on the label rather than the step, so the axis cannot feel
   it. The widest name is 144px and the tightest share at 1280 is ~96px,
   so this should never fire — but a clipped name beats a bar that
   quietly stops being a time axis because one label pushed it wider. */
.step-name{ overflow:hidden; text-overflow:ellipsis; padding-right:14px; }

.stepper.on-light .stepper-track{ background:rgba(34,64,77,.16); }
.stepper.on-light .stepper-fill{ background:var(--ink); }
.stepper.on-light .step-num{ color:rgba(34,64,77,.75); }
.stepper.on-light .step-name{ color:var(--ink); }
.stepper.on-light .step-dot{ background:rgba(34,64,77,.6); }
.stepper.on-light .step.active .step-num,
.stepper.on-light .step--end.active .step-dot{ color:var(--ink); background:var(--ink); }
.stepper.on-light .step.active .step-num{ background:none; }

/* ============================================================
   SUMMON — nothing at rest, the whole menu on intent.
   ============================================================ */

/* Quieter than the control at rest: the chips stop pretending to be
   readable and become what they actually are — a handle. */
.rail--summon .rail-item{ opacity:.3; }
.rail--summon .rail-item.active{ opacity:.85; }
.rail--summon .rail-num{ font-size:10px; }
/* the hover-pill is the thing being replaced; it must not fire too */
.rail--summon .rail-item:hover .rail-label{ opacity:0; }
body.summon-open .rail{ opacity:0; }

/* A PANEL, NOT A HOVER-PILL IN A BIGGER BOX.

   This was first written with no background and a 3px backdrop-filter on
   each item, on the theory that the story should stay visible behind the
   menu. On screen it was unreadable: the title card's white display type
   ran straight through the words, and the menu turned to grey mush over
   the dot field. That is the exact failure the hover-only label already
   has — text with nothing behind it — just bigger.

   So the panel commits. It is opaque enough to read against the darkest
   field and the brightest headline alike, and the story stays live around
   it rather than through it. An overlay that will not cover anything is
   not buying the space it costs. */
.summon{
  position:fixed; left:0; top:50%; transform:translateY(-50%);
  z-index:60; padding:22px clamp(16px,2.4vw,30px);
  background:rgba(34,64,77,.96); /* §4.6 — opaque panel, no field blur */
  border:1px solid rgba(255,255,255,.10); border-left:0;
  border-radius:0 10px 10px 0;
  box-shadow:0 24px 70px rgba(12,22,27,.5);
  opacity:0; transition:opacity .24s ease, transform .28s cubic-bezier(.2,.7,.3,1);
  pointer-events:none;
}
.summon.is-open{ opacity:1; pointer-events:auto; }
.summon:not(.is-open){ transform:translateY(-50%) translateX(-10px); }

.summon-list{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:2px; }

.summon-item{
  display:flex; align-items:flex-start; gap:12px; width:100%;
  background:none; border:0; cursor:pointer; text-align:left;
  padding:9px 16px 9px 12px; border-radius:5px;
  border-left:2px solid transparent;
  transition:background .18s ease, border-color .18s ease;
}
/* No per-item backdrop-filter — the panel behind them is the background
   now, and blurring what is already opaque buys nothing but compositing. */
.summon-item:hover{ background:rgba(255,255,255,.10); }
.summon-item.active{ border-left-color:#fff; background:rgba(255,255,255,.07); }

.summon-num{
  font-family:var(--sans); font-size:9.5px; font-weight:700; letter-spacing:.1em;
  color:rgba(255,255,255,.5); padding-top:4px; min-width:14px; flex:none;
  transition:color .3s ease;
}
.summon-text{ display:flex; flex-direction:column; gap:2px; min-width:0; }
.summon-name{
  font-family:var(--sans); font-size:12px; font-weight:700;
  letter-spacing:.08em; text-transform:uppercase; color:rgba(255,255,255,.85);
  transition:color .3s ease;
}
/* The line the other two modes have no room for. This is what the extra
   space actually bought — not bigger labels, a different sentence. */
.summon-holds{
  font-family:var(--display); font-size:12.5px; line-height:1.35;
  color:rgba(255,255,255,.6); max-width:34ch;
  transition:color .3s ease;
}
.summon-item.active .summon-num,
.summon-item.active .summon-name{ color:#fff; }
.summon-item.active .summon-holds{ color:rgba(255,255,255,.8); }

/* Arrival and Epilogue are not factors; they read as the ends of the
   thing rather than two more of it. */
.summon-item:not(.summon-item--key) .summon-name{ font-size:10.5px; color:rgba(255,255,255,.55); }
.summon-item:not(.summon-item--key) .summon-holds{ font-size:11.5px; }

.summon.on-light{
  background:rgba(243,242,179,.95);
  border-color:rgba(34,64,77,.12);
  box-shadow:0 24px 70px rgba(34,64,77,.18);
}
.summon.on-light .summon-item:hover{ background:rgba(34,64,77,.07); }
.summon.on-light .summon-item.active{ background:rgba(34,64,77,.05); border-left-color:var(--ink); }
.summon.on-light .summon-num{ color:rgba(34,64,77,.5); }
.summon.on-light .summon-name{ color:rgba(34,64,77,.85); }
.summon.on-light .summon-holds{ color:rgba(34,64,77,.62); }
.summon.on-light .summon-item.active .summon-num,
.summon.on-light .summon-item.active .summon-name{ color:var(--ink); }
.summon.on-light .summon-item.active .summon-holds{ color:rgba(34,64,77,.85); }
.summon.on-light .summon-item:not(.summon-item--key) .summon-name{ color:rgba(34,64,77,.55); }

/* ============================================================
   SCRUBBER — the timeline is the nav. A film-scrubber along the
   bottom edge: a hairline at rest, expanding to labelled chapter
   regions on hover/drag. Continuous, not a list of stops.
   ============================================================ */
.scrub{
  position:fixed; left:0; right:0; bottom:0; z-index:40;
  padding:0 clamp(16px,2.4vw,40px) 14px;
  opacity:0; transition:opacity .6s ease;
  pointer-events:none;
}
body.motion .scrub{ opacity:1; }
.scrub-inner{ pointer-events:auto; }

/* A scrim that only exists while the bar is open. At rest the bar is a
   bare hairline and needs no plate; the moment it expands and shows
   labels, this gradient lifts the labels off whatever copy the story has
   near the bottom edge, so the two never read as one tangled block. */
.scrub::before{
  content:''; position:absolute; left:0; right:0; bottom:0; height:72px;
  background:linear-gradient(to top, rgba(34,64,77,.82) 0%, rgba(34,64,77,.5) 45%, rgba(34,64,77,0) 100%);
  pointer-events:none; opacity:0; transition:opacity .28s ease;
}
.scrub:hover::before,
body.scrub-active .scrub::before,
.scrub:focus-within::before{ opacity:1; }
.scrub.on-light::before{
  background:linear-gradient(to top, rgba(243,242,179,.9) 0%, rgba(243,242,179,.55) 45%, rgba(243,242,179,0) 100%);
}

.scrub-track{
  position:relative; height:12px;            /* rest: a bare hairline */
  cursor:pointer; touch-action:none; outline:none;
  transition:height .28s ease;
}
.scrub:hover .scrub-track,
.scrub-track:focus-visible,
body.scrub-active .scrub-track{ height:46px; }

/* the baseline everything hangs from */
.scrub-track::after{
  content:''; position:absolute; left:0; right:0; bottom:7px; height:2px;
  background:rgba(255,255,255,.18); transition:background .4s ease;
}

.scrub-seg{
  position:absolute; bottom:0; height:100%; box-sizing:border-box;
  display:flex; align-items:flex-end; gap:6px; padding-bottom:13px;
  border-left:1px solid rgba(255,255,255,.14); overflow:hidden;
}
.scrub-seg:first-child{ border-left:0; }
/* Nothing but the hairline, fill and playhead at rest — no text at all,
   so the bar cannot collide with the story's own copy until you reach
   for it. Numbers and names arrive together on hover/drag, over the scrim. */
.scrub-num{
  font-family:var(--sans); font-size:10px; font-weight:700; letter-spacing:.1em;
  color:rgba(255,255,255,.6); padding-left:7px;
  opacity:0; transition:opacity .28s ease, color .35s ease;
}
.scrub:hover .scrub-num,
body.scrub-active .scrub-num,
.scrub-track:focus-visible .scrub-num{ opacity:1; }
.scrub-name{
  font-family:var(--sans); font-size:10.5px; font-weight:700; letter-spacing:.08em; text-transform:uppercase;
  color:rgba(255,255,255,.82); white-space:nowrap;
  opacity:0; transform:translateY(4px);
  transition:opacity .28s ease, transform .28s ease, color .35s ease;
}
.scrub:hover .scrub-name,
body.scrub-active .scrub-name,
.scrub-track:focus-visible .scrub-name{ opacity:1; transform:none; }
.scrub-seg--end .scrub-name{ font-weight:400; text-transform:none; letter-spacing:.02em; color:rgba(255,255,255,.5); }
.scrub-seg.active .scrub-num{ color:var(--gold); }
.scrub-seg.active .scrub-name{ color:#fff; }

/* progress fill + playhead */
.scrub-fill{
  position:absolute; left:0; bottom:7px; height:2px; width:0;
  background:var(--gold); pointer-events:none;
  box-shadow:0 0 8px rgba(238,243,244,.5);
}
.scrub-head{
  position:absolute; bottom:0; left:0; width:0; height:100%; pointer-events:none;
}
.scrub-head::before{
  content:''; position:absolute; bottom:2px; left:-1px; width:2px; height:12px;
  background:var(--gold);
}
.scrub-head::after{
  content:''; position:absolute; bottom:3px; left:-5px; width:10px; height:10px;
  border-radius:50%; background:var(--gold);
  box-shadow:0 0 0 3px rgba(238,243,244,.22);
  transition:transform .2s ease;
}
body.scrub-active .scrub-head::after{ transform:scale(1.25); }

.scrub-bubble{
  position:absolute; bottom:20px; left:0; transform:translateX(-50%);
  display:flex; align-items:baseline; gap:6px; white-space:nowrap;
  padding:4px 9px; border-radius:6px;
  background:rgba(34,64,77,.92);
  border:1px solid rgba(255,255,255,.1);
  opacity:0; transition:opacity .2s ease;
}
.scrub:hover .scrub-bubble,
body.scrub-active .scrub-bubble{ opacity:1; }
.scrub-bub-num{ font-family:var(--sans); font-size:9px; font-weight:700; letter-spacing:.1em; color:var(--gold); }
.scrub-bub-name{ font-family:var(--sans); font-size:11px; font-weight:700; letter-spacing:.05em; color:#fff; }

/* on-light — over the pale chapters, ink instead of white */
.scrub.on-light .scrub-track::after{ background:rgba(34,64,77,.16); }
.scrub.on-light .scrub-seg{ border-left-color:rgba(34,64,77,.14); }
.scrub.on-light .scrub-num{ color:rgba(34,64,77,.6); }
.scrub.on-light .scrub-name{ color:rgba(34,64,77,.8); }
.scrub.on-light .scrub-seg--end .scrub-name{ color:rgba(34,64,77,.5); }
.scrub.on-light .scrub-seg.active .scrub-num{ color:var(--ink); }
.scrub.on-light .scrub-seg.active .scrub-name{ color:var(--ink); }
.scrub.on-light .scrub-fill,
.scrub.on-light .scrub-head::before,
.scrub.on-light .scrub-head::after{ background:var(--ink); }
.scrub.on-light .scrub-head::after{ box-shadow:0 0 0 3px rgba(34,64,77,.18); }
.scrub.on-light .scrub-bubble{ background:rgba(243,242,179,.92); border-color:rgba(34,64,77,.12); }
.scrub.on-light .scrub-bub-name{ color:var(--ink); }
.scrub.on-light .scrub-bub-num{ color:var(--ink); }

/* ============================================================
   MODE: MINIMAP — the whole story as one vertical map, right edge.

   Not the scrubber turned on its side. The scrubber is a media control:
   a hairline, a playhead, time reading across the bottom. This reads as
   a MAP — seven tiles stacked down the edge, each tile's height its true
   share of story-time and each carrying its formation glyph, with a LENS
   (a viewport window) you drag over the terrain. At rest it is a slim
   proportional bar so the story's shape is always legible in the corner;
   reach for it and it opens into the full map with glyphs and names.
   ============================================================ */
.minimap{
  /* --mm-inset = the gap from each screen edge. Smaller inset = longer bar, so
     every chapter tile (sized as a % of the track) gets proportionally more
     room. The scrim below pins to this same value, so it always reaches the
     screen edges no matter what this is set to. */
  --mm-inset:6vh;
  position:fixed; right:0; top:var(--mm-inset); bottom:var(--mm-inset); z-index:40;
  display:flex; align-items:center;
  padding-right:clamp(10px,1.6vw,22px);
  opacity:0; transition:opacity .6s ease;
  pointer-events:none;
}
body.motion .minimap{ opacity:1; }

/* A scrim that only exists while the map is open — at rest the slim bar
   needs no plate. The moment it expands and shows names, this backing
   fades in: SOLID under the whole label band (the map's ~258px footprint, §R7.1),
   then a soft gradient to nothing further left. Opaque where the labels
   sit — so story copy behind them is cleanly occluded, not tinted-and-
   tangled — and edgeless where it meets the story, so there is no hard
   rectangle. Fades LEFT, away from the map's home on the right edge. */
.minimap::before{
  /* full viewport height — the minimap box is inset by --mm-inset top/bottom, so
     pulling out by the same amount lands the scrim on both screen edges. No hard
     top/bottom line: it only fades horizontally (to left), never vertically. */
  content:''; position:absolute;
  top:calc(-1 * var(--mm-inset)); bottom:calc(-1 * var(--mm-inset)); right:0; width:376px;
  /* the exact live page background — main.js writes --page-bg-rgb every frame,
     so the plate IS the surface it sits on (navy, sky, cream, or any blend
     mid-transition). Opaque under the labels; the SAME colour at zero alpha
     where it meets the story, so it dissolves instead of tinting.
     §R7.1 — widened with the 230px open track: solid under the whole label
     band (258px), then the same soft fade to nothing. */
  background:linear-gradient(to left,
    rgba(var(--page-bg-rgb,6,12,38),1) 0,
    rgba(var(--page-bg-rgb,6,12,38),1) 258px,
    rgba(var(--page-bg-rgb,6,12,38),0) 376px);
  pointer-events:none; opacity:0; transition:opacity .28s ease;
}
.minimap:hover::before,
body.mm-active .minimap::before,
.minimap:focus-within::before{ opacity:1; }

/* The story pins a bordered "Go deeper" card to the bottom-right corner —
   the exact corner the map's lower tiles (The multiplier, Epilogue) live
   in. The plate backs flowing prose cleanly, but slicing a discrete card
   with a gradient reads as broken. So while the map is open (hover or
   drag) the card yields entirely and returns the instant you leave: the
   labels and the card are never both present in that corner. Inline
   opacity is set per-scroll by the engine, hence !important. */
body:has(.mm-track:hover) .deeper,
body.mm-active .deeper{
  opacity:0 !important; pointer-events:none !important;
  transition:opacity .25s ease !important;
}

/* §R4 (round-3) — the same yield for RIGHT-ANCHORED STORY COPY. These blocks
   (right:var(--m)) live inside the open map's 320px plate band; the plate's
   fade edge was slicing them mid-word ("336 run thei…", "~2,0…" — Saurabh's
   screen recording). Half-amputated words read as a rendering bug; the
   deeper-card rule above already established the answer: while the map is
   open the colliding block yields entirely, and returns the instant you
   leave. Inline opacity is engine-written per frame, hence !important. */
body:has(.mm-track:hover) .act1-aside,
body:has(.mm-track:hover) .extract-lockup,
body:has(.mm-track:hover) .burst-stat,
body:has(.mm-track:hover) .mult-amal,
body.mm-active .act1-aside,
body.mm-active .extract-lockup,
body.mm-active .burst-stat,
body.mm-active .mult-amal{
  opacity:0 !important; pointer-events:none !important;
  transition:opacity .25s ease !important;
}

.mm-track{
  position:relative; width:14px; height:100%;   /* rest: a slim proportional bar */
  pointer-events:auto; cursor:pointer; touch-action:none; outline:none;
  transition:width .28s ease;
}
/* §R7.1 — 184px → 230px: the beats now carry full navigation names
   ("70% blocked by funding") plus their deck slide number, and the widest
   row measures ~196px of glyphs before its dot. Every consumer of the open
   width moves with it: the scrim below (solid band 210→258, fade 320→376)
   and the coach step-1 hint card (css/styles.css, right:clamp). */
.minimap:hover .mm-track,
.mm-track:focus-visible,
body.mm-active .mm-track{ width:230px; }

/* the spine everything hangs from, pinned to the screen edge */
.mm-track::after{
  content:''; position:absolute; top:0; bottom:0; right:6px; width:2px;
  background:rgba(255,255,255,.18); transition:background .4s ease;
}

.mm-seg{
  position:absolute; right:0; width:100%; box-sizing:border-box;
  display:flex; align-items:center; justify-content:flex-end; gap:9px;
  padding-right:14px;
  border-top:1px solid rgba(255,255,255,.14); overflow:hidden;
}
.mm-seg:first-child{ border-top:0; }
/* No filled highlight on the active tile — the lens circle on the spine is
   the single "you are here" marker. The active label still brightens (below),
   but the segment box stays flush with the rest of the terrain. */

/* ── THE ZOOM: chapter 3's four beats, listed inside its own tile ─────────
   The tile is nearly a third of the map — tall enough to show all four holds
   at once, pinned at their true story-time height, where the scrubber's
   hairline could only afford the nearest. Hidden until the map opens AND you
   are in (or hovering) the chapter; then the tile's own centred label steps
   aside and the beats take the space. */
/* §R9-W2 (Mo M7) — the label⇄beats swap is SEQUENCED, not crossfaded.
   Both texts print at the same spot; a plain .28s crossfade left them
   half-visible ON TOP of each other for ~300ms at every chapter boundary
   ("Chapter 03" stamped across "WORK & VENTURES" in his recording). So:
   whichever text is leaving goes out fast (.12s, no delay) and whichever
   is arriving waits .14s — by then the other is gone. Base transitions
   govern the leave-active edge, the revealed states govern enter-active. */
.mm-beats{
  position:absolute; inset:0; pointer-events:none;
  opacity:0; transition:opacity .12s ease;
}
.minimap:hover .mm-seg--beats.active .mm-beats,
.minimap:hover .mm-seg--beats:hover .mm-beats,
body.mm-active .mm-seg--beats.active .mm-beats,
.mm-track:focus-visible .mm-seg--beats.active .mm-beats{
  opacity:1; pointer-events:auto;
  transition:opacity .22s ease .14s;
}

/* the centred chapter label yields to the beats when they show (more specific
   than the generic .mm-label reveal below, so it wins) */
.minimap:hover .mm-seg--beats.active > .mm-label,
.minimap:hover .mm-seg--beats:hover > .mm-label,
body.mm-active .mm-seg--beats.active > .mm-label,
.mm-track:focus-visible .mm-seg--beats.active > .mm-label{
  opacity:0; transform:translateX(6px);
  transition:opacity .12s ease, transform .12s ease;
}

/* a quiet "03" anchor at the tile head — with the full label gone, this is
   how you still know whose beats these are */
.mm-beats-cap{
  position:absolute; top:3px; right:14px; pointer-events:none;
  font-family:var(--sans); font-size:9.5px; font-weight:700; letter-spacing:.1em;
  color:var(--gold); opacity:.7;
}

/* one beat = one row, absolutely pinned at its story-time %; the lens on the
   spine passes straight through the dot as you drag past that hold */
.mm-beat{
  position:absolute; right:0; width:100%; box-sizing:border-box;
  transform:translateY(-50%);
  display:flex; align-items:center; justify-content:flex-end;
  margin:0; padding:0 20px 0 0; border:0; background:none; cursor:pointer;
  text-align:right;
}
/* §R7.2 — the row now reads "14 · Five skills improved", right-aligned: the
   deck slide number first (small, dimmed, tabular), then the name. One row,
   one line — the wall number is the same number the pill shows on landing. */
/* line-height 1, not 1.16: rows are single-line nowrap, and the §R7.1 open
   tiles pack down to ~10.7px pitch on the shortest chapter at 1280 — the
   glyph box must be no taller than that or the labels kiss. */
.mm-beat-label{ display:flex; flex-direction:row; align-items:baseline; gap:5px; line-height:1; min-width:0; }
.mm-beat-wall{
  font-family:var(--sans); font-size:9px; font-weight:700; letter-spacing:.04em;
  color:rgba(255,255,255,.44); white-space:nowrap; font-variant-numeric:tabular-nums;
  transition:color .3s ease;
}
.mm-beat-wall:not(:empty)::after{ content:'·'; margin-left:5px; color:rgba(255,255,255,.28); }
.mm-beat-name{
  font-family:var(--sans); font-size:10px; font-weight:700; letter-spacing:.04em;
  color:rgba(255,255,255,.72); white-space:nowrap; transition:color .3s ease;
}
.mm-beat-dot{
  position:absolute; right:5px; top:50%; transform:translateY(-50%);
  width:5px; height:5px; border-radius:50%; background:rgba(255,255,255,.34);
  transition:background .3s ease, transform .3s ease, box-shadow .3s ease;
}
.mm-beat:hover .mm-beat-name{ color:#fff; }
/* nearest beat = "you are here" within the chapter — the same gold readout
   the scrubber and stepper give */
.mm-beat.is-near .mm-beat-name{ color:var(--paper); }
.mm-beat.is-near .mm-beat-wall{ color:var(--paper); }
.mm-beat.is-near .mm-beat-dot{
  background:var(--gold); transform:translateY(-50%) scale(1.3);
  box-shadow:0 0 8px rgba(238,243,244,.6);
}

/* Density guard. Every beat keeps its DOT on the true spine at its story-time
   height — the map never lies about where a hold sits. But LABELS need room.
   A tile earns .mm-seg--roomy (set in rail.js from its real px-per-beat) only
   if it is tall enough to list them all without collision — Journey, Skills'
   sibling acts, the Return all clear it and show every beat at once, the map's
   edge over the scrubber's hairline. The short, beat-dense tiles (Skills packs
   five into a sliver) would pile up, so there the labels behave like the
   scrubber's: only the nearest, and any you hover, speaks; the rest stay dots.
   Nothing is lost — the dots are all present and clickable; you just read the
   dense ones one at a time. */
.mm-seg--beats:not(.mm-seg--roomy) .mm-beat-label{ opacity:0; transition:opacity .2s ease; }
.mm-seg--beats:not(.mm-seg--roomy) .mm-beat.is-near .mm-beat-label,
.mm-seg--beats:not(.mm-seg--roomy) .mm-beat:hover .mm-beat-label{ opacity:1; }

/* §R7.1 — the OPEN tile retires the roomy gate. The guard above is a RESTING
   rule: it keeps a dense tile quiet while you are merely near the map. But the
   moment a tile's beats are actually showing (the same four states that reveal
   .mm-beats — the map open on the active tile, or the tile itself hovered),
   every label speaks: a menu you have opened on purpose that still hides its
   own entries is not a menu. Safe now because the §R7.0 relaxation in rail.js
   (CAP_CLEAR/ROW_GAP forward+backward passes) guarantees the rows cannot
   print over each other. .is-near keeps its emphasis on top. */
.minimap:hover .mm-seg--beats.active .mm-beat-label,
.minimap:hover .mm-seg--beats:hover .mm-beat-label,
body.mm-active .mm-seg--beats.active .mm-beat-label,
body.mm-active .mm-seg--beats:hover .mm-beat-label,
.mm-track:focus-visible .mm-seg--beats.active .mm-beat-label{ opacity:1; }

/* names arrive together with the glyphs, over the scrim */
.mm-label{
  display:flex; flex-direction:column; align-items:flex-start; gap:1px;
  min-width:0; opacity:0; transform:translateX(6px);
  /* §R9-W2 — the .14s delay is the other half of the sequenced swap above:
     a label returning after its tile's beats leave must wait until the
     beats' .12s exit has finished. (Also delays the map-open reveal by
     .14s, which just tucks the labels into the width animation.) */
  transition:opacity .22s ease .14s, transform .22s ease .14s;
}
.minimap:hover .mm-label,
body.mm-active .mm-label,
.mm-track:focus-visible .mm-label{ opacity:1; transform:none; }
.mm-num{
  font-family:var(--sans); font-size:9.5px; font-weight:700; letter-spacing:.1em;
  color:rgba(255,255,255,.55); transition:color .35s ease;
}
.mm-name{
  font-family:var(--sans); font-size:10.5px; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
  color:rgba(255,255,255,.82); white-space:nowrap; transition:color .35s ease;
}
.mm-seg--end .mm-name{ font-weight:400; text-transform:none; letter-spacing:.02em; color:rgba(255,255,255,.5); }
.mm-seg.active .mm-num{ color:var(--paper); }
.mm-seg.active .mm-name{ color:#fff; }

/* terrain crossed — fill runs down the spine from the top to T */
.mm-fill{
  position:absolute; top:0; right:6px; width:2px; height:0;
  background:var(--paper); pointer-events:none;
  box-shadow:0 0 8px rgba(238,243,244,.5);
}

/* the playhead — a single gold knob riding the spine at T: the "you are here"
   marker. It used to sit inside a bracketed viewport rectangle, but that box
   read as clutter dragged over the tiles. Gone now — the gold fill running down
   to the knob, and the knob itself, already say where you are, the same clean
   read as the scrubber's playhead turned on its side. Centred on the spine
   (right:6px+1px) and on T (margin-top:-6px against a 12px circle). */
.mm-lens{
  position:absolute; right:1px; width:12px; height:12px; margin-top:-6px;
  pointer-events:none; border-radius:50%; background:var(--paper);
  box-shadow:0 0 0 3px rgba(238,243,244,.22), 0 0 10px rgba(238,243,244,.5);
  transition:transform .2s ease, box-shadow .2s ease;
}
body.mm-active .mm-lens{ transform:scale(1.28); box-shadow:0 0 0 4px rgba(238,243,244,.26), 0 0 16px rgba(238,243,244,.6); }

/* on-light — over the pale chapters, ink instead of white */
.minimap.on-light .mm-track::after{ background:rgba(34,64,77,.16); }
.minimap.on-light .mm-seg{ border-top-color:rgba(34,64,77,.14); }
.minimap.on-light .mm-num{ color:rgba(34,64,77,.55); }
.minimap.on-light .mm-name{ color:rgba(34,64,77,.8); }
.minimap.on-light .mm-seg--end .mm-name{ color:rgba(34,64,77,.5); }
.minimap.on-light .mm-seg.active .mm-num{ color:var(--ink); }
.minimap.on-light .mm-seg.active .mm-name{ color:var(--ink); }
.minimap.on-light .mm-seg.active{ background:rgba(34,64,77,.10); }
.minimap.on-light .mm-fill{ background:var(--ink); box-shadow:0 0 8px rgba(34,64,77,.4); }
.minimap.on-light .mm-lens{ background:var(--ink); box-shadow:0 0 0 3px rgba(34,64,77,.18), 0 0 10px rgba(34,64,77,.4); }
/* beats when previewed from a pale chapter (hover act3 from a cream act) */
.minimap.on-light .mm-beats-cap{ color:var(--ink); }
.minimap.on-light .mm-beat-name{ color:rgba(34,64,77,.72); }
.minimap.on-light .mm-beat-wall{ color:rgba(34,64,77,.44); }
.minimap.on-light .mm-beat-wall:not(:empty)::after{ color:rgba(34,64,77,.28); }
.minimap.on-light .mm-beat-dot{ background:rgba(34,64,77,.4); }
.minimap.on-light .mm-beat:hover .mm-beat-name{ color:var(--ink); }
.minimap.on-light .mm-beat.is-near .mm-beat-name{ color:var(--ink); }
.minimap.on-light .mm-beat.is-near .mm-beat-wall{ color:var(--ink); }
.minimap.on-light .mm-beat.is-near .mm-beat-dot{ background:var(--ink); box-shadow:0 0 8px rgba(34,64,77,.45); }

/* ============================================================
   §R7.3 — the INDEX affordance: quiet small-caps at the head of the map,
   present only while the map is open (same reveal states as the labels).
   It opens the full-screen story index (main.js §R7.3); M does the same.
   ============================================================ */
.mm-index{
  position:absolute; top:-36px; right:clamp(10px,1.6vw,22px);
  margin:0; padding:5px 2px; border:0; background:none; cursor:pointer;
  font-family:var(--sans); font-size:9.5px; font-weight:700; letter-spacing:.24em;
  color:rgba(255,255,255,.5); text-transform:uppercase;
  opacity:0; pointer-events:none;
  transition:opacity .28s ease, color .2s ease;
}
.minimap:hover .mm-index,
body.mm-active .mm-index{ opacity:1; pointer-events:auto; }
.mm-index:hover,
.mm-index:focus-visible{ color:#fff; }
.minimap.on-light .mm-index{ color:rgba(34,64,77,.55); }
.minimap.on-light .mm-index:hover,
.minimap.on-light .mm-index:focus-visible{ color:var(--ink); }

/* ============================================================
   §R7.2 — THE SCRUB READOUT CHIP. Exists only while the lens is dragged
   (rail.js shows it on the first real move, kills it on release): a floating
   readout pinned left of the playhead, saying where the drag is in the shared
   language — "<chapter num> · <chapter name> — <beat name>". Petrol ground,
   ice text, Montserrat, one 1px keyline — the same chrome as the map. It
   stays dark even over the pale chapters (like the scrubber's bubble): the
   chip is transient presenter chrome, not surface. Reduced motion never
   shows it (rail.js gates), so no animation lives here at all.
   ============================================================ */
.mm-chip{
  position:absolute; right:calc(100% + 16px); transform:translateY(-50%);
  white-space:nowrap; padding:7px 13px; border-radius:7px;
  font-family:var(--display); font-weight:700; font-size:11px; letter-spacing:.04em;
  background:rgba(34,64,77,.96); color:var(--sky);
  border:1px solid rgba(194,237,240,.28);
  box-shadow:0 12px 36px rgba(6,14,18,.4);
  pointer-events:none; z-index:5;
}
.mm-chip[hidden]{ display:none; }

/* ============================================================
   §2.2 THE ARRIVAL BLINK — a 2-cycle glow the instant a snap (or a keyboard
   step) rests ON a hold, paired with the scroll friction. The playhead pulses
   even when the map is a slim bar (so you get the cue without reaching for it);
   when the map is open, the exact checkpoint dot and its tile pulse too. Ice
   glow on dark, ink glow on the pale chapters — the same accent language as the
   "you are here" states, just transient. Subtle, not carnival: two short beats.
   ============================================================ */
@keyframes mmLensPing{
  0%,100%{ transform:scale(1);   box-shadow:0 0 0 3px rgba(238,243,244,.22), 0 0 10px rgba(238,243,244,.5); }
  45%    { transform:scale(1.5); box-shadow:0 0 0 5px rgba(238,243,244,.4),  0 0 20px 5px rgba(238,243,244,.85); }
}
.mm-lens.is-ping{ animation:mmLensPing .5s ease-out 2; }
@keyframes mmDotPing{
  0%,100%{ box-shadow:0 0 0 0 rgba(238,243,244,0); }
  45%    { box-shadow:0 0 12px 3px rgba(238,243,244,.85); }
}
.mm-beat.is-ping .mm-beat-dot{ background:var(--gold); transform:translateY(-50%) scale(1.6); animation:mmDotPing .5s ease-out 2; }
@keyframes mmSegPing{ 0%,100%{ background:transparent; } 45%{ background:rgba(238,243,244,.06); } }
.mm-seg.is-ping{ animation:mmSegPing .5s ease-out 2; }

/* on-light — ink glow over the pale chapters */
@keyframes mmLensPingLight{
  0%,100%{ transform:scale(1);   box-shadow:0 0 0 3px rgba(34,64,77,.18), 0 0 10px rgba(34,64,77,.4); }
  45%    { transform:scale(1.5); box-shadow:0 0 0 5px rgba(34,64,77,.3),  0 0 20px 5px rgba(34,64,77,.6); }
}
.minimap.on-light .mm-lens.is-ping{ animation:mmLensPingLight .5s ease-out 2; }
@keyframes mmDotPingLight{ 0%,100%{ box-shadow:0 0 0 0 rgba(34,64,77,0); } 45%{ box-shadow:0 0 12px 3px rgba(34,64,77,.6); } }
.minimap.on-light .mm-beat.is-ping .mm-beat-dot{ background:var(--ink); animation:mmDotPingLight .5s ease-out 2; }
@keyframes mmSegPingLight{ 0%,100%{ background:transparent; } 45%{ background:rgba(34,64,77,.06); } }
.minimap.on-light .mm-seg.is-ping{ animation:mmSegPingLight .5s ease-out 2; }

/* ============================================================
   Reduced motion — the transitions are decoration, the nav is not.
   ============================================================ */
@media (prefers-reduced-motion:reduce){
  .summon, .summon:not(.is-open), .rail-mini, .stepper-fill{ transition:none; }
  .summon:not(.is-open){ transform:translateY(-50%); }
  .rail--atlas .rail-item:hover .rail-mini{ transform:none; }
  .scrub-track, .scrub-name, .scrub-bubble, .scrub-head::after{ transition:none; }
  .mm-track, .mm-label, .mm-beats, .mm-beat-name, .mm-beat-dot, .mm-beat-label, .mm-lens{ transition:none; }
  /* the arrival blink is a functional cue, not decoration — kept, but softened
     to a single, still (no-scale) pulse so it never reads as motion for motion's
     sake. The scale is dropped; only the glow flashes once. */
  .mm-lens.is-ping, .minimap.on-light .mm-lens.is-ping,
  .mm-beat.is-ping .mm-beat-dot, .minimap.on-light .mm-beat.is-ping .mm-beat-dot,
  .mm-seg.is-ping, .minimap.on-light .mm-seg.is-ping{ animation-iteration-count:1; animation-duration:.45s; }
  .mm-lens.is-ping{ animation-name:mmDotPing; }
  .mm-beat.is-ping .mm-beat-dot{ transform:translateY(-50%) scale(1); }
}
