/* scenes part 1: title card, chapter I (road), chapter II (threshold), drip wipe.
   Every pinned scene exposes --p (0..1 scrub progress, set by js/main.js).
   Scene artwork is painted Ottoman-miniature imagery in assets/; the scroll
   engine pans and zooms the paintings like a camera over a scroll. */

.scene { position: relative; }

.scene__stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
}

/* Soft edges at the top and bottom of every pinned stage, so a scene fades
   into its neighbour instead of meeting it at a hard seam. */
.scene__stage::before,
.scene__stage::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 13vh;
  z-index: 9;
  pointer-events: none;
}

.scene__stage::before {
  top: 0;
  background: linear-gradient(to bottom, var(--edge, rgb(3 1 9 / 62%)), transparent);
}

.scene__stage::after {
  bottom: 0;
  background: linear-gradient(to top, var(--edge, rgb(3 1 9 / 62%)), transparent);
}

/* light chapters fade to parchment instead of night */
#s-walk .scene__stage { --edge: rgb(242 232 213 / 55%); }

.scene-art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* keep the scrubbed layers on their own compositor layers */
.scene-art,
.road-art,
.th-guardian,
.walk-panel,
.eyes-wrap { will-change: transform; }

/* =============================== TITLE CARD =============================== */

#s-title { height: calc(100vh + 55vh); background: var(--ink); }

#s-title .scene__stage { display: grid; place-items: center; }

.title-sky {
  transform: scale(calc(1.06 + var(--p, 0) * 0.08)) translateY(calc(var(--p, 0) * -3%));
  transform-origin: 50% 40%;
}

.title-stack {
  position: relative;
  z-index: 3;
  text-align: center;
  color: var(--paper);
  transform: translateY(calc(var(--p, 0) * -14vh));
  text-shadow: 0 2px 30px rgb(10 7 5 / 75%), 0 0 8px rgb(10 7 5 / 55%);
}

.title-stack__eyebrow {
  font-family: var(--font-script);
  font-size: clamp(1.3rem, 1rem + 1.6vw, 2.4rem);
  color: var(--ochre);
  transform: rotate(-3deg);
  margin-bottom: 0.4em;
}

.title-stack h1 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(3.2rem, 1.4rem + 9.5vw, 11rem);
  line-height: 0.92;
  letter-spacing: 0.01em;
  text-transform: uppercase;
}

.title-stack h1 span { display: block; }

.title-stack h1 .t-studios {
  font-size: 0.32em;
  letter-spacing: 0.55em;
  margin-top: 0.55em;
  font-family: var(--font-display-sc);
}

.title-stack__tag {
  margin-top: 1.6em;
  font-family: var(--font-display-sc);
  font-size: clamp(0.7rem, 0.64rem + 0.3vw, 0.85rem);
  letter-spacing: 0.4em;
  opacity: 0.85;
}

/* The seal: a vermilion ink stamp carrying the site's own display L.
   One roughen pass over the whole thing pits the square's edges and the
   letter together, so they read as a single press onto paper. */
.scroll-seal {
  position: absolute;
  z-index: 4;
  bottom: 5vh;
  left: 50%;
  translate: -50% 0;
  width: clamp(58px, 6.8vw, 86px);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  text-decoration: none;
  border-radius: 7px;
  background: var(--vermilion);
  filter: url(#roughen) drop-shadow(0 8px 20px rgb(0 0 0 / 55%));
  animation: seal-bob 2.6s var(--ease-brush) infinite;
  opacity: calc(1 - var(--p, 0) * 2.5);
}

/* gold rule set inside the stamp, roughened along with everything else */
.scroll-seal::before {
  content: "";
  position: absolute;
  inset: 11%;
  border: 2px solid var(--gold);
  border-radius: 3px;
  opacity: 0.9;
}

/* Centred on the glyph's ink, not its line box: measured cap height sits
   0.045em above the box centre, so the letter is nudged back down. */
.seal-mark {
  position: relative;
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 4.9vw, 3.85rem);
  line-height: 1;
  color: var(--paper);
  translate: 0 0.045em;
}

@keyframes seal-bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(10px); }
}

/* ============================ CHAPTER I — ROAD ============================ */

#s-walk { height: calc(100vh + 170vh); background: var(--paper); }

/* the painting is wider than the frame; scroll pans across it */
.road-art {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 136%;
  max-width: none;
  object-fit: cover;
  object-position: left center;
  transform: translateX(calc(var(--p, 0) * -26.5%));
}

/* comic panels: framed close-up crops of the paintings */
.walk-panel { position: absolute; z-index: 5; }

