/* ChronoShell stage layer — see CLAUDE_CODE_BRIEF.md
   Loaded AFTER style.css. Everything here is scoped to .hero-stage so the
   modern era face and the rest of the site are untouched. */

/* The stage is deliberately wider than the viewport — it covers rather than
   letterboxes — so the document must be told not to grow to fit it. On a
   390px phone the stage computes to 1387px, which gave the whole page a
   horizontal scrollbar and left the front door sitting off to one side.
   `clip` rather than `hidden`: hidden would make <html> a scroll container
   and break `position: sticky` elsewhere. */
html, body { overflow-x: clip; }

/* This stylesheet inherits the legacy sheet's box model, which is content-box.
   That is why `width: min(30rem, 100%)` plus `padding-inline: 1.1rem` came out
   34px wider than the phone and the door hung off both edges. Scoped to the
   door rather than set globally: the 1888 stage's measured overlays are laid
   out in the old box model and must stay there. */
#cs-warp,
#cs-warp *,
#cs-warp *::before,
#cs-warp *::after { box-sizing: border-box; }

/* ---------- Time-transit loader ----------
   Covers the hero video download. Styled in this file but positioned `fixed`
   and outside the stage on purpose: it must be correct before the stage exists.
   Everything is CSS — no images, no library — so it paints on the first frame. */
#cs-warp {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: grid;
  /* An `auto` column sizes to its item's max-content, so a long heading made
     the column wider than the phone and the panel hung off both edges. A
     minmax(0, 100%) column cannot exceed the viewport, whatever is in it. */
  grid-template-columns: minmax(0, 100%);
  place-items: center;
  overflow: hidden;
  background: radial-gradient(ellipse at center, #10161f 0%, #07090c 62%, #04050700 100%), #07090c;
  transition: opacity .7s ease, visibility .7s;
}

#cs-warp.gone { opacity: 0; visibility: hidden; pointer-events: none; }

/* --- the front door ---
   A game main-menu, not a web form: liquid aurora behind, two glass panels in
   front, a sheen that travels across the one you are pointing at. All CSS, so
   it costs nothing on a screen that is already waiting for a video. */
#cs-warp::before {
  content: "";
  position: absolute;
  inset: -30%;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(38% 34% at 22% 30%, rgba(143, 211, 255, .22), transparent 70%),
    radial-gradient(34% 30% at 78% 26%, rgba(226, 189, 118, .20), transparent 70%),
    radial-gradient(46% 40% at 62% 78%, rgba(120, 90, 200, .18), transparent 70%),
    radial-gradient(30% 26% at 18% 76%, rgba(80, 200, 190, .14), transparent 70%);
  filter: blur(30px) saturate(120%);
  animation: cs-aurora 22s ease-in-out infinite alternate;
}

@keyframes cs-aurora {
  0%   { transform: translate3d(-2%, -1%, 0) scale(1); }
  50%  { transform: translate3d(3%, 2%, 0) scale(1.08) rotate(2deg); }
  100% { transform: translate3d(-1%, 3%, 0) scale(1.04) rotate(-2deg); }
}

.cs-gate {
  position: relative;
  z-index: 4;
  width: min(70rem, 90vw);
  text-align: center;
}

.cs-gate-eyebrow {
  margin: 0 0 1rem;
  font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
  font-size: clamp(9px, .72vw, 11px);
  letter-spacing: .42em;
  text-transform: uppercase;
  color: rgba(143, 211, 255, .75);
}

.cs-gate-title {
  margin: 0 0 clamp(1.6rem, 4vh, 3rem);
  font-family: "IM Fell English", Georgia, serif;
  font-weight: 400;
  font-size: clamp(30px, 5vw, 62px);
  line-height: 1.02;
  color: #fff6e4;
  text-shadow: 0 0 40px rgba(143, 211, 255, .25);
}

.cs-gate-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
  gap: clamp(1rem, 2.4vw, 1.8rem);
}

.cs-gate-card {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding: clamp(1.4rem, 3vw, 2.2rem);
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: #f6ecd8;
  border: 0;
  border-radius: 20px;
  background: linear-gradient(160deg, rgba(255, 255, 255, .13), rgba(255, 255, 255, .04) 42%, rgba(255, 255, 255, .02));
  backdrop-filter: blur(18px) saturate(150%);
  -webkit-backdrop-filter: blur(18px) saturate(150%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .35),      /* the lit top edge of glass */
    inset 0 0 0 1px rgba(255, 255, 255, .10),
    0 22px 50px rgba(0, 0, 0, .55);
  transition: transform .5s cubic-bezier(.22, .9, .3, 1), box-shadow .5s ease;
}

/* the travelling sheen — this is the "liquid" part */
.cs-gate-card::before {
  content: "";
  position: absolute;
  inset: -40% -60%;
  z-index: -1;
  background: linear-gradient(115deg, transparent 42%, rgba(255, 255, 255, .22) 50%, transparent 58%);
  transform: translateX(-70%);
  transition: transform 1.1s cubic-bezier(.22, .9, .3, 1);
}

/* the era's own colour, pooled in the corner and spreading on hover */
.cs-gate-card::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: radial-gradient(120% 90% at 12% 8%, rgba(226, 189, 118, .30), transparent 62%);
  opacity: .5;
  transition: opacity .5s ease, transform .8s ease;
}

.cs-gate-card[data-go="present"]::after {
  background: radial-gradient(120% 90% at 12% 8%, rgba(143, 211, 255, .32), transparent 62%);
}

.cs-gate-card:hover,
.cs-gate-card:focus-visible {
  transform: translateY(-8px) scale(1.012);
  outline: none;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .5),
    inset 0 0 0 1px rgba(255, 255, 255, .18),
    0 30px 70px rgba(0, 0, 0, .6),
    0 0 60px rgba(226, 189, 118, .22);
}

.cs-gate-card[data-go="present"]:hover,
.cs-gate-card[data-go="present"]:focus-visible {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .5),
    inset 0 0 0 1px rgba(255, 255, 255, .18),
    0 30px 70px rgba(0, 0, 0, .6),
    0 0 60px rgba(143, 211, 255, .24);
}

.cs-gate-card:hover::before,
.cs-gate-card:focus-visible::before { transform: translateX(70%); }

.cs-gate-card:hover::after,
.cs-gate-card:focus-visible::after { opacity: 1; transform: scale(1.15); }

.cs-gate-card:active { transform: translateY(-3px) scale(.998); }

/* the year, set large and low-contrast like a HUD plate number */
.cs-gate-year {
  display: block;
  font-family: ui-monospace, monospace;
  font-size: clamp(34px, 5vw, 58px);
  line-height: 1;
  letter-spacing: .04em;
  color: rgba(255, 246, 228, .92);
  text-shadow: 0 0 30px rgba(226, 189, 118, .45);
  margin-bottom: clamp(.6rem, 1.4vh, 1rem);
}

.cs-gate-card[data-go="present"] .cs-gate-year { text-shadow: 0 0 30px rgba(143, 211, 255, .5); }

.cs-gate-card b {
  display: block;
  font-family: "IM Fell English", Georgia, serif;
  font-weight: 400;
  font-size: clamp(18px, 1.9vw, 27px);
  line-height: 1.2;
  margin-bottom: .6rem;
}

.cs-gate-note {
  display: block;
  font-family: "IM Fell English", Georgia, serif;
  font-size: clamp(12.5px, 1.02vw, 15px);
  line-height: 1.62;
  color: rgba(246, 236, 216, .68);
  margin-bottom: clamp(1rem, 2.4vh, 1.6rem);
}

