/* =============================================================================
   THE REMNANT — the segments canvas
   -----------------------------------------------------------------------------
   One persistent canvas through which pre-authored segments play back to back.
   The engine knows nothing about any particular segment; everything specific
   to a scene lives in a file under segments/.

   1. THE STAGE IS A FILM FRAME. It has a fixed aspect and scales as a unit.
      Every slot is a percent OF THE STAGE, so a composition is identical at
      any screen size. It fills the section the way the tent and the loom do;
      the backdrop fills whatever the frame does not.

   2. PICTURES ON THE LEFT, WORDS ON THE RIGHT. Image overlap is kept
      minimal and no line sits on a picture -- except the note, which is
      inscribed on one, like a location super. Every picture is feathered on
      every edge; where two do meet, the feather is what makes it a blend.
   ============================================================================= */

/* --- the section ---------------------------------------------------------- */
/* One screen tall (svh: the height you actually get on a phone with its
   toolbar showing; vh first as the fallback). The nav overlays its top like
   every other full-bleed section. It is an inline-size container so the
   stage can size itself against the section's width in cqw. */
.canvas{
  position:relative;
  --seam:#060606;
  /* narration's own face: a scribe's italic, not the dialogue face's.
     Loaded in index.html. */
  --scribe:"Cormorant Garamond","Cormorant",Georgia,serif;
  background:var(--canvas-base,#060606);
  overflow:hidden;               /* the backdrop's ambient layers overscan,
                                    and a wide screen's stage is trimmed here */
  isolation:isolate;
  height:100vh;height:calc(100 * var(--vu, 1svh));
  container-type:inline-size;container-name:canvas;
  /* the room below the nav, which the stage is centred in (--vu: the
     page's one viewport unit, site.css) */
  --area:calc(100 * var(--vu, 1vh) - var(--header-h));
  transition:background 1.6s ease;
}

/* --- the stage ------------------------------------------------------------ */
/* FILLS THE WIDTH. Its height is the width x 9/16, capped at 108% of the
   room below the nav:
     · 16:9 or narrower (tablets, 16:10 laptops): full width, the backdrop
       fills above and below;
     · wider than 16:9: full width while the frame overflows the room by up
       to 8%, split equally top and bottom (the section trims the bottom,
       the nav covers the top); beyond that the frame stops growing, fits to
       that height, and the backdrop fills the sides.
   Every slot sits in 10%-95% of the stage vertically (see slots.js), so a
   4% trim at either end only ever removes empty stage. */
.canvas__stage{
  position:absolute;left:50%;
  top:calc(var(--header-h) + var(--area) / 2);
  transform:translate(-50%,-50%);
  --stage-h:min(100cqw * 9 / 16, var(--area) * 1.08);
  height:var(--stage-h);
  width:calc(var(--stage-h) * 16 / 9);
  /* The stage is a size container, so type is sized in cqh -- a percentage
     of the STAGE's height -- and holds with the slots at every size. */
  container-type:size;
  container-name:stage;
}
@supports not (width:1cqw){
  .canvas__stage{--stage-h:min(100vw * 9 / 16, var(--area) * 1.08)}
}

/* The room is the whole section, not the frame. An earlier version inset it
   from the stage, which drew a visible horizontal edge where the backdrop
   stopped -- the frame is supposed to sit IN the room, not carry it. */
.canvas__backdrop{position:absolute;inset:0;pointer-events:none;z-index:0}
.canvas__layer{position:absolute;inset:0;z-index:2}

/* --- slots ---------------------------------------------------------------- */
/* Geometry comes from segments/slots.js and is written onto each element as
   inline left/top/width/height percentages. Nothing here hard-codes a
   position; this only says how a slot behaves once it has one. */
.slot{position:absolute;pointer-events:none;margin:0}
.slot--image{display:grid;place-items:start center;
  /* Pictures are the only layers promoted up front: three at most, and each
     is animated on every entrance and exit. Nothing else on the stage gets
     will-change -- every promoted layer costs a phone GPU memory. */
  will-change:opacity,transform}
.slot--text{display:grid}

/* --- images --------------------------------------------------------------- */
/* figure.slot > .spill + .shot > img. The signatures animate the FIGURE's
   opacity and transform only. The .shot carries the shape -- its mask is
   fixed and never animated -- and clips the picture, so nothing can tile
   or show past the shape. The spill is a sibling, so the mask never clips
   the light. */
.spill,.shot{grid-area:1/1;position:relative;width:100%;height:100%}
/* Down the slot, the picture starts at the TOP and the engine lets it down
   to its place with a relative `top` (a `whole` picture that doesn't fill
   the slot's height: centred on desktop, hung near the top on the phone,
   or wherever its cue's `align` says). */

/* PRESERVED, and the highest-leverage thing on the site: every still throws
   its own light into the room. It is the still itself, blurred past
   recognition and laid behind -- so the fire throws orange and the torchlit
   chamber amber without anyone picking a colour.
   PRE-BLURRED: the blur is baked into a tiny image
   (tools/make_canvas_assets.py, `<name>-spill.webp`), with its fade into the
   room baked into the alpha, so nothing is blurred live. The file carries
   a 30% margin round the picture, hence the inset.
   --spill-op is set by the engine and falls as more stills share the stage,
   so the light where they overlap can never over-brighten. */
.spill{
  z-index:0;pointer-events:none;
  background:center/100% 100% no-repeat;
  transform:scale(1.6);           /* = 1 + 2 x the baked 30% margin */
  opacity:var(--spill-op,.5);
  transition:opacity 1.2s ease;
}
/* the slow fallback, for a spill that has not been pre-blurred */
.spill--live{filter:blur(58px) saturate(1.7) brightness(1.15);transform:scale(1.32);background-size:cover}

.shot{
  z-index:1;overflow:hidden;
  /* PRESERVED: feathered on all edges, never a hard photo border.
     ONE MASK IMAGE, ONE ELEMENT. The engine gives each rectangular picture
     a single SVG mask with its feather baked in (a gradient across, masked
     by a gradient down), sized to the picture -- so nothing asks the
     browser to COMPOSITE mask layers (WebKit on iOS added two layers where
     it should have intersected them: the priest's "strips"), and there is
     only one masked surface per picture (two nested masked wrappers
     doubled the compositing cost and dropped desktop to ~56fps). The
     fallback below is used only until the engine sets the SVG. */
  -webkit-mask-image:linear-gradient(to right, transparent 0, #000 5%, #000 95%, transparent 100%);
  mask-image:linear-gradient(to right, transparent 0, #000 5%, #000 95%, transparent 100%);
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-size:100% 100%;mask-size:100% 100%;
}
.shot img{
  display:block;width:100%;height:100%;max-width:none;
  /* The .shot is always exactly the VISIBLE picture (the engine sizes it:
     the slot for `fill`, the picture's own box for `whole`), so the image
     always covers it; any zoom past that is the transform below. */
  object-fit:cover;
  object-position:var(--focal,50% 50%);
  /* zoom >= 1 crops IN about the focal point; the .shot clips it */
  transform:scale(var(--zoom,1));transform-origin:var(--focal,50% 50%);
}

/* C, the close-up, is an OVAL: a radial mask, which is right here because
   the percentage being a radius is exactly what an oval wants.
   `closest-side` is load-bearing: a radial gradient defaults to
   farthest-CORNER, which leaves the ramp ~40% opaque at the box edge and
   cuts the oval off square. ONE mask layer, so nothing depends on
   mask-composite.
   THE FADE is the outer quarter of the radius (75% -> 100%; it was the
   outer half, which ate the face), eased -- a smoothstep in four steps --
   so the solid face never meets the fade on a visible ring. */
.slot--oval .shot,.slot--round .shot{
  /* !important: the engine's inline rectangle mask must never win here */
  -webkit-mask-image:radial-gradient(ellipse closest-side at 50% 50%, #000 75%, rgba(0,0,0,.84) 81.25%, rgba(0,0,0,.5) 87.5%, rgba(0,0,0,.16) 93.75%, rgba(0,0,0,0) 100%) !important;
  mask-image:radial-gradient(ellipse closest-side at 50% 50%, #000 75%, rgba(0,0,0,.84) 81.25%, rgba(0,0,0,.5) 87.5%, rgba(0,0,0,.16) 93.75%, rgba(0,0,0,0) 100%) !important;
}

/* THE JOIN. In a held B+C moment the oval sits over B's left edge, and
   B's edge is straight: through the oval's fade it read as a vertical seam.
   The oval carries a SHADE -- a soft dark ellipse on the oval's own centre,
   a little wider than the oval and much taller, centred lower (x1.1 wide,
   x1.9 tall, centre 60% down): B's edge showed most BELOW the oval's
   widest point, where B's lower left is lit wall and floor, while beside
   the oval at mid-height is the shadow figure, which must stay clear --
   behind the oval's light spill -- that
   fades B out along a CURVE, from under the solid face to past the rim. So
   the join is soft on both sides: the oval's own fade over it, B's fade
   under it, and no straight line anywhere. No mask (it is a background),
   so one mask per picture still holds. It shows only while B is on the
   stage with the oval, on desktop (the handoff over A in Scene 103 is untouched), and
   fades in and out with it. */
.shade{
  display:none;grid-area:1/1;position:relative;width:100%;height:100%;
  z-index:0;pointer-events:none;
  background:radial-gradient(ellipse closest-side at 50% 60%,
    rgba(10,7,5,.92) 72%, rgba(10,7,5,.6) 82%, rgba(10,7,5,.3) 90%,
    rgba(10,7,5,.1) 96%, rgba(10,7,5,0) 100%);
  transform:scale(1.1,1.9);
  opacity:0;transition:opacity 1.2s ease;
}
.slot--oval .shade{display:block}
.canvas__layer:has(> .slot--B) .slot--oval .shade{opacity:1}
/* Desktop only. On the phone B sits BELOW the oval, full width, and the
   shade's reach fell across the shadow figure. */
.canvas[data-stage="phone"] .shade{display:none}

.slot--oval .spill,.slot--round .spill{
  -webkit-mask-image:radial-gradient(ellipse closest-side at 50% 50%, #000 30%, rgba(0,0,0,0) 100%);
  mask-image:radial-gradient(ellipse closest-side at 50% 50%, #000 30%, rgba(0,0,0,0) 100%);
}

/* --- text ----------------------------------------------------------------- */
/* ONE VOICE PER SLOT. The three read as three different kinds of text at a
   glance:
     note       the location, a slugline -- small spaced capitals in muted
                gold, like letters cut in stone. Sits ON its picture.
     narration  the record being written -- its own face, Cormorant Garamond
                italic, a scribe's hand rather than the dialogue face's
                italic, in a parchment tone. Arrives by pen.
     dialogue   the spoken voice -- upright Spectral, warm white, largest and
                steadiest.
   Sizes are cqh (percent of the stage's height), so type holds with the
   slots; the rem floors are the readable minimum and the ceilings are high
   enough never to bind on a real screen.

   LEGIBILITY IS A GLOW ON THE LETTERS, never a box. Nothing but the note
   sits on a picture, so the glow only has to hold the words off the
   backdrop and the pictures' light spill. */
.line{
  font-family:var(--display);color:var(--text);margin:0;
  text-wrap:pretty;line-height:1.45;text-align:left;
}
.line--dialogue{
  font-style:normal;font-weight:500;
  font-size:clamp(.95rem,3.05cqh,3rem);line-height:1.42;letter-spacing:.004em;
  color:#F3EADB;
  text-shadow:0 0 18px rgba(0,0,0,.78), 0 0 4px rgba(0,0,0,.55), 0 1px 2px rgba(0,0,0,.5);
}
.line--narration{
  font-family:var(--scribe);font-style:italic;font-weight:500;
  font-size:clamp(1rem,3.0cqh,3rem);line-height:1.32;letter-spacing:.003em;
  color:#E3D5B5;
  text-shadow:0 0 16px rgba(0,0,0,.8), 0 0 3px rgba(0,0,0,.55);
}
.line--note{
  font-family:var(--display);font-style:normal;font-weight:500;
  font-size:clamp(.62rem,1.45cqh,1.4rem);line-height:1.35;
  letter-spacing:.24em;text-transform:uppercase;
  color:#BFA56A;
  /* cut in stone: a hairline of shadow under each letter and a faint lit
     edge above, then a soft glow behind -- still only on the letters */
  text-shadow:0 1px 0 rgba(0,0,0,.75), 0 -1px 0 rgba(255,228,168,.10),
              0 0 6px rgba(0,0,0,.9), 0 0 16px rgba(0,0,0,.8), 0 0 32px rgba(0,0,0,.55);
}
.slot--T1{align-content:start}
.slot--T2{align-content:end}         /* narration sits down on the dialogue */
.slot--T3{align-content:end;justify-items:start}   /* bottom-left of its picture */

/* The note's legibility over a bright picture (the fire's sand): a soft
   dark patch darkening the picture just around the lettering -- a radial
   gradient behind the words' own box, fading to nothing before its edge,
   so there is no edge to see and it reaches only a little past the text.
   The letter glow stays on top of it. */
.line__ink{position:relative;display:inline-block;max-width:100%}
.line__ink::before{
  content:"";position:absolute;z-index:-1;pointer-events:none;
  inset:-.9em -1.6em;
  background:radial-gradient(closest-side, rgba(6,4,2,.62) 0%, rgba(6,4,2,.46) 45%, rgba(6,4,2,.16) 78%, rgba(6,4,2,0) 100%);
}

@supports not (container-type: size){
  .line--dialogue{font-size:clamp(1.05rem,1.7vw,1.9rem)}
  .line--narration{font-size:clamp(1.1rem,1.68vw,1.9rem)}
  .line--note{font-size:clamp(.68rem,.82vw,.9rem)}
}

/* --- pause ------------------------------------------------------------------
   Auto-advancing content that runs longer than five seconds needs a pause
   (WCAG 2.2.2), and a slow reader needs it more than the spec does. There is
   no visible button: a tap or click anywhere on the canvas pauses, and
   another resumes (engine.js tells a tap from a scroll or a drag).

   While paused, one word -- "Paused" -- sits faintly in the corner in the
   note's voice: muted gold spaced capitals with a soft glow, no box.
   Nothing shows while playing. */
.canvas__paused{
  position:absolute;z-index:6;right:var(--gutter);bottom:clamp(.75rem,2vh,1.5rem);
  margin:0;pointer-events:none;
  font-family:var(--display);font-weight:500;font-size:.66rem;
  letter-spacing:.24em;text-transform:uppercase;color:#BFA56A;
  text-shadow:0 1px 0 rgba(0,0,0,.75), 0 0 6px rgba(0,0,0,.9), 0 0 16px rgba(0,0,0,.8);
  opacity:0;transition:opacity .6s ease}
.canvas[data-paused="true"] .canvas__paused{opacity:.72}

/* The REAL control, for keyboard and screen readers: a button, labelled,
   invisible until it is focused with Tab -- then it appears where the word
   does, and Enter or Space toggles it. */
.canvas__pause{
  position:absolute;z-index:7;right:var(--gutter);bottom:clamp(.75rem,2vh,1.5rem);
  appearance:none;border:0;background:none;padding:.4rem .2rem;cursor:pointer;
  font-family:var(--display);font-size:.66rem;letter-spacing:.24em;text-transform:uppercase;
  color:#F2ECE0;
  /* visually hidden, but reachable and read */
  width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;
  -webkit-tap-highlight-color:transparent;
}
.canvas__pause:focus-visible{
  width:auto;height:auto;overflow:visible;clip:auto;clip-path:none;
  outline:1px solid var(--gold-soft);outline-offset:4px;
  text-shadow:0 0 10px rgba(0,0,0,.9)}
/* the word steps aside while the focused button stands in its place */
.canvas__pause:focus-visible ~ .canvas__paused{opacity:0}
/* the canvas is the control, so it says so to a pointer */
.canvas[data-pausable="true"] .canvas__stage{cursor:pointer}
/* (The gold outline round the stage when paused is preview-only -- it lives
   in segments/preview.js.) */

/* --- transcript ----------------------------------------------------------- */
/* The canvas itself is aria-hidden: text that rewrites itself on a timer is
   hostile to a screen reader. The same writing is published once, in order,
   as a static transcript instead. */
.canvas__transcript{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* =============================================================================
   BACKDROPS
   Each segment names one, and each carries its own motion, so the section is
   visibly alive the moment someone scrolls onto it. Adding a backdrop means
   adding a block here and naming it from a segment file -- the engine never
   needs to know.

   The backdrop element has ::before, ::after and four spare layers
   (.bd__l1-.bd__l4, in index.html) that a backdrop may style however it
   likes; unused ones stay empty and cost nothing.

   BUILT CHEAP. Every moving part animates transform or opacity only, on a
   layer that was painted once -- pre-made gradients and one small texture,
   no live lighting, no filters, no pixel warping. Everything stops when the
   section is off screen or the tab is hidden (data-awake, below), and with
   reduced motion every backdrop is still.
   ============================================================================= */
.bd__l{position:absolute;pointer-events:none}

/* --- Scene 103: the temple hall. CANDLELIGHT AND SHIFTING SHADOWS ----------
   Enclosed, oppressive. Stone piers in deep shadow; a few warm pools of
   flame light that flicker irregularly -- three layers on three unrelated
   cycles (3.7s, 5.3s, 6.1s), each with uneven steps, so the sum never
   settles into a rhythm -- and two soft shadow shapes on the walls that
   sway slowly as if the flames moved. */
.bd--temple-hall{
  background:
    linear-gradient(90deg,
      rgba(0,0,0,.42) 0%, transparent 9%, rgba(0,0,0,.22) 17%, transparent 27%,
      rgba(0,0,0,.30) 38%, transparent 49%, rgba(0,0,0,.18) 61%,
      transparent 72%, rgba(0,0,0,.34) 84%, rgba(0,0,0,.48) 100%),
    linear-gradient(175deg,#0b0908 0%,#171009 46%,#0a0807 100%);
}
.bd--temple-hall::after{         /* falloff into the corners (static) */
  content:"";position:absolute;inset:0;z-index:2;
  background:radial-gradient(ellipse 84% 70% at 44% 58%, transparent 38%, rgba(4,3,2,.82) 100%)}
/* the pools: each layer sized to its own glow, so no layer is bigger than
   it needs to be (a full-screen layer per glow would cost a phone ~12MB) */
.bd--temple-hall .bd__l1{left:-8%;top:34%;width:34%;height:62%;
  background:radial-gradient(closest-side, rgba(240,150,62,.40), rgba(214,120,44,.15) 55%, transparent);
  animation:flameA 3.7s linear infinite}
.bd--temple-hall .bd__l2{left:22%;top:70%;width:52%;height:44%;
  background:radial-gradient(closest-side, rgba(230,140,56,.36), rgba(200,110,40,.13) 55%, transparent);
  animation:flameB 5.3s linear infinite}
.bd--temple-hall .bd__l3{left:66%;top:74%;width:40%;height:42%;
  background:radial-gradient(closest-side, rgba(236,150,66,.30), rgba(210,118,42,.10) 55%, transparent);
  animation:flameC 6.1s linear infinite}
/* the shadows on the walls, swaying from their base as the flames move */
.bd--temple-hall::before{
  content:"";position:absolute;z-index:1;left:0;top:6%;width:16%;height:84%;
  background:radial-gradient(closest-side, rgba(0,0,0,.55), rgba(0,0,0,.22) 60%, transparent);
  transform-origin:50% 100%;
  animation:swayA 7.9s ease-in-out infinite}
.bd--temple-hall .bd__l4{right:0;top:4%;width:14%;height:80%;
  background:radial-gradient(closest-side, rgba(0,0,0,.5), rgba(0,0,0,.2) 60%, transparent);
  transform-origin:50% 100%;
  animation:swayB 10.3s ease-in-out infinite}
@keyframes flameA{0%{opacity:0.65}6%{opacity:0.89}9%{opacity:0.52}17%{opacity:0.94}21%{opacity:0.74}30%{opacity:1.00}
  36%{opacity:0.56}41%{opacity:0.84}52%{opacity:0.68}56%{opacity:0.95}64%{opacity:0.53}70%{opacity:0.79}
  79%{opacity:0.66}84%{opacity:1.00}92%{opacity:0.60}100%{opacity:0.65}}
@keyframes flameB{0%{opacity:0.84}11%{opacity:0.58}15%{opacity:0.92}27%{opacity:0.71}33%{opacity:0.50}39%{opacity:0.87}
  48%{opacity:1.00}53%{opacity:0.63}61%{opacity:0.81}68%{opacity:0.55}77%{opacity:0.95}85%{opacity:0.68}
  91%{opacity:0.89}100%{opacity:0.84}}
@keyframes flameC{0%{opacity:0.71}8%{opacity:0.95}19%{opacity:0.61}24%{opacity:0.84}35%{opacity:0.52}44%{opacity:0.90}
  50%{opacity:0.73}58%{opacity:1.00}69%{opacity:0.58}75%{opacity:0.82}88%{opacity:0.65}100%{opacity:0.71}}
@keyframes swayA{0%{transform:rotate(-1.4deg) translateX(-2%)}37%{transform:rotate(1.1deg) translateX(1.5%)}
  58%{transform:rotate(-.4deg) translateX(-.5%)}100%{transform:rotate(-1.4deg) translateX(-2%)}}
@keyframes swayB{0%{transform:rotate(1deg) translateX(1.5%)}44%{transform:rotate(-1.3deg) translateX(-2%)}
  71%{transform:rotate(.5deg) translateX(.6%)}100%{transform:rotate(1deg) translateX(1.5%)}}
/* The same flame touches the light each still throws into the room --
   a few percent either way, on a flame's cycle, so the pictures feel lit by
   the same fire and never visibly pulse. */
.canvas[data-backdrop="temple-hall"] .spill{animation:spillFlicker 3.7s linear infinite}
@keyframes spillFlicker{0%{opacity:calc(var(--spill-op,.5) * .93)}17%{opacity:calc(var(--spill-op,.5) * 1)}
  36%{opacity:calc(var(--spill-op,.5) * .9)}56%{opacity:calc(var(--spill-op,.5) * .99)}
  64%{opacity:calc(var(--spill-op,.5) * .91)}84%{opacity:calc(var(--spill-op,.5) * 1)}
  100%{opacity:calc(var(--spill-op,.5) * .93)}}

/* --- Scene 105: the valley. SAND IN A LIGHT WIND --------------------------
   Outdoors at harsh midday: amber glare, heat off the ground, and fine
   streams of sand drifting sideways low across it. The streams are one
   small tileable texture (assets/sand-streaks.webp, 10KB) slid by
   transform at two speeds; each layer is exactly one tile wider than the
   section and moves exactly one tile, so the loop is seamless. Now and then
   a gust lifts a thin veil of sand across. And a few dozen single grains,
   drawn once as box-shadows on one element per layer and then only moved. */
.bd--midday-glare{
  background:
    radial-gradient(ellipse 52% 46% at 58% 40%, rgba(255,186,88,.30), transparent 68%),
    radial-gradient(ellipse 90% 60% at 50% 104%, rgba(190,88,30,.24), transparent 70%),
    linear-gradient(192deg,#1a1109 0%,#2a1a0b 42%,#0d0906 100%);
}
.bd--midday-glare::before{       /* sand stream, slow, broad */
  content:"";position:absolute;left:-1024px;bottom:0;width:calc(100% + 1024px);height:24%;
  background:url(/assets/sand-streaks.webp) 0 100% / 1024px 100% repeat-x;
  -webkit-mask-image:linear-gradient(to top,#000 35%,transparent);mask-image:linear-gradient(to top,#000 35%,transparent);
  opacity:.8;
  animation:sandSlow 38s linear infinite}
.bd--midday-glare .bd__l1{       /* sand stream, faster, lower, finer */
  left:-700px;bottom:0;width:calc(100% + 700px);height:13%;
  background:url(/assets/sand-streaks.webp) 0 100% / 700px 100% repeat-x;
  -webkit-mask-image:linear-gradient(to top,#000 40%,transparent);mask-image:linear-gradient(to top,#000 40%,transparent);
  opacity:.65;
  animation:sandFast 15s linear infinite}
.bd--midday-glare .bd__l2{       /* the occasional gust: a veil lifted across */
  left:0;bottom:4%;width:55%;height:36%;opacity:0;
  background:
    url(/assets/sand-streaks.webp) 0 100% / 520px 60% repeat-x,
    radial-gradient(ellipse 50% 55% at 50% 72%, rgba(228,190,132,.16), transparent 72%);
  -webkit-mask-image:radial-gradient(closest-side,#000 40%,transparent);mask-image:radial-gradient(closest-side,#000 40%,transparent);
  animation:gust 23s ease-in-out infinite}
.bd--midday-glare .bd__l3{       /* single grains, far */
  left:0;top:66%;width:1px;height:1px;border-radius:50%;
  box-shadow:
    -1219px 114px 0 0.5px rgba(236,204,150,0.49),
    -599px 14px 0 0.3px rgba(236,204,150,0.57),
    -1185px 49px 0 0.9px rgba(236,204,150,0.45),
    -262px 100px 0 0.7px rgba(236,204,150,0.35),
    -584px 182px 0 0.6px rgba(236,204,150,0.54),
    -526px 13px 0 0.8px rgba(236,204,150,0.49),
    -1118px 7px 0 0.8px rgba(236,204,150,0.45),
    -450px 185px 0 0.7px rgba(236,204,150,0.59),
    -968px 168px 0 0.6px rgba(236,204,150,0.60),
    -194px 20px 0 0.4px rgba(236,204,150,0.37),
    -55px 92px 0 0.7px rgba(236,204,150,0.40),
    -788px 81px 0 0.5px rgba(236,204,150,0.49),
    -665px 190px 0 0.7px rgba(236,204,150,0.60),
    -230px 208px 0 0.7px rgba(236,204,150,0.35),
    -223px 203px 0 0.8px rgba(236,204,150,0.48),
    -458px 44px 0 0.8px rgba(236,204,150,0.48),
    381px 114px 0 0.5px rgba(236,204,150,0.49),
    1001px 14px 0 0.3px rgba(236,204,150,0.57),
    415px 49px 0 0.9px rgba(236,204,150,0.45),
    1338px 100px 0 0.7px rgba(236,204,150,0.35),
    1016px 182px 0 0.6px rgba(236,204,150,0.54),
    1074px 13px 0 0.8px rgba(236,204,150,0.49),
    482px 7px 0 0.8px rgba(236,204,150,0.45),
    1150px 185px 0 0.7px rgba(236,204,150,0.59),
    632px 168px 0 0.6px rgba(236,204,150,0.60),
    1406px 20px 0 0.4px rgba(236,204,150,0.37),
    1545px 92px 0 0.7px rgba(236,204,150,0.40),
    812px 81px 0 0.5px rgba(236,204,150,0.49),
    935px 190px 0 0.7px rgba(236,204,150,0.60),
    1370px 208px 0 0.7px rgba(236,204,150,0.35),
    1377px 203px 0 0.8px rgba(236,204,150,0.48),
    1142px 44px 0 0.8px rgba(236,204,150,0.48),
    1981px 114px 0 0.5px rgba(236,204,150,0.49),
    2601px 14px 0 0.3px rgba(236,204,150,0.57),
    2015px 49px 0 0.9px rgba(236,204,150,0.45),
    2938px 100px 0 0.7px rgba(236,204,150,0.35),
    2616px 182px 0 0.6px rgba(236,204,150,0.54),
    2674px 13px 0 0.8px rgba(236,204,150,0.49),
    2082px 7px 0 0.8px rgba(236,204,150,0.45),
    2750px 185px 0 0.7px rgba(236,204,150,0.59),
    2232px 168px 0 0.6px rgba(236,204,150,0.60),
    3006px 20px 0 0.4px rgba(236,204,150,0.37),
    3145px 92px 0 0.7px rgba(236,204,150,0.40),
    2412px 81px 0 0.5px rgba(236,204,150,0.49),
    2535px 190px 0 0.7px rgba(236,204,150,0.60),
    2970px 208px 0 0.7px rgba(236,204,150,0.35),
    2977px 203px 0 0.8px rgba(236,204,150,0.48),
    2742px 44px 0 0.8px rgba(236,204,150,0.48);
  animation:grainsFar 24s linear infinite}
.bd--midday-glare .bd__l4{       /* single grains, near (not on phones) */
  left:0;top:74%;width:1px;height:1px;border-radius:50%;
  box-shadow:
    -712px 84px 0 1.1px rgba(236,204,150,0.51),
    -640px 88px 0 0.6px rgba(236,204,150,0.53),
    -481px 119px 0 0.6px rgba(236,204,150,0.46),
    -1182px 121px 0 1.0px rgba(236,204,150,0.36),
    -23px 145px 0 1.0px rgba(236,204,150,0.57),
    -1095px 2px 0 0.9px rgba(236,204,150,0.37),
    -1053px 36px 0 0.5px rgba(236,204,150,0.51),
    -727px 126px 0 0.9px rgba(236,204,150,0.57),
    -650px 99px 0 0.8px rgba(236,204,150,0.45),
    -3px 149px 0 1.1px rgba(236,204,150,0.60),
    -890px 34px 0 0.7px rgba(236,204,150,0.37),
    -304px 60px 0 1.1px rgba(236,204,150,0.49),
    588px 84px 0 1.1px rgba(236,204,150,0.51),
    660px 88px 0 0.6px rgba(236,204,150,0.53),
    819px 119px 0 0.6px rgba(236,204,150,0.46),
    118px 121px 0 1.0px rgba(236,204,150,0.36),
    1277px 145px 0 1.0px rgba(236,204,150,0.57),
    205px 2px 0 0.9px rgba(236,204,150,0.37),
    247px 36px 0 0.5px rgba(236,204,150,0.51),
    573px 126px 0 0.9px rgba(236,204,150,0.57),
    650px 99px 0 0.8px rgba(236,204,150,0.45),
    1297px 149px 0 1.1px rgba(236,204,150,0.60),
    410px 34px 0 0.7px rgba(236,204,150,0.37),
    996px 60px 0 1.1px rgba(236,204,150,0.49),
    1888px 84px 0 1.1px rgba(236,204,150,0.51),
    1960px 88px 0 0.6px rgba(236,204,150,0.53),
    2119px 119px 0 0.6px rgba(236,204,150,0.46),
    1418px 121px 0 1.0px rgba(236,204,150,0.36),
    2577px 145px 0 1.0px rgba(236,204,150,0.57),
    1505px 2px 0 0.9px rgba(236,204,150,0.37),
    1547px 36px 0 0.5px rgba(236,204,150,0.51),
    1873px 126px 0 0.9px rgba(236,204,150,0.57),
    1950px 99px 0 0.8px rgba(236,204,150,0.45),
    2597px 149px 0 1.1px rgba(236,204,150,0.60),
    1710px 34px 0 0.7px rgba(236,204,150,0.37),
    2296px 60px 0 1.1px rgba(236,204,150,0.49);
  animation:grainsNear 11s linear infinite}
.bd--midday-glare::after{        /* heat off the ground: a transform, not a warp */
  content:"";position:absolute;inset:0;
  background:linear-gradient(to top, rgba(255,178,92,.16), transparent 34%);
  transform-origin:50% 100%;
  animation:shimmer 11s ease-in-out infinite alternate}
@keyframes sandSlow{from{transform:translateX(0)}to{transform:translateX(1024px)}}
@keyframes sandFast{from{transform:translateX(0)}to{transform:translateX(700px)}}
@keyframes gust{
  0%,58%{opacity:0;transform:translate(-100%,0)}
  66%{opacity:.9;transform:translate(-20%,-2%)}
  80%{opacity:.55;transform:translate(95%,-14%)}
  88%,100%{opacity:0;transform:translate(190%,-22%)}}
@keyframes grainsFar{0%{transform:translate(0,0)}50%{transform:translate(800px,-6px)}100%{transform:translate(1600px,0)}}
@keyframes grainsNear{0%{transform:translate(0,0)}50%{transform:translate(650px,-10px)}100%{transform:translate(1300px,0)}}
@keyframes shimmer{from{transform:scaleY(1)}to{transform:scaleY(1.05)}}

/* A neutral room, for a segment that has not chosen one yet, and the fallback
   when a segment names a backdrop that does not exist. */
.bd--plain{background:linear-gradient(180deg,#0a0908 0%,#0f0d0b 52%,#070605 100%)}

/* ASLEEP: off screen, or the tab hidden. Every backdrop animation (and the
   candle flicker on the spill) stops where it is. The engine sets
   data-awake. */
.canvas:not([data-awake="true"]) .canvas__backdrop,
.canvas:not([data-awake="true"]) .canvas__backdrop::before,
.canvas:not([data-awake="true"]) .canvas__backdrop::after,
.canvas:not([data-awake="true"]) .bd__l,
.canvas:not([data-awake="true"]) .spill{animation-play-state:paused}

/* Every backdrop republishes the section's seam colour and base, so the
   transitions into the tent above and the loom below keep working while the
   backdrop changes underneath them. */
.canvas[data-backdrop="temple-hall"] {--seam:#0b0908; --canvas-base:#0a0807}
.canvas[data-backdrop="midday-glare"]{--seam:#140d07; --canvas-base:#120c06}
.canvas[data-backdrop="plain"]       {--seam:#080706; --canvas-base:#080706}
/* the backdrop element itself cross-fades on a segment handoff */
.canvas__backdrop{opacity:1;transition:opacity var(--bd-fade,1.4s) ease}
.canvas__backdrop[data-swapping="true"]{opacity:0}

/* The section's seams (the graded hand-over to the tent above and the loom
   below) sit BELOW the stage: now that the frame fills the section, a seam
   painted over it darkened the pictures and the top line. They blend the
   backdrop only. */
.canvas > .seam{z-index:1}

/* --- phone stage ---------------------------------------------------------- */
/* Breakpoint must match RC.phoneBreakpoint in segments/slots.js.

   ONE SCREEN, NO SCROLL. The section is exactly one screen tall in svh --
   the height with the browser's toolbars SHOWN, which in Chrome on iPhone is
   shorter than Safari's because Chrome's bottom bar is taller. svh is
   stable: the toolbars sliding in and out never change it, so nothing is
   ever relaid while they move. Below the nav comes a clear gap (no text may
   sit under the nav), then the stage, then a short row where "Paused"
   appears.

   HEIGHT. On a phone the section is --canvas-h tall: the SMALLEST visible
   height, measured by the engine when the page loads (toolbars showing)
   and locked -- it never changes while toolbars slide. It cannot simply be
   100svh: Chrome on iPhone never implemented the WebKit hooks that tell a
   page how big its toolbars are, so there svh is the LARGE height, and a
   100svh section was a whole toolbars-hidden screen tall -- the dialogue
   fell off the bottom. 100svh remains only as the fallback before the
   engine runs (and is right in Safari).

   No CSS scroll snap (browsers re-snap whenever content under a snapped
   position changes size, which yanked the page back to the canvas on every
   segment loop), and no glide either: the page never moves itself onto the
   section. Scrolling is always the visitor's own. */
@media (max-width:860px){
  .canvas{
    height:var(--canvas-h, calc(100 * var(--vu, 1svh)));
    padding:calc(var(--header-h) + 10px) 0 0;
    display:flex;flex-direction:column;align-items:center;
    container-type:normal;
  }
  .canvas__stage{
    position:relative;left:auto;top:auto;transform:none;
    flex:1 1 auto;min-height:0;
    width:calc(100% - 32px);   /* 16px gutters */
    height:auto;
  }
  /* a short last row, below the dialogue, where the "Paused" word (and the
     keyboard button, when focused) sit -- never over a line of text */
  .canvas{padding-bottom:26px}
  .canvas__paused,.canvas__pause{right:16px;bottom:6px}
}

/* Phone TYPE. Keyed on data-stage (set by the engine) rather than the media
   query, so preview's forced phone stage gets it too. Still cqh, with
   floors that are the readable minimum on a phone. Cormorant runs small for
   its size, so narration's floor is higher than dialogue's and it still
   reads as the smaller voice. The bands are MEASURED to these lines on the
   device (RC.fitPhoneBands), so they always fit exactly. */
.canvas[data-stage="phone"] .line--dialogue{font-size:clamp(.95rem,2.35cqh,1.15rem);line-height:1.4}
.canvas[data-stage="phone"] .line--narration{font-size:clamp(1.02rem,2.45cqh,1.2rem);line-height:1.28}
.canvas[data-stage="phone"] .line--note{font-size:clamp(.6rem,1.4cqh,.72rem);letter-spacing:.2em}
/* narration hangs from the TOP of its band: if a line ever ran long it
   would grow down toward the picture, never up under the nav */
.canvas[data-stage="phone"] .slot--T2{align-content:start}
.canvas[data-stage="phone"] .slot--T1{align-content:start}

/* the spill drawn a little closer to its picture on a small screen */
.canvas[data-stage="phone"] .spill{transform:scale(1.45)}
/* phone backdrops: fewer grains, no heat shimmer */
.canvas[data-stage="phone"] .bd--midday-glare .bd__l4,
.canvas[data-stage="phone"] .bd--midday-glare::after{display:none}
/* (the phone's wider feather -- 11% / 10%, at least 18px -- is set by the
   engine with the rest of the mask; see FEATHER in engine.js) */

/* --- reduced motion ------------------------------------------------------- */
/* The engine composes the first segment's final state and stops. Nothing
   here should animate, including the backdrops' own ambient layers. */
@media (prefers-reduced-motion:reduce){
  .canvas *,.canvas *::before,.canvas *::after{
    animation:none !important;transition-duration:.01ms !important}
}