.walk-panel .crop {
  display: block;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

/* panel L: close-up of the lantern-bearer */
.walk-panel .crop img {
  width: 300%;
  max-width: none;
  transform: translate(-50%, -32%);
}

.walk-panel--l {
  top: 12%;
  left: calc(var(--frame-gap) * 2);
  width: clamp(150px, 19vw, 270px);
  aspect-ratio: 4 / 3.1;
  rotate: -1.6deg;
  --seg: clamp(0, calc((var(--p, 0) - 0.10) / 0.14), 1);
  opacity: var(--seg);
  transform: translateY(calc((1 - var(--seg)) * 6vh));
}

.walk-panel--r {
  bottom: 13%;
  right: calc(var(--frame-gap) * 2);
  width: clamp(160px, 21vw, 300px);
  aspect-ratio: 4 / 2.9;
  rotate: 1.8deg;
  --seg: clamp(0, calc((var(--p, 0) - 0.42) / 0.14), 1);
  opacity: var(--seg);
  transform: translateY(calc((1 - var(--seg)) * 6vh));
}

/* panel R: the domed sanctuary on the hill */
.walk-panel--r .crop img {
  width: 340%;
  transform: translate(-72%, -12%);
}

.walk-cap--a {
  position: absolute;
  z-index: 5;
  top: 16%;
  right: calc(var(--frame-gap) * 2.4);
  rotate: 0.8deg;
  --seg: clamp(0, calc((var(--p, 0) - 0.24) / 0.14), 1);
  opacity: var(--seg);
  transform: translateY(calc((1 - var(--seg)) * 4vh));
}

.walk-cap--b {
  position: absolute;
  z-index: 5;
  bottom: 17%;
  left: calc(var(--frame-gap) * 2.6);
  rotate: -1deg;
  --seg: clamp(0, calc((var(--p, 0) - 0.56) / 0.14), 1);
  opacity: var(--seg);
  transform: translateY(calc((1 - var(--seg)) * 4vh));
}

/* closing ink splat that swallows the scene into black */
.walk-out {
  position: absolute;
  z-index: 8;
  inset: -30%;
  display: grid;
  place-items: center;
  pointer-events: none;
  --seg: clamp(0, calc((var(--p, 0) - 0.58) / 0.42), 1);
}

/* squared growth so the splat creeps in, then rushes — a readable wipe
   rather than a shutter that slams closed in the last few pixels */
.walk-out svg {
  width: 60%;
  color: var(--ink);
  transform: scale(calc(var(--seg) * var(--seg) * 4.8)) rotate(calc(var(--seg) * 20deg));
  /* Reaches full opacity almost immediately. The <use> clones eight separate
     draw ops, so any value below 1 forces a saveLayer every frame — and the old
     ramp stayed translucent until seg 0.74, i.e. through the frames where this
     thing is largest. At seg 0.167 the scale is still 0.13, a speck: the wipe is
     carried by the growth, never by the fade. */
  opacity: min(1, calc(var(--seg) * 6));
}

/* ========================= CHAPTER II — THRESHOLD ========================= */

#s-threshold { height: calc(100vh + 230vh); background: var(--ink); }

#s-threshold .scene__stage {
  /* slow pull-back from the muqarnas detail to the whole gate */
  --pb: clamp(0, calc((var(--p, 0) - 0.12) / 0.5), 1);
}

.gate-art {
  transform: scale(calc(1.32 - var(--pb, 0) * 0.32));
  transform-origin: 46% 30%;
}

/* the guardian rises from below at the climax */
.th-guardian {
  --seg: clamp(0, calc((var(--p, 0) - 0.62) / 0.22), 1);
  position: absolute;
  z-index: 3;
  bottom: 0;
  left: 50%;
  height: min(88vh, 120vw);
  translate: -50% 0;
  transform: translateY(calc((1 - var(--seg)) * 78%));
  opacity: min(1, calc(var(--seg) * 4));
  filter: drop-shadow(0 0 44px rgb(10 7 5 / 65%));
}

.th-guardian img { height: 100%; width: auto; max-width: none; }

@media (max-width: 1100px) { .th-guardian { filter: none; } }

/* manga speed-line bars sliding from the sides at the climax */
.th-bars { position: absolute; inset: 0; z-index: 4; pointer-events: none; }

.th-bars i {
  --seg: clamp(0, calc((var(--p, 0) - 0.72) / 0.22), 1);
  position: absolute;
  height: 13%;
  width: 46%;
  background:
    repeating-linear-gradient(to bottom,
      var(--ink) 0 3px, transparent 3px 9px, var(--ink) 9px 11px, transparent 11px 22px),
    var(--paper);
  border: 3px solid var(--ink);
}

.th-bars i:nth-child(1) { top: 12%; left: 0; transform: translateX(calc((var(--seg) - 1) * 115%)); }
.th-bars i:nth-child(2) { bottom: 14%; right: 0; transform: translateX(calc((1 - var(--seg)) * 115%)); }

.th-cap--a {
  position: absolute;
  z-index: 6;
  top: 14%;
  left: calc(var(--frame-gap) * 2.4);
  rotate: -1deg;
  --seg: clamp(0, calc((var(--p, 0) - 0.06) / 0.12), 1);
  --out: clamp(0, calc((var(--p, 0) - 0.4) / 0.1), 1);
  opacity: calc(var(--seg) - var(--out));
  transform: translateY(calc((1 - var(--seg)) * 4vh));
}