.cs-gate-cta {
  display: inline-flex;
  align-items: center;
  gap: .5em;
  padding: .55em 1.1em;
  border-radius: 999px;
  font-family: ui-monospace, monospace;
  font-size: clamp(9.5px, .78vw, 11.5px);
  letter-spacing: .24em;
  text-transform: uppercase;
  color: #0c0a06;
  background: linear-gradient(180deg, #ffe6b0, #d9b46a);
  box-shadow: 0 6px 18px rgba(226, 189, 118, .3);
  transition: transform .4s ease, box-shadow .4s ease;
}

.cs-gate-card[data-go="present"] .cs-gate-cta {
  background: linear-gradient(180deg, #cfeaff, #7fc4f2);
  box-shadow: 0 6px 18px rgba(143, 211, 255, .3);
}

.cs-gate-card:hover .cs-gate-cta { transform: translateX(4px); }

.cs-gate-small {
  margin: clamp(1rem, 2.6vh, 1.8rem) 0 0;
  font-family: ui-monospace, monospace;
  font-size: clamp(9px, .72vw, 11px);
  letter-spacing: .12em;
  color: rgba(246, 236, 216, .38);
  min-height: 1.2em;
}

/* --- the phone -----------------------------------------------------------
   `#cs-warp` is `overflow: hidden` because the tunnel behind it must not be
   scrollable, so the front door carries its own scroll instead of relying on
   the page. `svh` rather than `vh`: on iOS Safari `vh` is the *expanded*
   viewport, which put the second card under the address bar. --- */
.cs-gate {
  max-height: 100svh;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: max(1.2rem, env(safe-area-inset-top)) .25rem max(1.2rem, env(safe-area-inset-bottom));
}

@media (max-width: 820px) {
  .cs-gate { width: min(34rem, 92vw); }
  .cs-gate-eyebrow { font-size: 10px; letter-spacing: .3em; }
  .cs-gate-title { font-size: clamp(26px, 7.6vw, 40px); margin-bottom: 1.4rem; }
  .cs-gate-cards { gap: .8rem; }
  .cs-gate-card { padding: 1.15rem 1.2rem; border-radius: 16px; }
  .cs-gate-year { font-size: clamp(24px, 8vw, 34px); }
  .cs-gate-note { font-size: .86rem; }
  /* no hover on a touch screen, so the sheen and the lift only cost paint */
  .cs-gate-card::before { display: none; }
}

/* the refusal shown when the past is chosen on a phone */
.cs-gate-notice {
  display: grid;
  justify-items: center;
  gap: .9rem;
  width: min(34rem, 100%);
  padding-inline: 1.25rem;
  text-align: center;
}
/* the heading here is a sentence, not a two-word title, so it needs its own
   (smaller) scale rather than the door's */
.cs-gate-notice .cs-gate-title {
  font-size: clamp(21px, 5.4vw, 34px);
  line-height: 1.15;
  margin-bottom: .2rem;
  text-wrap: balance;
}
.cs-gate-notice .cs-gate-note { max-width: 34ch; text-align: center; }
.cs-gate-go {
  display: inline-block;
  margin-top: .6rem;
  padding: .7rem 1.4rem;
  border-radius: 999px;
  text-decoration: none;
  color: #08111d;
  background: linear-gradient(120deg, #8fd3ff, #b9a6ff);
  box-shadow: 0 10px 28px rgba(143, 211, 255, .3);
}

/* short screens: no scrollbars, just tighter */
@media (max-height: 620px) {
  .cs-gate-title { margin-bottom: 1rem; }
  .cs-gate-note { display: none; }
  .cs-gate-year { font-size: clamp(26px, 4vw, 38px); }
}

/* The pointer response used to be switched off here under reduce-motion.
   `.cs-gate-card:hover { transform: none }` outranks the second-pass tilt
   rule by one pseudo-class, so it killed the tilt at exactly the moment it
   exists. The door is toned down through `.calm` instead — see the bottom of
   this file. */

/* `:not([hidden])` matters: a bare `display: grid` here beat the hidden
   attribute, so the tunnel copy, the year bar and the skip button all bled
   through behind the front door. */
.cs-warp-run:not([hidden]) { position: absolute; inset: 0; display: grid; place-items: center; }
[hidden] { display: none !important; }

/* receding brass rings — ten elements on one keyframe, staggered */
.cs-warp-tunnel {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  perspective: 620px;
}

.cs-warp-tunnel span {
  position: absolute;
  width: 78vmin;
  height: 78vmin;
  border-radius: 50%;
  border: 2px solid rgba(226, 189, 118, .5);
  box-shadow: 0 0 46px rgba(120, 190, 255, .18), inset 0 0 60px rgba(226, 189, 118, .10);
  animation: cs-warp-ring 3.6s linear infinite;
  opacity: 0;
}

.cs-warp-tunnel span:nth-child(1) { animation-delay: 0s; }
.cs-warp-tunnel span:nth-child(2) { animation-delay: -.36s; }
.cs-warp-tunnel span:nth-child(3) { animation-delay: -.72s; }
.cs-warp-tunnel span:nth-child(4) { animation-delay: -1.08s; }
.cs-warp-tunnel span:nth-child(5) { animation-delay: -1.44s; }
.cs-warp-tunnel span:nth-child(6) { animation-delay: -1.8s; }
.cs-warp-tunnel span:nth-child(7) { animation-delay: -2.16s; }
.cs-warp-tunnel span:nth-child(8) { animation-delay: -2.52s; }
.cs-warp-tunnel span:nth-child(9) { animation-delay: -2.88s; }
.cs-warp-tunnel span:nth-child(10) { animation-delay: -3.24s; }

/* rings rush toward the viewer: small and far, then past the camera */
@keyframes cs-warp-ring {
  0%   { transform: translateZ(-1500px) rotate(0deg); opacity: 0; }
  12%  { opacity: .85; }
  85%  { opacity: .5; }
  100% { transform: translateZ(420px) rotate(24deg); opacity: 0; }
}

/* the artifact, on top of the CSS fallback */
.cs-warp-frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  z-index: 2;
  background: transparent;
  /* it is decoration during a wait, not something to tab into or scroll */
  pointer-events: none;
}

.cs-warp-copy {
  position: relative;
  text-align: center;
  color: #f4e2be;
  padding: 0 6vw;
}

.cs-warp-years {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .8em;
  font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
  font-size: clamp(13px, 1.5vw, 22px);
  letter-spacing: .22em;
  color: #8fd3ff;
}

.cs-warp-years b { font-weight: 500; }
.cs-warp-years b:last-child { color: #e2bd76; }

/* the travelling dash between the two years */
.cs-warp-years i {
  width: clamp(40px, 8vw, 120px);
  height: 1px;
  background: linear-gradient(90deg, #8fd3ff, #e2bd76);
  position: relative;
  overflow: visible;
}

.cs-warp-years i::after {
  content: "";
  position: absolute;
  top: -2px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #fff3d6;
  box-shadow: 0 0 10px #ffd98a;
  animation: cs-warp-dot 1.9s ease-in-out infinite;
}

@keyframes cs-warp-dot { 0% { left: 0; } 100% { left: 100%; } }

.cs-warp-line {
  margin: .9em 0 1.1em;
  min-height: 2.6em;
  font-family: "IM Fell English", Georgia, serif;
  font-size: clamp(16px, 2vw, 30px);
  line-height: 1.3;
  transition: opacity .35s ease, transform .35s ease;
}

.cs-warp-line.swap { opacity: 0; transform: translateY(6px); }

.cs-warp-bar {
  width: min(320px, 62vw);
  height: 2px;
  margin: 0 auto;
  background: rgba(226, 189, 118, .2);
  overflow: hidden;
}

.cs-warp-bar span {
  display: block;
  height: 100%;
  width: 0;
  background: linear-gradient(90deg, #8fd3ff, #e2bd76);
  transition: width .4s ease;
}

/* --- the two doors, shown only once the transit has finished --- */
.cs-warp-choice { margin-top: 1.6em; }

.cs-warp-ask {
  margin: 0 0 1em;
  font-family: "IM Fell English", Georgia, serif;
  font-size: clamp(14px, 1.4vw, 20px);
  color: #ffd98a;
}

.cs-warp-doors {
  display: flex;
  gap: 1em;
  justify-content: center;
  flex-wrap: wrap;
}

.cs-warp-doors button {
  position: relative;
  flex: 0 1 15em;
  padding: 1em 1.1em;
  text-align: left;
  color: #f4e2be;
  background: rgba(226, 189, 118, .06);
  border: 1px solid rgba(226, 189, 118, .42);
  border-radius: 10px;
  cursor: pointer;
  font: inherit;
  transition: background .25s ease, transform .25s ease, box-shadow .25s ease;
}

.cs-warp-doors button:hover,
.cs-warp-doors button:focus-visible {
  background: rgba(226, 189, 118, .14);
  transform: translateY(-2px);
  box-shadow: 0 0 26px rgba(255, 200, 110, .28);
  outline: none;
}

.cs-warp-doors button:focus-visible { outline: 2px solid #ffc45c; outline-offset: 3px; }

.cs-warp-doors b {
  display: block;
  font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
  font-size: 1.5em;
  letter-spacing: .1em;
  color: #8fd3ff;
  margin-bottom: .3em;
}

.cs-warp-doors [data-era="past"] b { color: #e2bd76; }

.cs-warp-doors span {
  display: block;
  font-family: "IM Fell English", Georgia, serif;
  font-size: .95em;
  line-height: 1.45;
  opacity: .78;
}

.cs-warp-wip {
  display: inline-block;
  margin-top: .6em;
  padding: .12em .55em;
  font: 700 .62em/1.6 ui-monospace, monospace;
  letter-spacing: .16em;
  text-transform: uppercase;
  font-style: normal;
  color: #0d0a05;
  background: linear-gradient(180deg, #e8c88a, #c9a259);
  border-radius: 999px;
}

/* the ONLY thing that ends the journey early */
.cs-warp-skip {
  position: absolute;
  right: 4vw;
  bottom: 4vh;
  z-index: 3;
  padding: .5em 1em;
  font: 500 clamp(11px, .9vw, 13px)/1 ui-monospace, monospace;
  letter-spacing: .1em;
  color: rgba(244, 226, 190, .6);
  background: rgba(12, 8, 4, .5);
  border: 1px solid rgba(226, 189, 118, .3);
  border-radius: 999px;
  cursor: pointer;
}

.cs-warp-skip:hover { color: #f4e2be; border-color: rgba(226, 189, 118, .7); }

.cs-warp-sub {
  display: block;
  margin-top: 1.1em;
  font-family: ui-monospace, monospace;
  font-size: clamp(9px, .8vw, 12px);
  letter-spacing: .2em;
  text-transform: uppercase;
  color: rgba(244, 226, 190, .45);
}

@media (prefers-reduced-motion: reduce) {
  .cs-warp-tunnel span,
  .cs-warp-years i::after { animation: none; }
  .cs-warp-tunnel span:nth-child(4) { opacity: .5; }
}

/* ---------- §2 shared 16:9 stage ---------- */
.hero-stage {
  position: relative;
  /* Smallest 16:9 box that COVERS the viewport — no bars on normal screens.
     Scaling the whole stage (not just the video) is what keeps every overlay
     locked to the frame: hotspots are % of this box, so they crop with it and
     the terminal stays welded to the glass panel at any size.

     On a 16:9 screen all three terms are equal, so it lands on an exact fit and
     nothing is cropped — identical to before.

     Capped at 1.22x: at most 22% cropped, split evenly 11% top / 11% bottom.
     That budget is set by the two things that must survive — the glass panel
     starts at 24.54% (so 11% off the top is safe) and the journal ends at
     98.15% (so 11% off the bottom clips only the front desk edge). Covers every
     ratio up to ~2.17:1; past that thin bars return rather than eat the frame.

     Nested min/max, NOT clamp(): in clamp() the lower bound wins outright when
     it exceeds the upper, so `clamp(100vw, …, cap)` would blow straight past
     the cap on an ultrawide screen. */
  width: min(max(100vw, calc(100vh * 16 / 9)), calc(100vh * 16 / 9 * 1.22));
  aspect-ratio: 16 / 9;
  flex: 0 0 auto;
  overflow: hidden;
  background: #0a0806;
}

/* §2 letterbox against a dark background on non-16:9 screens — never crop */
body:has(.hero-stage) {
  margin: 0;
  /* height, NOT min-height: the body must stay exactly one viewport tall or it
     grows to fit an over-tall stage, and the whole overflow ends up at the
     bottom instead of splitting evenly. */
  height: 100vh;
  display: flex;
  /* Symmetric: overflow splits evenly, a little off the ceiling machines and a
     little off the front desk edge. The 1.18 cap above is what keeps both the
     glass panel and the journal inside the visible band. */
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: #0a0806;
}

.hero-stage { z-index: 1; }

/* override style.css's fixed/viewport video — stage is already 16:9 */
.hero-stage .banner-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
}

.hero-stage .bg-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: fill;
  z-index: 1;
  transition: filter .45s ease;
}

.hero-stage .bg-video.dimmed {
  filter: brightness(.4) saturate(.9);
}

/* Brief §2 puts this at z-index 3, under the DOM overlays. In practice a bubble
   that renders under the terminal is a bubble nobody sees, and bubbles are
   transient, so this layer sits on top. Children re-enable pointer events. */
#label-layer {
  position: absolute;
  inset: 0;
  z-index: 9;
  pointer-events: none;
}

/* Stacking order inside the stage:
   1 video · 4 hotspots · 5 coins · 7 parchment panels · 8 terminal · 9 bubbles
   The terminal must outrank the hotspots and the coins — expanded it covers
   88% of the stage, and anything above it swallows clicks meant for the shell.
   The z-index lives on .container's own rule below, NOT here: this line used to
   set 8 and the block below then set 4, later in the file, so 4 won and the
   coins drew on top of the expanded shell. */
.hero-stage .steampunk-floating-panels { z-index: 7; }

/* Every scene hotspot sits in here, so their ordering relative to each other
   cannot escape and climb over the terminal or an opened badge. */
.cs-hotspot-layer {
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;   /* children re-enable individually */
}

/* An opened badge or book owns the pointer. Without this a hotspot underneath
   still answered clicks meant for the verify link. */
.hero-stage:has(.cs-plate-wrap.open) .cs-hotspot-layer,
.hero-stage:has(.cs-plate-wrap.open) .cs-coin,
.hero-stage:has(.cs-book-wrap.open) .cs-hotspot-layer,
.hero-stage:has(.cs-book-wrap.open) .cs-coin {
  pointer-events: none;
}

/* Nothing on the desk is live while the shell has the stage.
   Keyed off the terminal's OWN classes, not a class this script sets: the
   shell can also be opened by main.js's maximize button, which sets
   `fullscreen` and never told us — so clicks were still landing on hotspots
   hidden behind a full-screen terminal and popping their bubbles. */
.hero-stage:has(#terminal.expanded) .hotspot,
.hero-stage:has(#terminal.expanded) .cs-coin,
.hero-stage:has(#terminal.fullscreen) .hotspot,
.hero-stage:has(#terminal.fullscreen) .cs-coin,
.hero-stage.cs-expanded .hotspot,
.hero-stage.cs-expanded .cs-coin {
  pointer-events: none;
  opacity: 0;
}

/* ---------- §3 terminal in the measured glass rect ---------- */
/* glass panel: x 567→1331, y 265→688 of 1920×1080 */
.hero-stage .container {
  position: absolute;
  inset: 0;
  display: block;
  height: auto;
  z-index: 8;
  pointer-events: none;
}

/* `.hero-stage .container #terminal` on purpose, not `.hero-stage #terminal`.
   main.js is obfuscated and adds its own state classes (fullscreen, flip, AI
   mode); a rule like `#terminal.whatever` (1,1,0) would outrank the shorter
   form and drag the panel off the glass. This is (1,3,0) and cannot lose. */
.hero-stage .container #terminal {
  position: absolute;
  /* Glass measured NUMERICALLY off poster_day.jpg, not read off a grid by eye.
     The screen is blue where the brass is warm, so a `B > R + 12` mask isolates
     it cleanly: x 31.46–70.00, y 17.87–57.78.
     Overhangs each edge by ~0.2% on purpose — the electrical arcs sit ON the
     glass border (brief §11) and the terminal background is what covers them.
     Any gap here shows an arc, which is what "alignment issue" looked like. */
  left: 31.25%;
  top: 17.65%;
  width: 39.0%;
  height: 40.4%;
  aspect-ratio: auto;
  margin: 0;
  padding: 0;
  /* style.css animates #terminal with a transform; it would displace the
     measured rect. Flip/minimize states re-enable it below. */
  transform: none;
  pointer-events: auto;
  /* B7 — sit inside the machine rather than on top of it.
     The baked housing already provides the brass; what was missing was the
     transition between it and the panel. Three cheap layers do it: a dark
     seated ring where the glass meets the bezel, a warm inner reflection from
     the surrounding metal, and the blue arc spill the brief describes in §11
     (which is real in the video, so matching it makes the edge continuous). */
  border: 1px solid rgba(184, 134, 59, .22);
  /* elliptical: more curve on the left/right edges, to sit with the brass
     machine face's rounded glass rather than reading as a flat web rectangle */
  border-radius: 3.2% / 5.6%;
  overflow: auto;
  background: rgba(0, 0, 0, .55);
  box-shadow:
    inset 0 0 50px rgba(0, 150, 255, .18),          /* arc spill, as in the video */
    inset 0 0 0 1px rgba(0, 0, 0, .85),             /* the seam where glass meets brass */
    inset 0 2px 14px -4px rgba(0, 0, 0, .95),       /* bezel casting down onto the glass */
    inset 0 -10px 26px -14px rgba(255, 186, 92, .5),/* warm bounce off the lower housing */
    inset 6px 0 18px -14px rgba(255, 186, 92, .35), /* left key light, per brief §6 */
    inset -6px 0 18px -14px rgba(120, 190, 255, .3);/* cool window rim from the right */
  font-size: clamp(6px, .55vw, 11px);
  color: #d8e6f0;
  cursor: pointer;
  transition: left .45s cubic-bezier(.4, 0, .2, 1), top .45s cubic-bezier(.4, 0, .2, 1),
    width .45s cubic-bezier(.4, 0, .2, 1), height .45s cubic-bezier(.4, 0, .2, 1),
    font-size .45s ease, background .45s ease;
}

.hero-stage .container #terminal.expanded {
  /* Anchored to the VIEWPORT, not the stage. The stage is deliberately larger
     than the window (it covers rather than letterboxes), so a percentage of it
     put the expanded shell's top edge above the fold — fine in fullscreen,
     clipped in a normal browser window with chrome. Fixed insets always fit. */
  position: fixed;
  left: 3vw;
  top: 3vh;
  width: 94vw;
  height: 94vh;
  border-radius: 12px;
  background: rgba(6, 5, 4, .94);
  box-shadow: 0 0 0 3px #b8863b, 0 0 80px rgba(0, 0, 0, .8);
  font-size: clamp(11px, 1.05vw, 17px);
  cursor: auto;
}

/* two-state banner: ASCII art only fits when expanded (§3 typography) */
.hero-stage #terminal .ascii-art-container { display: none; }
.hero-stage #terminal.expanded .ascii-art-container { display: block; }
/* The banner is ~110 characters wide. It was left at the terminal's own font
   size and simply ran off the edge — "RISHAB D..." and nothing more — which
   looked like the typing animation had stalled. Sized to FIT instead of
   relying on a horizontal scroll nobody finds: at 1920 this is ~11px, so
   110 chars of monospace come to ~730px inside an 1690px panel. */
.hero-stage #terminal .ascii-art {
  white-space: pre;
  overflow-x: auto;
  font-size: clamp(3px, 0.62vw, 11px);
  line-height: 1.15;
  margin: 0 0 .6em;
}

.hero-stage #terminal.expanded .cs-compact-id { display: none; }

.cs-compact-id {
  font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  color: #d8b06a;
  letter-spacing: .08em;
  line-height: 1.5;
}

.cs-compact-id .cs-tag { color: #8fd3ff; }

.cs-caret {
  display: inline-block;
  width: .55em;
  height: 1em;
  background: #64f105;
  vertical-align: text-bottom;
  animation: cs-blink 1.1s steps(1) infinite;
}

@keyframes cs-blink { 50% { opacity: 0; } }

.cs-affordance {
  position: absolute;
  right: .5em;
  bottom: .35em;
  z-index: 6;
  font: inherit;
  font-size: 1.05em;
  color: #ffc45c;
  background: rgba(0, 0, 0, .45);
  border: 1px solid rgba(255, 196, 92, .45);
  border-radius: 4px;
  padding: .1em .45em;
  cursor: pointer;
}

.cs-affordance:focus-visible { outline: 2px solid #ffc45c; }

/* ---------- §4 desk hotspots ---------- */
.hotspot {
  position: absolute;
  z-index: 4;
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  border-radius: 8px;
  transition: box-shadow .3s ease, background .3s ease;
}

/* A rectangle can only look aligned if the rect is perfect. A soft radial glow
   has no edges to misalign, so it reads as "this thing is lit" rather than
   "here is a box that does not quite fit the thing". Same rects, no corners. */
.hotspot::after {
  content: "";
  position: absolute;
  inset: -12%;
  border-radius: 50%;
  background: radial-gradient(closest-side,
      rgba(255, 214, 140, .40) 0%,
      rgba(255, 196, 92, .22) 42%,
      rgba(255, 196, 92, .07) 68%,
      rgba(255, 196, 92, 0) 100%);
  opacity: 0;
  transition: opacity .28s ease, transform .28s ease;
  transform: scale(.86);
  pointer-events: none;
}

.hotspot:hover::after,
.hotspot:focus-visible::after {
  opacity: 1;
  transform: scale(1);
}

.hotspot:hover,
.hotspot:focus-visible {
  background: none;
  box-shadow: none;
  outline: none;
}

/* keyboard focus still needs a hard, visible marker — a glow alone is not one */
.hotspot:focus-visible {
  outline: 2px solid #ffc45c;
  outline-offset: 2px;
  border-radius: 12px;
}

/* --- the click "feel" ---
   Adventure games sell an object by answering the pointer twice: a warm lift on
   hover, and a ring that leaves the object on click. Both are cheap; without
   them an invisible rect reads as dead scenery. */
/* the lift stays; the box does not — the glow is drawn by ::after above */
.hotspot:hover { transform: scale(1.035); }

.hotspot:active { transform: scale(.985); }

/* expanding ring, spawned on click */
.cs-ping {
  position: absolute;
  z-index: 8;
  border: 2px solid rgba(255, 210, 130, .85);
  border-radius: 50%;
  pointer-events: none;
  animation: cs-ping .55s cubic-bezier(.2, .7, .3, 1) forwards;
}

@keyframes cs-ping {
  from { transform: translate(-50%, -50%) scale(.25); opacity: .9; }
  to   { transform: translate(-50%, -50%) scale(1);   opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .hotspot:hover, .hotspot:active { transform: none; }
  .cs-ping { display: none; }
}

/* measured — CLAUDE_CODE_BRIEF.md §4, do not recalculate */
/* Re-measured against poster_day.jpg / poster_night.jpg — the day and night
   frames share one geometry (phase-correlation displacement 0.1–4 px, under
   0.25% of frame width), so there is one table, not two. */
/* Night defaults; chronoshell-stage.js swaps in the day rects on switch.
   All five measured — see measurements/SCENE_MEASURED.json. */
.hotspot[data-panel="trivia"]  { left: 24.79%; top: 69.26%; width: 15.89%; height: 19.53%; }
.hotspot[data-panel="joke"]    { left: 59.06%; top: 74.81%; width: 10.31%; height: 11.11%; }
.hotspot[data-panel="weather"] { left: 78.85%; top: 60.28%; width:  7.65%; height: 25.93%; }
.hotspot[data-cmd="help"]      { left: 44.53%; top: 83.52%; width: 12.03%; height:  5.93%; }
.hotspot[data-cmd="sound"]     { left:  0.42%; top: 53.06%; width: 14.27%; height: 26.48%; }

/* ---------- §5 hint bubbles ---------- */
.cs-bubble {
  position: absolute;
  z-index: 5;
  max-width: 22ch;
  padding: .7em .9em;
  transform: translate(-50%, -100%) scale(.85);
  opacity: 0;
  pointer-events: auto;
  background: #f6e6c4;
  color: #4a3418;
  border: 2px solid #8a6534;
  border-radius: 14px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .55);
  font-family: "Dancing Script", Georgia, serif;
  font-size: clamp(11px, 1.1vw, 19px);
  line-height: 1.25;
  text-align: center;
  transition: opacity .3s ease, transform .3s cubic-bezier(.34, 1.3, .64, 1);
}

.cs-bubble.show { opacity: 1; transform: translate(-50%, -100%) scale(1); }

/* tail pointing down at the object */
.cs-bubble::after,
.cs-bubble::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 100%;
  border: 11px solid transparent;
  border-top-color: #8a6534;
  transform: translateX(-50%);
}

.cs-bubble::after {
  border-width: 9px;
  border-top-color: #f6e6c4;
  margin-top: -2px;
}

/* click-to-read object bubbles: same component, a touch wider for prose */
.cs-bubble--info { max-width: 30ch; }

/* The Gurudev bubble is the same component as every other object bubble — same
   face, same size, same fill. Only the width is raised, because it carries two
   full sentences where the others carry one line. */
.cs-bubble--reverent { max-width: 42ch; }

/* flipped: object sits near the top of the frame, so the bubble hangs below it
   and the tail points up */
.cs-bubble--below { transform: translate(-50%, 0) scale(.85); }
.cs-bubble--below.show { transform: translate(-50%, 0) scale(1); }

.cs-bubble--below::before,
.cs-bubble--below::after {
  top: auto;
  bottom: 100%;
  border-top-color: transparent;
}

.cs-bubble--below::before { border-bottom-color: #8a6534; }

.cs-bubble--below::after {
  border-bottom-color: #f6e6c4;
  margin-top: 0;
  margin-bottom: -2px;
}

/* `hotspots` terminal command draws every box so the rects can be nudged
   against the real frame instead of guessed at */
.hero-stage.cs-calibrate .hotspot {
  outline: 1px dashed rgba(255, 196, 92, .9);
  background: rgba(255, 196, 92, .10);
}

/* calibration must win over the expanded-terminal stand-down rule, or the boxes
   vanish the moment the shell is open */
.hero-stage.cs-calibrate .hotspot {
  opacity: 1;
  pointer-events: auto;
}

.cs-calibrate-banner {
  position: fixed;
  left: 50%;
  bottom: 16px;
  z-index: 999;
  transform: translateX(-50%);
  padding: 7px 14px;
  font: 12px/1.3 ui-monospace, monospace;
  color: #ffe6b0;
  background: rgba(20, 12, 4, .92);
  border: 1px solid #b8863b;
  border-radius: 6px;
  pointer-events: none;
}

.hero-stage.cs-calibrate .hotspot::after {
  content: attr(data-label);
  position: absolute;
  left: 0;
  top: 0;
  font: 10px/1.2 ui-monospace, monospace;
  color: #ffc45c;
  background: rgba(0, 0, 0, .7);
  padding: 1px 3px;
  white-space: nowrap;
}

/* ---------- §7 portrait frame (covers the sparkle watermark) ---------- */
.portrait {
  position: absolute;
  left: 86.5%;
  top: 72%;
  width: 8.5%;
  margin: 0;
  z-index: 4;
}

/* Slightly narrower and taller than the rect in §7 — the supplied photograph is
   a 0.84 portrait and the original near-square rect cropped his head. Still
   covers the watermark (x 89.17–92.14, y 79.81–87.12) with margin on all sides:
   this runs x 86.5–95, y 72–92. */
/* Clickable at Rishab's request, overriding brief §7's "no hover, no click",
   and answering the pointer exactly like every other object in the room. */
.portrait-frame {
  position: relative;
  display: block;
  padding: 0;
  cursor: pointer;
  transition: box-shadow .3s ease, transform .3s ease;
  width: 100%;
  /* exactly the photograph's ratio — 470x560 — so `cover` has nothing to crop.
     aspect-ratio rather than a padding-bottom hack: percentage padding resolves
     against the CONTENT width, so the border threw the ratio out by ~1%. */
  aspect-ratio: 470 / 560;
  border-radius: 2px;

  /* A gilt moulding is never one flat band — it is a stack of steps, each
     catching light on its upper-left and shading on its lower-right. Six
     box-shadow rings do that for free: dark rebate, gold bead, dark valley,
     broad gold ogee, dark backing, then the drop shadow onto the books. */
  border: .18vw solid #2a1c0c;
  box-shadow:
    0 0 0 .10vw #6b4a1c,
    0 0 0 .20vw #e8c88a,
    0 0 0 .30vw #8a6534,
    0 0 0 .62vw #c9a259,
    0 0 0 .74vw #4a3316,
    0 6px 20px rgba(0, 0, 0, .8),
    inset 0 0 14px rgba(0, 0, 0, .55);
}

/* the ogee's own highlight: warm along the top-left, falling away bottom-right,
   matching the room's key light (§6) so the moulding reads as carved */
.portrait-frame::before {
  content: "";
  position: absolute;
  inset: -.68vw;
  border-radius: 3px;
  z-index: -1;
  background: linear-gradient(128deg,
      rgba(255, 232, 180, .55) 0%, rgba(255, 210, 130, .12) 26%,
      rgba(0, 0, 0, .10) 58%, rgba(0, 0, 0, .45) 100%);
  pointer-events: none;
}

.portrait-frame img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  /* the photograph has a cold blue studio backdrop; the room is amber and lit
     by candles. Sepia carries the blue over to a warm brown so it reads as a
     period print on the desk rather than a modern JPEG pasted into the scene. */
  filter: brightness(.82) sepia(.42) contrast(1.06) saturate(.85);
}

/* same warm answer the hotspots give, sitting outside the gilt rings so the
   moulding is not washed out */
.portrait-frame:hover,
.portrait-frame:focus-visible {
  transform: scale(1.035);
  outline: none;
}

.portrait-frame:hover { filter: drop-shadow(0 0 26px rgba(255, 200, 110, .55)); }
.portrait-frame:focus-visible { filter: drop-shadow(0 0 0 2px #ffc45c) drop-shadow(0 0 26px rgba(255, 200, 110, .55)); }
.portrait-frame:active { transform: scale(.985); }

@media (prefers-reduced-motion: reduce) {
  .portrait-frame:hover,
  .portrait-frame:focus-visible,
  .portrait-frame:active { transform: none; }
}

/* candlelight falling across the glass, warm from the left as §6 describes */
.portrait-frame::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(118deg,
      rgba(255, 206, 140, .22) 0%, rgba(255, 206, 140, .05) 38%,
      rgba(0, 0, 0, .16) 72%, rgba(0, 0, 0, .34) 100%);
}

/* a small hand-inked card at the foot of the frame — part of the room, not UI */
.portrait-caption {
  display: block;
  margin-top: .35em;
  text-align: center;
  line-height: 1.25;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .9);
}

.portrait-name {
  display: block;
  font-family: Georgia, serif;
  font-size: clamp(6px, .58vw, 11px);
  letter-spacing: .04em;
  color: #e8c98a;
}

.portrait-line {
  display: block;
  font-family: "Dancing Script", Georgia, serif;
  font-size: clamp(8px, .78vw, 15px);
  color: #f4e2be;
  opacity: .92;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* The three parchment panels are never shown any more — the teacup and the
   lantern answer in object bubbles and the journal opens as the book. They stay
   in the document because main.js writes their content and we read it back.

   Hidden HERE, in CSS, not by the script: style.css parks the weather panel at
   top-right and main.js sets an inline display on all three, so until the stage
   script ran there was a visible flash of the old popup on every page load.
   `!important` because the inline style would otherwise win. */
#trivia-dropdown,
#joke-dropdown,
#weather-popup {
  display: none !important;
}

/* ---------- §8 parchment panels: closed by default, beside their object ---------- */
.hero-stage .steampunk-floating-panels { z-index: 6; }

.hero-stage .steampunk-floating-panels .info-dropdown,
.hero-stage .steampunk-floating-panels #weather-popup {
  position: absolute;
  transform-origin: var(--cs-origin, 50% 100%);
  animation: cs-unfurl .35s cubic-bezier(.34, 1.3, .64, 1);
}

@keyframes cs-unfurl {
  from { transform: scale(.7); opacity: 0; }
  to   { transform: scale(1);  opacity: 1; }
}

/* left/right thirds only — never over the glass rect (29.5%→69.3%) */
.hero-stage #trivia-dropdown {
  left: 2%;
  top: 30%;
  right: auto;
  bottom: auto;
  --cs-origin: 40% 100%;
  max-height: 42%;
  overflow-y: auto;
}

.hero-stage #joke-dropdown {
  left: auto;
  right: 2%;
  top: auto;
  bottom: 18%;
  --cs-origin: 20% 100%;
}

.hero-stage #weather-popup.victorian-card.poster {
  left: auto;
  right: 2%;
  top: 12%;
  bottom: auto;
  --cs-origin: 30% 100%;
}

/* brass rail scrollbar (§8) */
.hero-stage #trivia-dropdown::-webkit-scrollbar { width: 8px; }
.hero-stage #trivia-dropdown::-webkit-scrollbar-track { background: rgba(60, 42, 20, .35); border-radius: 4px; }
.hero-stage #trivia-dropdown::-webkit-scrollbar-thumb {
  background: linear-gradient(180deg, #e2bd76, #8a6534);
  border-radius: 4px;
}

/* ---------- The journal, opening at centre stage ----------
   CSS 3D, not WebGL. Brief §6 reserves WebGL for the coins, where a metallic
   specular is the entire point; parchment has no specular to miss, and §7's
   reasoning applies here too — clean WebGL output sits badly against baked
   lighting and grain. This gets the same trick for no dependency and degrades
   to a plain fade when 3D transforms are unavailable. */
.cs-book-wrap {
  position: absolute;
  inset: 0;
  z-index: 20;
  display: grid;
  place-items: center;
  perspective: 1800px;
  background: radial-gradient(ellipse at center, rgba(0, 0, 0, .45) 0%, rgba(0, 0, 0, .82) 75%);
  opacity: 0;
  pointer-events: none;
  transition: opacity .35s ease;
}

.cs-book-wrap.open { opacity: 1; pointer-events: auto; }

.cs-book {
  position: relative;
  width: 58%;
  height: 62%;
  transform-style: preserve-3d;
  /* flies up out of the journal on the desk, then settles facing the viewer */
  transform: translateY(26%) rotateX(52deg) scale(.42);
  transition: transform .62s cubic-bezier(.22, .9, .3, 1.05);
}

.cs-book-wrap.open .cs-book { transform: translateY(0) rotateX(8deg) scale(1); }

.cs-page {
  position: absolute;
  top: 0;
  width: 50%;
  height: 100%;
  overflow: hidden;
  /* vintage_paper.jpg is a whole decorative spread with a central ornament, so
     stretch one copy across BOTH pages — each page shows its own half and the
     ornament lands in the gutter where a real binding would put it, instead of
     being duplicated twice under the text. */
  background:
    linear-gradient(180deg, rgba(70, 48, 20, .13), rgba(70, 48, 20, .03) 22%, rgba(70, 48, 20, .16)),
    url("vintage_paper.jpg") no-repeat, #efe0bd;
  background-size: auto, 200% 100%, auto;
  box-shadow: inset 0 0 60px rgba(96, 66, 26, .45);
  backface-visibility: hidden;
}

.cs-page--l { background-position: center, left center, center; }
.cs-page--r { background-position: center, right center, center; }

/* keep the ornament from competing with the writing — the wash sits on the
   page, under the inner, so it lightens the texture without touching the text
   (the trivia HTML arrives as bare text nodes, so it cannot be raised above a
   wash placed inside the same element) */
.cs-page::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  /* papyrus wash — heavy enough that the ornament reads as watermark under the
     text rather than competing with it */
  background:
    radial-gradient(ellipse at 50% 45%, rgba(246, 233, 202, .93) 0%, rgba(243, 226, 189, .82) 55%, rgba(232, 210, 165, .62) 100%);
  pointer-events: none;
}

/* Torn outer edge, Victorian-tome style. Reuses the #v-wavy turbulence filter
   already defined in index.html — the displacement chews an irregular line out
   of a plain strip. It sits on its own layer so the text is never distorted. */
.cs-page::after {
  content: "";
  position: absolute;
  top: -2%;
  height: 104%;
  width: 5%;
  z-index: 2;
  background: #efe0bd;
  filter: url(#v-wavy);
  pointer-events: none;
}

.cs-page--l::after { left: -2.5%; }
.cs-page--r::after { right: -2.5%; }

/* the torn lip catches the candlelight along its ragged line */
.cs-page--l { box-shadow: inset 0 0 60px rgba(96, 66, 26, .45), inset 10px 0 22px -14px rgba(60, 38, 10, .8); }
.cs-page--r { box-shadow: inset 0 0 60px rgba(96, 66, 26, .45), inset -10px 0 22px -14px rgba(60, 38, 10, .8); }

.cs-page-inner { position: relative; z-index: 1; }

.cs-page--l {
  left: 0;
  transform-origin: right center;
  border-radius: 10px 3px 3px 10px;
  transform: rotateY(88deg);
  transition: transform .62s cubic-bezier(.22, .9, .3, 1.05) .12s;
}

.cs-page--r {
  right: 0;
  transform-origin: left center;
  border-radius: 3px 10px 10px 3px;
  transform: rotateY(-88deg);
  transition: transform .62s cubic-bezier(.22, .9, .3, 1.05) .12s;
}

.cs-book-wrap.open .cs-page--l { transform: rotateY(6deg); }
.cs-book-wrap.open .cs-page--r { transform: rotateY(-6deg); }

/* the gutter shadow is what sells it as one bound object rather than two cards */
.cs-book::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 0;
  width: 7%;
  height: 100%;
  transform: translateX(-50%);
  background: linear-gradient(90deg,
      rgba(0, 0, 0, 0), rgba(60, 40, 14, .55) 45%, rgba(60, 40, 14, .55) 55%, rgba(0, 0, 0, 0));
  pointer-events: none;
}

/* Readability beats atmosphere. The body was set in Dancing Script — a
   handwriting face — over a busy texture, which is unreadable at any size for
   prose. Script is now reserved for the title block; the entries are set in a
   plain old-style serif, larger, looser and in darker ink. */
.cs-page-inner {
  padding: 8% 9%;
  height: 100%;
  overflow-y: auto;
  color: #2b1a08;
  /* IM Fell English — a real English press face still in use through the
     Victorian era. Period-correct and readable as prose, unlike a script.
     It runs small and light for its point size, so it gets a size and weight
     bump and a touch more line-height than a modern serif would need. */
  font-family: "IM Fell English", Georgia, "Iowan Old Style", "Palatino Linotype", serif;
  font-size: clamp(14px, 1.28vw, 21px);
  line-height: 1.78;
  letter-spacing: .004em;
}

.cs-page--l .cs-page-inner { text-align: center; }

/* the script survives here, where it is three words and decorative */
/* Tangerine is English roundhand — the copperplate a Victorian would actually
   have written a title page in, where Dancing Script is a modern brush hand.
   Already loaded for the sticky note, so it costs no extra request. It is a
   very light face, hence 700 weight and the larger sizes below. */
.cs-book-title,
.cs-book-sub,
.cs-book-date {
  font-family: Tangerine, "Dancing Script", cursive;
  font-weight: 700;
}

.cs-book-title {
  font-size: 3em;
  font-weight: 700;
  line-height: 1.15;
  margin-bottom: .35em;
  color: #4a2c0c;
}

.cs-book-sub { font-size: 1.75em; line-height: 1.15; opacity: .8; }

/* --- the old-mystical dressing: gilt edges, candlelight, ink --- */
.cs-book-rule {
  width: 62%;
  height: 2px;
  margin: .9em auto;
  background: linear-gradient(90deg, transparent, #a3762f 18%, #e0bb74 50%, #a3762f 82%, transparent);
}

.cs-book-rule::after {
  content: "❦";
  display: block;
  margin-top: -.75em;
  color: #8a6534;
  font-size: 1.1em;
  background: transparent;
}

.cs-book-date {
  margin-top: 1.1em;
  font-size: 1.9em;
  color: #6b4a1c;
  letter-spacing: .04em;
}

/* gilded page edges — the thin bright line that says "this book is old and cared for" */
.cs-page--l { box-shadow: inset 0 0 60px rgba(96, 66, 26, .45), inset 6px 0 0 -3px #c9a259; }
.cs-page--r { box-shadow: inset 0 0 60px rgba(96, 66, 26, .45), inset -6px 0 0 -3px #c9a259; }

/* candle throw from the desk below, plus the burn at the page corners */
.cs-page-inner {
  background:
    radial-gradient(ellipse at 50% 120%, rgba(255, 196, 92, .28), transparent 60%),
    radial-gradient(ellipse at 15% 8%, rgba(90, 58, 20, .22), transparent 45%),
    radial-gradient(ellipse at 88% 92%, rgba(90, 58, 20, .26), transparent 50%);
}

/* entries are the thing people actually read — give them room and separation */
.cs-book-body { text-align: left; }
.cs-book-body br + br { line-height: 2.4; }

/* the book throws its own light into the room while it is open */
.cs-book {
  filter: drop-shadow(0 26px 46px rgba(0, 0, 0, .8))
          drop-shadow(0 0 60px rgba(255, 190, 100, .22));
}

/* motes drifting in the candlelight — one element, no JS */
.cs-book-wrap.open::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    radial-gradient(2px 2px at 20% 30%, rgba(255, 226, 170, .55), transparent 60%),
    radial-gradient(2px 2px at 72% 22%, rgba(255, 226, 170, .40), transparent 60%),
    radial-gradient(1.5px 1.5px at 38% 74%, rgba(255, 226, 170, .45), transparent 60%),
    radial-gradient(1.5px 1.5px at 84% 66%, rgba(255, 226, 170, .35), transparent 60%),
    radial-gradient(2px 2px at 56% 48%, rgba(255, 226, 170, .30), transparent 60%);
  animation: cs-motes 14s ease-in-out infinite alternate;
}

@keyframes cs-motes {
  from { transform: translate3d(0, 0, 0); opacity: .5; }
  to   { transform: translate3d(-18px, -26px, 0); opacity: .95; }
}

.cs-page-inner::-webkit-scrollbar { width: 7px; }
.cs-page-inner::-webkit-scrollbar-thumb {
  background: linear-gradient(180deg, #b08a4a, #6b4a1c);
  border-radius: 4px;
}

/* --- turning leaf ---
   One leaf does the whole job: it carries the current right-hand page on its
   front and the next left-hand page on its back, swings on the spine, and when
   it lands the two static pages are swapped to the new spread. A real stack of
   leaves would be prettier and about ten times the code. */
.cs-leaf {
  position: absolute;
  top: 0;
  left: 50%;
  width: 50%;
  height: 100%;
  z-index: 3;
  transform-origin: left center;
  transform-style: preserve-3d;
  transition: transform .78s cubic-bezier(.42, .02, .34, 1);
  pointer-events: none;
  display: none;
}

.cs-leaf.turning { display: block; transform: rotateY(-176deg); }
.cs-leaf.turning-back { display: block; transform: rotateY(0deg); }
.cs-leaf.at-left { transform: rotateY(-176deg); }

.cs-leaf-face {
  position: absolute;
  inset: 0;
  overflow: hidden;
  backface-visibility: hidden;
  background:
    linear-gradient(180deg, rgba(70, 48, 20, .13), rgba(70, 48, 20, .03) 22%, rgba(70, 48, 20, .16)),
    url("vintage_paper.jpg") no-repeat, #efe0bd;
  background-size: auto, 200% 100%, auto;
  box-shadow: inset 0 0 60px rgba(96, 66, 26, .45);
}

.cs-leaf-front { background-position: center, right center, center; border-radius: 3px 10px 10px 3px; }

.cs-leaf-back {
  transform: rotateY(180deg);
  background-position: center, left center, center;
  border-radius: 10px 3px 3px 10px;
}

.cs-leaf-face::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at 50% 45%, rgba(246, 233, 202, .93) 0%, rgba(243, 226, 189, .82) 55%, rgba(232, 210, 165, .62) 100%);
}

/* the shading that sells paper bending rather than a flat card rotating */
.cs-leaf-face::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, rgba(60, 40, 14, .38), rgba(60, 40, 14, 0) 32%);
  opacity: 0;
  transition: opacity .78s ease;
}

.cs-leaf.turning .cs-leaf-face::after,
.cs-leaf.turning-back .cs-leaf-face::after { opacity: 1; }

/* corner tabs — the affordance that says the pages move */
.cs-turn {
  position: absolute;
  bottom: 3%;
  z-index: 4;
  padding: .35em .8em;
  font: 1em/1 "Dancing Script", Georgia, serif;
  color: #f6e6c4;
  background: linear-gradient(180deg, rgba(107, 74, 28, .92), rgba(51, 34, 13, .92));
  border: 1px solid #c9a259;
  border-radius: 6px;
  cursor: pointer;
  opacity: .85;
}

.cs-turn:hover { opacity: 1; }
.cs-turn[disabled] { opacity: .25; cursor: default; }
.cs-turn--prev { left: 4%; }
.cs-turn--next { right: 4%; }

.cs-book-folio {
  position: absolute;
  bottom: 3.2%;
  left: 50%;
  transform: translateX(-50%);
  z-index: 4;
  font: .8em/1 Georgia, serif;
  color: #6b4a1c;
  opacity: .8;
}

@media (prefers-reduced-motion: reduce) {
  .cs-leaf { transition: none; }
}

.cs-book-close {
  position: absolute;
  top: -4%;
  right: -3%;
  z-index: 2;
  width: 2.2em;
  height: 2.2em;
  font: 700 1em/1 ui-monospace, monospace;
  color: #f6e6c4;
  background: linear-gradient(180deg, #6b4a1c, #33220d);
  border: 1px solid #e2bd76;
  border-radius: 50%;
  cursor: pointer;
}

@media (max-width: 768px) {
  .cs-book { width: 92%; height: 70%; }
  .cs-page-inner { font-size: 15px; padding: 10% 8%; }
}

@media (prefers-reduced-motion: reduce) {
  /* The opening is the point of the book, so it survives here — same call as
     the background video. Only the ambient drift is dropped. */
  .cs-book-wrap.open::after { animation: none; }
}

/* ---------- Certification coins ----------
   The coins are baked into the video, but the model rendered their lettering
   wrong (`GGP`, `Aeuro`, `LeerCedo`). These PNGs sit exactly on top of the
   baked ones, so the desk reads correctly. Positioned in % of the stage, which
   is what welds them to the video at any size — the stage scales, they scale
   with it. */
.cs-coin {
  position: absolute;
  z-index: 5;
  border: 0;
  padding: 0;
  background: none;
  cursor: pointer;
  transition: transform .28s cubic-bezier(.34, 1.3, .64, 1), filter .28s ease;
  /* the baked coins are lit warm from the left; match or they read as stickers */
  filter: drop-shadow(0 3px 6px rgba(0, 0, 0, .75)) saturate(.92) brightness(.96);
}

.cs-coin img {
  width: 100%;
  height: 100%;
  /* `fill`, not `contain`. The baked coins are seen at a desk angle, so they
     are ELLIPSES — 111x93 px at 1080p. `contain` fitted the round PNG to the
     shorter side and left it 93x93, too small to cover the garbled lettering
     underneath, and unevenly so per coin. Filling stretches the circle into
     the same ellipse the video already shows. */
  object-fit: fill;
  display: block;
  pointer-events: none;
}

.cs-coin:hover,
.cs-coin:focus-visible {
  transform: translateY(-6%) scale(1.06);
  filter: drop-shadow(0 8px 14px rgba(0, 0, 0, .8)) drop-shadow(0 0 18px rgba(255, 200, 110, .6));
  outline: none;
}

.cs-coin:active { transform: translateY(-2%) scale(1.01); }

/* --- the opened plate, hovering at centre stage --- */
.cs-plate-wrap {
  position: absolute;
  inset: 0;
  z-index: 20;
  display: grid;
  place-items: center;
  perspective: 1600px;
  background: radial-gradient(ellipse at center, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .86) 75%);
  opacity: 0;
  pointer-events: none;
  transition: opacity .35s ease;
}

.cs-plate-wrap.open { opacity: 1; pointer-events: auto; }

.cs-plate-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3vh;
}

.cs-plate {
  position: relative;
  width: min(34vw, 46vh);
  aspect-ratio: 1;
  transform-style: preserve-3d;
  /* two motions at once: a slow turn, and a gentle float so it reads as
     hovering rather than mounted on a spindle */
  animation: cs-plate-spin 9s linear infinite, cs-plate-float 4.5s ease-in-out infinite;
}

@keyframes cs-plate-spin {
  from { transform: rotateY(0deg); }
  to   { transform: rotateY(360deg); }
}

@keyframes cs-plate-float {
  0%, 100% { translate: 0 -1.2%; }
  50%      { translate: 0 1.2%; }
}

.cs-plate-face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  display: grid;
  place-items: center;
}

.cs-plate-face img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  filter: drop-shadow(0 0 26px rgba(255, 200, 110, .35));
}

/* The reverse. A flat image turned 180° would show a mirrored front, which
   looks like a mistake — so the back is its own face: engraved brass carrying
   the credential, exactly as brief §6 specified for Face B. */
.cs-plate-back {
  transform: rotateY(180deg);
  border-radius: 50%;
  padding: 12%;
  text-align: center;
  color: #f0d9a4;
  background:
    radial-gradient(circle at 32% 28%, #8a6a33 0%, #5b431d 45%, #33240e 100%);
  box-shadow: inset 0 0 40px rgba(0, 0, 0, .7), 0 0 0 .4vh #c9a259;
  font-family: Georgia, serif;
  text-shadow: 0 1px 0 rgba(0, 0, 0, .6);
}

.cs-plate-back b { display: block; font-size: 1.35em; margin-bottom: .35em; }
.cs-plate-back span { display: block; opacity: .8; font-size: .95em; }

/* the mirrored reverse: same art, flipped, slightly cooled so it reads as the
   far side of one object rather than a second copy pasted on */
.cs-plate-back--mirror {
  background: none;
  box-shadow: none;
  padding: 0;
  border-radius: 0;
}

.cs-plate-back--mirror img {
  transform: scaleX(-1);
  filter: brightness(.72) saturate(.8) drop-shadow(0 0 22px rgba(255, 200, 110, .22));
}

/* a certificate is landscape; it gets a plate that is not square */
.cs-plate--cert { width: min(52vw, 74vh); aspect-ratio: 1.4; }
.cs-plate--cert .cs-plate-back { border-radius: 6px; }

.cs-plate-caption {
  max-width: min(60vw, 60ch);
  text-align: center;
  font-family: "IM Fell English", Georgia, serif;
  color: #f4e2be;
  font-size: clamp(13px, 1.25vw, 21px);
  line-height: 1.5;
  text-shadow: 0 2px 6px rgba(0, 0, 0, .9);
}

.cs-plate-caption b { color: #ffd98a; }
.cs-plate-caption em { display: block; opacity: .78; font-style: normal; font-size: .85em; }

.cs-plate-verify {
  display: inline-block;
  margin-top: .6em;
  padding: .3em .9em;
  color: #2b1a08;
  background: linear-gradient(180deg, #e8c88a, #c9a259);
  border: 1px solid #8a6534;
  border-radius: 4px;
  text-decoration: none;
  font-size: .85em;
}

.cs-plate-verify:hover { background: linear-gradient(180deg, #f6dda8, #d9b46a); }

.cs-plate-close {
  position: absolute;
  top: 4%;
  right: 4%;
  z-index: 2;
  width: 2.2em;
  height: 2.2em;
  font: 700 1em/1 ui-monospace, monospace;
  color: #f6e6c4;
  background: linear-gradient(180deg, #6b4a1c, #33220d);
  border: 1px solid #e2bd76;
  border-radius: 50%;
  cursor: pointer;
}

@media (max-width: 768px) {
  .cs-coin { display: none; }          /* §9 — 25px coins are untappable */
  .cs-plate { width: 78vw; }
  .cs-plate--cert { width: 92vw; }
}

@media (prefers-reduced-motion: reduce) {
  /* The turn keeps its normal speed. Slowing it to 14s here was meant as a
     courtesy and backfired: it reads as a static badge rather than a turning
     one, which is the whole feature. Same call already made for the hero video
     and the book. Only the idle float and the pointer lift are dropped. */
  .cs-plate { animation: cs-plate-spin 9s linear infinite; }
  .cs-coin:hover, .cs-coin:focus-visible, .cs-coin:active { transform: none; }
}

/* ---------- the credit line ----------
   It shipped as a big yellow pill parked across the middle of the desk, which
   is exactly where the coins sit (x 32–71%, y 89–98%). Shrunk and moved into
   the bottom-left corner, clear of the coin band, and dimmed until hovered —
   it is a credit, not a call to action. */
body:has(.hero-stage) .footer.center-footer {
  position: absolute;
  left: 1.2%;
  right: auto;
  bottom: 1%;
  z-index: 6;
  margin: 0;
  padding: 0;
  width: auto;
  background: none;
  text-align: left;
  opacity: .5;
  transition: opacity .3s ease;
}

body:has(.hero-stage) .footer.center-footer:hover { opacity: 1; }

body:has(.hero-stage) .footer.center-footer p {
  margin: 0;
  padding: .25em .7em;
  font-family: Georgia, serif;
  font-size: clamp(8px, .68vw, 12px);
  color: #e8c98a;
  background: rgba(12, 8, 4, .55);
  border: 1px solid rgba(184, 134, 59, .35);
  border-radius: 999px;
  white-space: nowrap;
}

body:has(.hero-stage) .footer.center-footer a { color: #ffd98a; }

@media (max-width: 768px) {
  body:has(.hero-stage) .footer.center-footer { left: 50%; transform: translateX(-50%); }
}

/* ---------- §9 mobile ---------- */
.cs-dock {
  display: none;
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 6;
  gap: 10px;
  justify-content: center;
  padding: 8px;
  background: linear-gradient(0deg, rgba(10, 8, 6, .92), rgba(10, 8, 6, 0));
}

.cs-dock button {
  font-size: 20px;
  line-height: 1;
  padding: 10px 14px;
  color: #f6e6c4;
  background: linear-gradient(180deg, #6b4a1c, #3a2711);
  border: 1px solid #e2bd76;
  border-radius: 10px;
  cursor: pointer;
}

@media (max-width: 768px) {
  .hero-stage { aspect-ratio: auto; height: 100vh; height: 100dvh; }
  .hero-stage .bg-video { object-fit: cover; }
  /* must match the desktop rule's specificity or it will never apply */
  .hero-stage .container #terminal,
  .hero-stage .container #terminal.expanded {
    left: 4%;
    top: 4%;
    width: 92%;
    height: 74%;
    font-size: clamp(11px, 3.2vw, 15px);
  }
  .hero-stage #terminal .ascii-art-container { display: block; }
  .hero-stage .cs-compact-id,
  .cs-affordance,
  .hotspot,
  .portrait { display: none; }
  .cs-dock { display: flex; }
  .hero-stage .steampunk-floating-panels .info-dropdown,
  .hero-stage .steampunk-floating-panels #weather-popup {
    left: 4%;
    right: 4%;
    top: 10%;
    bottom: auto;
    max-height: 60%;
    overflow-y: auto;
  }
}

/* ---------- §10 reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  /* DELIBERATE DEVIATION from brief §10: the brief says swap the video for the
     poster here. Rishab wants the background video playing regardless — it is a
     slow, deliberate loop (§11), not a motion hazard. The poster still covers
     first paint, and every *interface* animation below is still disabled. */
  /* Only the UNPROMPTED hint bubbles are suppressed (brief §5). A bubble the
     visitor opened by clicking an object is a response, not motion — killing
     those made every object look broken. */
  .cs-bubble:not(.cs-bubble--info) { display: none !important; }
  .cs-bubble--info { transition: none; }
  .hero-stage #terminal,
  .hero-stage .steampunk-floating-panels .info-dropdown,
  .hero-stage .steampunk-floating-panels #weather-popup {
    transition: none;
    animation: none;
  }
}


/* ===========================================================================
   THE FRONT DOOR, SECOND PASS

   The first version was two flat panels and a sheen. This adds the things a
   game main-menu actually has: a portal breathing behind the copy, a rotating
   light around each card's edge, an era emblem that moves (brass gear for
   1888, orbiting bodies for 2026), a spotlight that follows the pointer, and a
   card that tilts towards it.

   All of it is CSS. The only thing JavaScript contributes is four numbers per
   card — --mx, --my, --rx, --ry — so nothing here waits on a script to paint.
   These rules come after the originals on purpose: same specificity, later
   wins, and the first pass stays readable as the fallback it now is.
   ========================================================================= */

/* --- the portal: concentric rings easing outward, forever ---------------- */
#cs-warp::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: min(150vh, 150vw);
  aspect-ratio: 1;
  translate: -50% -50%;
  z-index: 1;
  pointer-events: none;
  opacity: .5;
  background: repeating-radial-gradient(circle at center,
      rgba(143, 211, 255, .085) 0 1px,
      transparent 1px 74px);
  mask-image: radial-gradient(circle at center, #000 8%, transparent 62%);
  -webkit-mask-image: radial-gradient(circle at center, #000 8%, transparent 62%);
  animation: cs-portal 9s cubic-bezier(.4, 0, .5, 1) infinite;
}

@keyframes cs-portal {
  0%   { transform: scale(.55); opacity: 0; }
  22%  { opacity: .55; }
  100% { transform: scale(1.45); opacity: 0; }
}

/* --- title: letters arrive one at a time, then the line catches the light - */
.cs-gate-title .ltr {
  display: inline-block;
  opacity: 0;
  transform: translateY(.5em) rotateX(-70deg);
  animation: cs-drop .62s cubic-bezier(.2, .8, .25, 1) forwards;
}
.cs-gate-title .ltr.sp { width: .32em; }

@keyframes cs-drop {
  to { opacity: 1; transform: none; }
}

.cs-gate-title {
  position: relative;
  background: linear-gradient(100deg, #fff6e4 30%, #ffe6b0 44%, #8fd3ff 52%, #fff6e4 66%);
  background-size: 260% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: cs-sheen 7s linear infinite 1.2s;
}
@keyframes cs-sheen {
  0%   { background-position: 130% 0; }
  100% { background-position: -130% 0; }
}

/* --- the cards ----------------------------------------------------------- */
.cs-gate-card {
  transform-style: preserve-3d;
  transform:
    perspective(1000px)
    rotateX(var(--rx, 0deg))
    rotateY(var(--ry, 0deg))
    translateY(var(--lift, 0px));
  transition: transform .35s cubic-bezier(.22, .9, .3, 1), box-shadow .5s ease;
  will-change: transform;
}
.cs-gate-card:hover,
.cs-gate-card:focus-visible { --lift: -6px; }
.cs-gate-card:active { --lift: -2px; }

/* the rotating edge light. A conic gradient the size of the card, masked down
   to a 1px ring by two backgrounds clipped differently — no SVG, no filter. */
.cs-card-ring {
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  opacity: .55;
  background: conic-gradient(from var(--a, 0deg),
      transparent 0deg,
      rgba(226, 189, 118, .95) 26deg,
      rgba(143, 211, 255, .85) 52deg,
      transparent 90deg,
      transparent 360deg);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  -webkit-mask-composite: xor;
  animation: cs-ring 6s linear infinite;
}
.cs-gate-card[data-go="present"] .cs-card-ring {
  background: conic-gradient(from var(--a, 0deg),
      transparent 0deg,
      rgba(143, 211, 255, .95) 26deg,
      rgba(167, 139, 250, .85) 52deg,
      transparent 90deg,
      transparent 360deg);
  animation-direction: reverse;
}
.cs-gate-card:hover .cs-card-ring { opacity: 1; }

/* `--a` has to be registered for a conic gradient's angle to animate at all;
   without @property this steps rather than sweeps. Browsers without it simply
   get a still ring, which is a perfectly good card edge. */
@property --a {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}
@keyframes cs-ring { to { --a: 360deg; } }

/* the spotlight — coordinates come from the pointer, so it only exists on a
   device that has one */
.cs-card-glow {
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity .35s ease;
  background: radial-gradient(14rem 14rem at var(--mx, 50%) var(--my, 50%),
      rgba(255, 236, 190, .18), transparent 68%);
}
.cs-gate-card[data-go="present"] .cs-card-glow {
  background: radial-gradient(14rem 14rem at var(--mx, 50%) var(--my, 50%),
      rgba(160, 210, 255, .20), transparent 68%);
}
.cs-gate-card:hover .cs-card-glow { opacity: 1; }

/* keep the copy above both decorative layers */
.cs-gate-year,
.cs-gate-card b,
.cs-gate-note,
.cs-gate-cta { position: relative; z-index: 2; }

/* --- era emblems --------------------------------------------------------- */
.cs-emblem {
  position: absolute;
  right: clamp(.9rem, 2.4vw, 1.6rem);
  top: clamp(.9rem, 2.4vw, 1.6rem);
  width: clamp(2.6rem, 5vw, 3.6rem);
  aspect-ratio: 1;
  z-index: 1;
  pointer-events: none;
  opacity: .55;
  transition: opacity .4s ease, transform .6s cubic-bezier(.22,.9,.3,1);
}
.cs-gate-card:hover .cs-emblem { opacity: 1; transform: scale(1.08); }

/* 1888 — a brass gear. Teeth are a conic gradient, the hub is a mask. */
.cs-emblem-gear i {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: conic-gradient(from 0deg,
      #e2bd76 0 5.5%, transparent 5.5% 12.5%,
      #e2bd76 12.5% 18%, transparent 18% 25%,
      #e2bd76 25% 30.5%, transparent 30.5% 37.5%,
      #e2bd76 37.5% 43%, transparent 43% 50%,
      #e2bd76 50% 55.5%, transparent 55.5% 62.5%,
      #e2bd76 62.5% 68%, transparent 68% 75%,
      #e2bd76 75% 80.5%, transparent 80.5% 87.5%,
      #e2bd76 87.5% 93%, transparent 93% 100%);
  animation: cs-gear 11s linear infinite;
}
.cs-emblem-gear i:last-child {
  inset: 22%;
  background: radial-gradient(circle, transparent 38%, rgba(226, 189, 118, .85) 39% 78%, transparent 79%);
  animation-direction: reverse;
  animation-duration: 7s;
}
@keyframes cs-gear { to { transform: rotate(360deg); } }

/* 2026 — two bodies on tilted orbits around a small sun */
.cs-emblem-orbit i {
  position: absolute;
  inset: 0;
  border: 1px solid rgba(143, 211, 255, .5);
  border-radius: 50%;
  animation: cs-gear 9s linear infinite;
}
.cs-emblem-orbit i:nth-child(2) { inset: 20%; animation-duration: 5.5s; animation-direction: reverse; }
.cs-emblem-orbit i::after {
  content: "";
  position: absolute;
  top: -3px; left: 50%;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: #8fd3ff;
  box-shadow: 0 0 12px 2px rgba(143, 211, 255, .8);
}
.cs-emblem-orbit i:nth-child(2)::after { background: #c3b0ff; box-shadow: 0 0 12px 2px rgba(167,139,250,.8); }
.cs-emblem-orbit b {
  position: absolute;
  inset: 41%;
  border-radius: 50%;
  background: radial-gradient(circle at 34% 30%, #fff, #e2bd76 45%, #b06a1c);
  box-shadow: 0 0 18px 4px rgba(226, 189, 118, .45);
}

/* the year glows on hover */
.cs-gate-year { transition: text-shadow .4s ease, letter-spacing .4s ease; }
.cs-gate-card:hover .cs-gate-year { letter-spacing: .02em; text-shadow: 0 0 26px currentColor; }

/* the click: one expanding ring from where the pointer landed, so the choice
   registers before the page changes under it */
.cs-ripple {
  position: absolute;
  z-index: 3;
  width: 12px; height: 12px;
  margin: -6px 0 0 -6px;
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(circle, rgba(255,255,255,.5), transparent 70%);
  animation: cs-ripple .62s ease-out forwards;
}
@keyframes cs-ripple {
  to { transform: scale(34); opacity: 0; }
}

/* the ticking line under the cards */
.cs-gate-small { font-variant-numeric: tabular-nums; }
.cs-gate-small .tick { color: rgba(143, 211, 255, .75); }

/* "Reduce motion" slows the door's ambient loops rather than stopping them;
   the letters, the tilt and the ripple are all responses to something the
   visitor did, and stay. `.calm` is set on <html> by chronoshell-stage.js. */
.calm #cs-warp::after { animation-duration: 26s; }
.calm .cs-card-ring { animation-duration: 20s; }
.calm .cs-emblem-gear i { animation-duration: 34s; }
.calm .cs-emblem-gear i:last-child { animation-duration: 22s; }
.calm .cs-emblem-orbit i { animation-duration: 28s; }
.calm .cs-emblem-orbit i:nth-child(2) { animation-duration: 17s; }
.calm .cs-gate-title { animation-duration: 20s; }

/* a touch screen has no pointer to follow, so the spotlight and the tilt are
   simply not part of the design there */
@media (hover: none) {
  .cs-card-glow { display: none; }
  .cs-gate-card { transform: none; }
}

/* ===========================================================================
   THE FRONT DOOR, THIRD PASS — the galaxy, and the things you can grab

   Two problems with the second pass, both visible on a wide screen:

   1. The title was invisible. `-webkit-background-clip: text` on the heading
      stopped clipping its letters the moment each letter became a transformed
      inline-block — a transform gives the child its own paint layer, outside
      the parent's clip, so every glyph rendered at `color: transparent`. The
      gradient is gone; the letters are solid now and the light travels across
      them as a staggered glow instead, which cannot break the same way.

   2. It did not feel like a gate at the crux of space and time. There is a
      painted galaxy behind it now (see #cs-sky in chronoshell-stage.js) and
      the cards can be picked up and thrown.
   ========================================================================= */

#cs-sky {
  position: absolute;
  inset: 0;
  /* A canvas is a replaced element: with `left:0; right:0; width:auto` the
     used width comes from its intrinsic size — the width attribute — not from
     the insets. Without these the resize observer fed the bitmap size back
     into the layout size and the canvas grew without bound (6825px on a
     624px window before this was caught). */
  width: 100%;
  height: 100%;
  z-index: 1;
  pointer-events: none;
}

/* the aurora sits under the galaxy rather than over it */
#cs-warp::before { z-index: 0; }
#cs-warp::after { z-index: 2; opacity: .32; }
.cs-gate { z-index: 5; }

/* --- title --------------------------------------------------------------
   Solid ink, and a wave of light that crosses it letter by letter. No
   background-clip anywhere near a transformed child. --- */
.cs-gate-title {
  background: none;
  color: #fff6e4;
  -webkit-text-fill-color: currentColor;
  animation: none;
  text-shadow: 0 0 42px rgba(143, 211, 255, .28);
}

.cs-gate-title .ltr {
  display: inline-block;
  opacity: 0;
  transform: translateY(.55em) rotateX(-70deg);
  transition: transform .35s cubic-bezier(.2, .8, .25, 1), color .35s ease;
  animation:
    cs-drop .62s cubic-bezier(.2, .8, .25, 1) forwards,
    cs-lit 5.4s ease-in-out infinite;
  /* cs-drop owns the first 0.62s; the glow wave starts after the letter has
     landed, hence the second delay being set inline alongside the first */
}
.cs-gate-title .ltr.sp { width: .34em; }

@keyframes cs-lit {
  0%, 82%, 100% { color: #fff6e4; text-shadow: none; }
  90% {
    color: #ffffff;
    text-shadow: 0 0 22px rgba(255, 230, 176, .9), 0 0 54px rgba(143, 211, 255, .55);
  }
}

/* every letter is its own little target */
.cs-gate-title .ltr:hover {
  transform: translateY(-7px) scale(1.06);
  color: #ffe6b0;
}

.calm .cs-gate-title .ltr { animation-duration: .62s, 16s; }

/* --- cards you can pick up ----------------------------------------------
   `--dx/--dy` are written by the drag handler; the tilt keeps its own vars,
   so a card being thrown and a card being pointed at compose instead of
   fighting. --- */
.cs-gate-card {
  transform:
    perspective(1000px)
    translate3d(var(--dx, 0px), var(--dy, 0px), 0)
    rotateX(var(--rx, 0deg))
    rotateY(var(--ry, 0deg))
    rotate(var(--spin, 0deg))
    translateY(var(--lift, 0px))
    scale(var(--sc, 1));
  touch-action: pan-y;          /* vertical scroll still belongs to the page */
  cursor: grab;
}
.cs-gate-card.dragging {
  cursor: grabbing;
  transition: none;             /* follow the finger exactly while held */
  z-index: 3;
  --sc: 1.03;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .4),
    inset 0 0 0 1px rgba(255, 255, 255, .14),
    0 40px 90px rgba(0, 0, 0, .6);
}
/* the throw home: slower and springier than the pointer tilt */
.cs-gate-card.settling {
  transition: transform .85s cubic-bezier(.18, 1.4, .36, 1), box-shadow .5s ease;
}

/* --- the ticking line ---------------------------------------------------- */
.cs-gate-small { min-height: 2.6em; }

/* ===========================================================================
   THE PHONE

   Everything above is written for a pointer. This is the same door for a
   thumb: one column, bigger targets, the emblem moved out of the corner so it
   stops colliding with the year, and type that does not shrink below 15px.
   ========================================================================= */
@media (max-width: 820px) {
  .cs-gate {
    width: min(30rem, 100%);
    padding-inline: 1.1rem;
  }

  .cs-gate-eyebrow { font-size: 10px; letter-spacing: .26em; }
  .cs-gate-title { font-size: clamp(28px, 8.4vw, 42px); margin-bottom: 1.3rem; }

  .cs-gate-cards { gap: .85rem; }

  .cs-gate-card {
    padding: 1.15rem 1.15rem 1.25rem;
    border-radius: 18px;
    /* the emblem used to sit top-right, on top of the year at this width */
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "year emblem"
      "name name"
      "note note"
      "cta  cta";
    align-items: center;
    gap: .3rem .6rem;
  }
  .cs-gate-card .cs-gate-year { grid-area: year; font-size: clamp(30px, 9vw, 40px); }
  .cs-gate-card b { grid-area: name; font-size: 1.02rem; }
  .cs-gate-card .cs-gate-note { grid-area: note; font-size: .9rem; line-height: 1.45; }
  .cs-gate-card .cs-gate-cta {
    grid-area: cta;
    justify-self: start;
    margin-top: .7rem;
    padding: .62rem 1.05rem;    /* a 44px-tall target, which is the point */
    font-size: .74rem;
  }

  .cs-emblem {
    grid-area: emblem;
    /* `relative`, not `static`: the gear and the orbit rings inside are
       `position: absolute; inset: 0`, so a static parent hands them the card
       as their containing block and the emblem fills the whole panel */
    position: relative;
    inset: auto;
    width: 2.5rem;
    opacity: .8;
  }

  .cs-gate-small { font-size: 10px; letter-spacing: .08em; line-height: 1.7; }
}

@media (max-width: 380px) {
  .cs-gate-title { font-size: 26px; }
  .cs-gate-note { font-size: .86rem; }
}

/* a short landscape phone: the door has to fit between two thumbs */
@media (max-height: 520px) and (orientation: landscape) {
  .cs-gate-title { font-size: 24px; margin-bottom: .7rem; }
  .cs-gate-note { display: none; }
  .cs-gate-cards { grid-template-columns: 1fr 1fr; }
}

/* ===========================================================================
   THE 1888 CARD — candlelight

   The 2026 card is glass and starlight and needs nothing. This one should feel
   like the door to somewhere with a library in it: warm parchment behind the
   glass, gilt corner brackets, embers drifting up off the panel, and a candle
   that never quite holds still.

   Still all CSS — seven spans and two pseudo-elements. It costs one composited
   layer for the embers and nothing else.
   ========================================================================= */
.cs-gate-card[data-go="past"] {
  background:
    radial-gradient(120% 90% at 18% 8%, rgba(255, 214, 148, .13), transparent 62%),
    radial-gradient(90% 70% at 92% 96%, rgba(150, 84, 30, .16), transparent 66%),
    linear-gradient(160deg, rgba(255, 238, 208, .11), rgba(120, 82, 44, .05) 46%, rgba(40, 26, 14, .06));
  animation: cs-candle 6.5s ease-in-out infinite;
}
.cs-gate-card[data-go="past"].dragging { animation: none; }

/* the flame is never steady, and never on a metronome either — the keyframes
   are deliberately uneven so it does not read as a pulse */
@keyframes cs-candle {
  0%, 100% { box-shadow: inset 0 1px 0 rgba(255, 236, 196, .3), inset 0 0 0 1px rgba(226, 189, 118, .10), 0 22px 50px rgba(0, 0, 0, .55), 0 0 34px -12px rgba(240, 173, 87, .45); }
  17%      { box-shadow: inset 0 1px 0 rgba(255, 236, 196, .42), inset 0 0 0 1px rgba(226, 189, 118, .16), 0 22px 50px rgba(0, 0, 0, .55), 0 0 52px -10px rgba(240, 173, 87, .7); }
  29%      { box-shadow: inset 0 1px 0 rgba(255, 236, 196, .28), inset 0 0 0 1px rgba(226, 189, 118, .09), 0 22px 50px rgba(0, 0, 0, .55), 0 0 30px -14px rgba(240, 173, 87, .4); }
  53%      { box-shadow: inset 0 1px 0 rgba(255, 236, 196, .48), inset 0 0 0 1px rgba(226, 189, 118, .2), 0 22px 50px rgba(0, 0, 0, .55), 0 0 62px -8px rgba(240, 173, 87, .8); }
  71%      { box-shadow: inset 0 1px 0 rgba(255, 236, 196, .3), inset 0 0 0 1px rgba(226, 189, 118, .11), 0 22px 50px rgba(0, 0, 0, .55), 0 0 36px -13px rgba(240, 173, 87, .5); }
}
.calm .cs-gate-card[data-go="past"] { animation-duration: 20s; }

/* --- gilt corner brackets ------------------------------------------------
   Two elements, four corners: each draws an L at opposite ends of the card. --- */
.cs-filigree {
  position: absolute;
  inset: 11px;
  z-index: 1;
  pointer-events: none;
}
.cs-filigree::before,
.cs-filigree::after {
  content: "";
  position: absolute;
  width: 30px;
  height: 30px;
  border: 1.4px solid rgba(226, 189, 118, .5);
  transition: border-color .5s ease, width .5s ease, height .5s ease;
}
.cs-filigree::before {
  top: 0; left: 0;
  border-right: 0; border-bottom: 0;
  border-top-left-radius: 11px;
}
.cs-filigree::after {
  bottom: 0; right: 0;
  border-left: 0; border-top: 0;
  border-bottom-right-radius: 11px;
}
.cs-filigree.alt::before {
  top: 0; left: auto; right: 0;
  border: 1.4px solid rgba(226, 189, 118, .5);
  border-left: 0; border-bottom: 0;
  border-radius: 0 11px 0 0;
}
.cs-filigree.alt::after {
  bottom: 0; right: auto; left: 0;
  border: 1.4px solid rgba(226, 189, 118, .5);
  border-right: 0; border-top: 0;
  border-radius: 0 0 0 11px;
}
.cs-gate-card[data-go="past"]:hover .cs-filigree::before,
.cs-gate-card[data-go="past"]:hover .cs-filigree::after {
  border-color: rgba(255, 224, 160, .95);
  width: 42px;
  height: 42px;
}

/* --- embers --------------------------------------------------------------
   Seven of them, each with its own column, delay, drift and duration, so the
   pattern never repeats visibly. --- */
.cs-embers {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  border-radius: inherit;
  pointer-events: none;
}
.cs-embers i {
  position: absolute;
  bottom: -8px;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: radial-gradient(circle, #ffe0a8, #f0a94a 55%, rgba(240, 169, 74, 0) 72%);
  box-shadow: 0 0 9px 2px rgba(240, 169, 74, .45);
  opacity: 0;
  animation: cs-ember 8s linear infinite;
}
.cs-embers i:nth-child(1) { left:  8%; --wx:  16px; animation-duration: 7.4s;  animation-delay: 0s; }
.cs-embers i:nth-child(2) { left: 21%; --wx: -14px; animation-duration: 9.1s;  animation-delay: 1.3s; }
.cs-embers i:nth-child(3) { left: 37%; --wx:  22px; animation-duration: 6.8s;  animation-delay: 2.9s; }
.cs-embers i:nth-child(4) { left: 52%; --wx: -19px; animation-duration: 10.2s; animation-delay: .7s; }
.cs-embers i:nth-child(5) { left: 68%; --wx:  12px; animation-duration: 8.3s;  animation-delay: 4.1s; }
.cs-embers i:nth-child(6) { left: 81%; --wx: -24px; animation-duration: 7.7s;  animation-delay: 3.2s; }
.cs-embers i:nth-child(7) { left: 93%; --wx:  10px; animation-duration: 9.6s;  animation-delay: 5.4s; }

@keyframes cs-ember {
  0%   { transform: translate3d(0, 0, 0) scale(.5); opacity: 0; }
  12%  { opacity: .95; }
  65%  { opacity: .6; }
  100% { transform: translate3d(var(--wx, 14px), -230px, 0) scale(1.1); opacity: 0; }
}
.calm .cs-embers i { animation-duration: 22s; }

/* the year, lit like gilt lettering on a spine */
.cs-gate-card[data-go="past"] .cs-gate-year {
  color: #f7e3bb;
  text-shadow: 0 1px 0 rgba(120, 74, 26, .5), 0 0 30px rgba(240, 173, 87, .35);
}

/* on a phone the embers have less height to travel, and seven of them in a
   short card reads as smoke rather than sparks */
@media (max-width: 820px) {
  .cs-embers i:nth-child(n + 5) { display: none; }
  @keyframes cs-ember {
    0%   { transform: translate3d(0, 0, 0) scale(.5); opacity: 0; }
    14%  { opacity: .9; }
    100% { transform: translate3d(var(--wx, 12px), -150px, 0) scale(1.05); opacity: 0; }
  }
}

/* ===========================================================================
   §14 THE SPACE-TIME LOOP

   The tunnel artifact is 500 KB of WebGL in front of an 8-12 MB video. On a
   cold cache or a slow line it can spend a long time unpacking, and until it
   has finished it paints its own opaque "Unpacking…" screen — which sat on
   z-index 2, over the copy, so the whole wait read as a broken page.

   So the run has three phases, carried on `data-phase` on `.cs-warp-run`:

     wait    the loop is painted over the artifact. However long this takes,
             something is visibly moving and the copy is readable.
     travel  the artifact has reported a year, so it is really running: the
             loop fades away and the real journey is revealed.
     ready   arrived.

   Everything here is transform/opacity on a single canvas plus one blurred
   glow, so it stays on the compositor and can run for minutes on a phone.
   ========================================================================= */

.cs-vortex {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  opacity: 0;
  transition: opacity .9s ease;
  pointer-events: none;
}

/* while the artifact unpacks, the loop and the copy sit ABOVE it (the frame
   is z-index 2) — the skip button stays above both at z-index 3 so the way
   out is reachable for the whole of the wait, however long the wait is. */
.cs-warp-run[data-phase="wait"] .cs-warp-tunnel { z-index: 2; }
.cs-warp-run[data-phase="wait"] .cs-warp-copy   { z-index: 2; }
.cs-warp-run[data-phase="wait"] .cs-vortex      { opacity: 1; }

/* the loop's own backdrop, so the artifact's loading screen never shows
   through it. Painted on the tunnel box, which is already inset: 0. */
.cs-warp-run[data-phase="wait"] .cs-warp-tunnel::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(60% 60% at 50% 50%, rgba(24, 16, 8, .55), rgba(6, 5, 9, .96) 72%),
    #06050a;
}

/* once it is genuinely travelling the artifact is the thing worth looking at */
.cs-warp-run[data-phase="travel"] .cs-vortex,
.cs-warp-run[data-phase="ready"]  .cs-vortex { opacity: 0; }

/* a slow halo behind the canvas so the middle of the loop glows rather than
   sitting flat. One element, one transform loop. */
.cs-warp-run[data-phase="wait"] .cs-warp-tunnel::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 46vmin;
  height: 46vmin;
  margin: -23vmin 0 0 -23vmin;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(226, 189, 118, .30), rgba(120, 190, 255, .12) 45%, transparent 70%);
  filter: blur(14px);
  animation: cs-loop-breathe 6.5s ease-in-out infinite;
  will-change: transform, opacity;
}

@keyframes cs-loop-breathe {
  0%, 100% { transform: scale(.92); opacity: .55; }
  50%      { transform: scale(1.12); opacity: .95; }
}

.calm .cs-warp-run[data-phase="wait"] .cs-warp-tunnel::before { animation-duration: 16s; }

/* the wait line under the bar — only present while unpacking */
.cs-warp-wait {
  display: block;
  margin-top: .7em;
  font-family: ui-monospace, monospace;
  font-size: clamp(9px, .8vw, 12px);
  letter-spacing: .12em;
  color: rgba(244, 226, 190, .38);
}

.cs-warp-run:not([data-phase="wait"]) .cs-warp-wait { visibility: hidden; }

/* the skip button must never be the thing that is hard to find during a long
   wait, so it is given a little more presence than it had. */
.cs-warp-skip { z-index: 5; }