.th-cap--b {
  position: absolute;
  z-index: 6;
  bottom: 12%;
  right: calc(var(--frame-gap) * 2.4);
  rotate: 1.2deg;
  --seg: clamp(0, calc((var(--p, 0) - 0.5) / 0.12), 1);
  opacity: var(--seg);
  transform: translateY(calc((1 - var(--seg)) * 4vh));
}

.th-title {
  position: absolute;
  z-index: 5;
  left: 50%;
  bottom: 8%;
  translate: -50% 0;
  text-align: center;
  color: var(--paper);
  font-family: var(--font-display);
  text-transform: uppercase;
  font-size: var(--text-h-chapter);
  line-height: 1;
  white-space: nowrap;
  text-shadow: 0 2px 26px rgb(10 7 5 / 80%);
  --seg: clamp(0, calc((var(--p, 0) - 0.88) / 0.12), 1);
  opacity: var(--seg);
  letter-spacing: calc(0.3em - var(--seg) * 0.22em);
}

/* ============================== DRIP WIPE ================================ */

#s-drip { height: calc(100vh + 85vh); background: var(--ink); }

.drip-curtain {
  position: absolute;
  inset: 0;
  color: var(--vermilion);
}

.drip-curtain svg {
  width: 100%;
  height: 116%;
  transform: translateY(calc((var(--p, 0) - 1) * 100%));
  /* Pure translation, so the raster scale never changes — promoting it means one
     raster and then compositor offsets, instead of repainting a full-viewport
     fifteen-path curtain on every frame of an 85vh transition. */
  will-change: transform;
}

.drip-eye {
  position: absolute;
  z-index: 3;
  bottom: 8%;
  left: 50%;
  translate: -50% 0;
  width: clamp(220px, 34vw, 460px);
  aspect-ratio: 460 / 200;
  border: var(--panel-border) solid var(--ink);
  background: var(--paper);
  box-shadow: 6px 7px 0 rgb(23 18 16 / 85%);
  rotate: -1deg;
  overflow: hidden;
  --seg: clamp(0, calc((var(--p, 0) - 0.55) / 0.2), 1);
  opacity: var(--seg);
  transform: translateY(calc((1 - var(--seg)) * 5vh));
}

.drip-eye img,
.drip-eye video {
  width: 175%;
  max-width: none;
  transform: translate(-19%, -26%);
}

@media (max-width: 860px) {
  .walk-panel--l { top: 9%; width: 34vw; }
  .walk-panel--r { width: 37vw; }
  .walk-cap--a { top: auto; bottom: 40%; right: var(--frame-gap); }
  .walk-cap--b { bottom: 6%; left: var(--frame-gap); }
  .caption-box { max-width: 26ch; }
  .road-art { width: 240%; transform: translateX(calc(var(--p, 0) * -58%)); }
  .gate-art { transform: scale(calc(1.5 - var(--pb, 0) * 0.5)); }
  .th-cap--a { left: var(--frame-gap); }
  .th-cap--b { right: var(--frame-gap); bottom: 20%; }
}

@media (max-width: 860px) {
  /* keep the seal's shadow, drop the turbulence filter — see base.css */
  .scroll-seal { filter: drop-shadow(0 8px 20px rgb(0 0 0 / 55%)); }
}

/* The splat's ink texture. It used to sit as filter="url(#roughen-strong)" on a
   <g> inside the <symbol>, where no media query could reach it — the element
   scales to 4.8x on the way out of Chapter I, so a four-octave feTurbulence was
   being recomputed over a full screen every frame on phones.

   Applied to <use> rather than the host <svg>: <use> still sits in SVG user
   space, so displacement stays 14 viewBox units the way the attribute had it.
   On the host it would be 14 CSS pixels and the texture would wash out as the
   element scales. */
.walk-out svg use,
.burst-splash use { filter: url(#roughen-strong); }

@media (max-width: 860px) {
  .walk-out svg use,
  .burst-splash use { filter: none; }

  /* Scaling *up* moves the ideal raster scale every frame, so the texture is
     regenerated continuously — and past seg 0.9 that texture is the whole
     viewport, rasterised on the main thread during the fastest flick on the
     page. Bake the final size into layout instead and let the transform only
     ever scale down to it: one raster, then pure compositor matrix work.

     288% of the box equals the old 60% x 4.8 — both are percentages of the same
     parent, so the wipe lands identically at every width. max-width:none is
     required: base.css caps every svg at 100% and would clip this to a speck. */
  .walk-out svg {
    width: 288%;
    max-width: none;
    transform: scale(calc(var(--seg) * var(--seg))) rotate(calc(var(--seg) * 20deg));
    will-change: transform;
  }
}
