/* ============================================================
   DOUBLOON STUDIO · FILM PLAYER
   Owns .scene__stage and what is inside it, and the stage layout
   (.film-track, .film-stage, .film-beat) under html.film-on. Uses tokens only
   (css/tokens.css). Works with no script: each stage then shows its
   still as a plain image. js/film.js adds html.film-on when the film
   runs; every rule that changes layout is scoped under it.

   Inside each scene's .film sits one plain still, .film__still: the
   scene's resting still (its loop's first frame). It is what shows with
   no script, under reduced motion, with ?film=off, and on the stage
   until the first clip moves.

   While the film runs, js/film.js adds one screen to the stage,
   .film-screen, under every scene's words. It holds one video per clip
   that has been asked for. A clip comes up either at once (a cut, or a
   swap where the frames match) or by dissolving in over the clip
   below (.is-fade, --dissolve long, set from DISSOLVE in film.js).
   ============================================================ */

.film {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: var(--bg);
}

.film__still,
.film__still img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
}

/* Draft label. The data-standin attribute on the slot drives it, so
   removing that attribute when the real clip arrives removes it. */
.film[data-standin]::after {
  content: "Stand-in";
  position: absolute;
  z-index: 4;
  right: 0.75rem;
  bottom: 0.75rem;
  padding: 0.3rem 0.55rem;
  border: 1px solid var(--rule);
  background: var(--scrim);
  color: var(--text-muted);
  font-size: var(--size-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  pointer-events: none;
}

/* ---------- the stage (while the film runs) ---------- */

/* js/film.js adds html.film-on. Without it (scripts off, reduced
   motion, ?film=off) none of this applies and the scenes stack as plain
   sections, each with its still and its words.

   With it, the seven scenes sit on one stage, .film-stage, pinned with
   native sticky positioning inside one tall track, .film-track. The
   track is one beat per stop tall plus one screen (seven stops, set by
   js/film.js as --stops); scrolling it moves nothing on the stage, it
   only passes the stop markers (.film-beat). Passing halfway to the
   next marker starts the leg of film to the next stop. After the last
   stop the track ends and the stage scrolls away with the page.

   One gesture, one leg (owner-approved exception to "never take over
   scrolling", 2026-10-09 and 2026-10-10, for this stage only): while
   the film runs the page snaps to the markers, mandatory, and each
   marker stops a fling (scroll-snap-stop: always). While a leg plays
   js/film.js adds html.film-lock and the page does not scroll at all.
   The exit marker is one more snap area: it starts a screen past stop
   4, where the stage has just left, so leaving the film takes the same
   gesture as continuing, and js/film.js stretches it to the foot of
   the page. A snap area taller than the screen lets the page rest
   anywhere inside it, so below the film the page scrolls freely and
   nothing traps. The browser does all the snapping.

   --beat is the scroll distance from one stop to the next: a screen
   and a half. The snap carries the page from marker to marker, so the
   distance no longer sets how far a hand moves. It is long because
   Safari and Chrome count scroll-snap-stop from where the fingers lift
   (Chrome from its first momentum step): at 55 and 100 screen percent
   the drag of a hard trackpad fling crossed a marker before the lift
   and the fling went two scenes; from 130 on it held (2026-10-09).
   --last-hold is how far the last stop stays before the stage lets go: none,
   since the page snaps to the last stop and the exit point is a screen on. */
.film-track {
  --stops: 7;
  --screen: 1vh;
  --beat: calc(150 * var(--screen));
  --last-hold: 0px;
}

/* a custom property takes any value, so the svh line needs its own
   test; older browsers keep vh */
@supports (height: 1svh) {
  .film-track {
    --screen: 1svh;
  }
}

html.film-on .film-track {
  position: relative;
  height: calc(100vh + (var(--stops) - 1) * var(--beat) + var(--last-hold));
  height: calc(100lvh + (var(--stops) - 1) * var(--beat) + var(--last-hold));
}

.film-beat {
  position: absolute;
  left: 0;
  top: calc(var(--k, 0) * var(--beat));
  width: 1px;
  height: 1px;
  pointer-events: none;
  visibility: hidden;
}

html.film-on {
  scroll-snap-type: y mandatory;
  /* the lock below hides the scroll bar; the gutter keeps its room so
     the stage does not change width */
  scrollbar-gutter: stable;
}

/* a leg of film is playing: the page holds still until it ends */
html.film-lock {
  overflow: hidden;
}

html.film-lock .film-stage {
  touch-action: none;
}

html.film-on .film-beat {
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

/* the exit point, one screen past the last stop: the stage has just left and
   the page below it is at the top of the screen. js/film.js sets its
   height, down to the foot of the page. */
html.film-on .film-beat--exit {
  top: calc((var(--stops) - 1) * var(--beat) + 100 * var(--screen));
  scroll-snap-stop: normal;
}

/* The stage fills the largest screen the browser can show (lvh), so no
   edge of anything shows when a phone's toolbar slides away. The words
   on it keep to the smallest screen (svh), so a toolbar never covers
   them. Both units ignore the phone keyboard. */
html.film-on .film-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100lvh;
  overflow: hidden;
  overflow: clip;
  isolation: isolate;
  background: var(--bg);
}

/* Every scene sits on the same spot; only the current one is seen
   (that rule is in base.css). The words change at once; the pictures
   dissolve on the screen below them. */
html.film-on .film-stage > .scene {
  position: absolute;
  inset: 0;
  min-height: 0;
  border-top: 0;
  align-items: start;
  transition: none;
}

html.film-on .film-stage > .scene[data-current] {
  z-index: 1;
}

html.film-on .film-stage .scene__words {
  height: 100vh;
  height: 100svh;
}

/* Only the current scene and its two neighbours keep their picture
   rendered (so a cut is instant); the rest load nothing until near. */
html.film-on .film-stage > .scene:not([data-current]):not([data-near]) .scene__stage {
  display: none;
}

/* ---------- the screen (made by js/film.js) ---------- */

/* First child of the stage, so every scene paints over it; the scenes
   have no background of their own once the screen is live. */
.film-screen {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  background: var(--bg);
}

.film-screen__clip {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
  opacity: 0;
}

.film-screen__clip.is-up {
  opacity: 1;
}

.film-screen__clip.is-fade {
  transition: opacity var(--dissolve, 0.35s) linear;
}

/* Once a clip moves, the screen carries the film and the scenes' plain
   stills step aside (they load nothing more). */
html.film-on .film-stage.is-live > .scene > .scene__stage {
  display: none;
}

/* ---------- scenes 5 and 6 on a tall screen: the camera tilt ----------
   Owner decision 2026-10-13, 4:29 PM: "move the camera up so less sand
   is showing and create enough room for more writing". While the film
   rests on the sand loop (stops 5 and 6), js/film.js shows that loop's
   video lower down by --tilt of the stage's height (.is-tilt), gliding
   down as stop 5 arrives. The water it opens above is a gradient on the
   screen behind every clip (data-tilt on the stage), from deep water at
   the top to the color of the footage's own top water at the seam
   (tokens.css), and the feather (.film-screen__feather, just above the
   loop) softens the picture's top 70px into it, so no edge shows.
   (2026-10-14) Only the loop's own video moves, never the whole screen:
   moving the screen that holds every clip made Safari on iPhone paint
   scene 6's and scene 7's words under the picture on most frames. The
   loop never glides back on screen: the clip after it dissolves in over
   it, and it is put back once hidden. With the film off (scripts off,
   reduced motion, ?film=off) scene 5's still stands in the tilted
   place with no glide. Tall screens only: the ones that get the 1080 by
   1920 file. */
.film-screen__feather {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
}

@media (max-aspect-ratio: 1/1) {
  html.film-on .film-stage {
    --tilt: 30%;
    --tilt-glide: 1.2s cubic-bezier(0.45, 0, 0.55, 1);
  }

  html.film-on .film-stage[data-tilt] .film-screen,
  html.film-on .film-stage[data-tilt] {
    background:
      linear-gradient(to bottom, var(--tilt-sea-top) 0, var(--tilt-sea-mid) calc(var(--tilt) * 0.6), var(--tilt-sea-seam) var(--tilt)),
      var(--bg);
  }

  .film-screen__feather {
    background: linear-gradient(to bottom, var(--tilt-sea-seam), transparent 70px);
    transition: transform var(--tilt-glide), opacity var(--tilt-glide);
  }

  .film-screen__clip.is-tilt {
    transition: transform var(--tilt-glide);
  }

  .film-screen__clip.is-tilt.is-fade {
    transition: transform var(--tilt-glide), opacity var(--dissolve, 0.35s) linear;
  }

  .film-screen__clip.is-tilt,
  .film-screen__feather.is-tilt {
    transform: translateY(var(--tilt));
  }

  .film-screen__feather.is-tilt {
    opacity: 1;
  }

  html.film-on .film-stage #s5-under .scene__stage {
    transition: transform var(--tilt-glide);
  }

  html.film-on .film-stage #s5-under .scene__stage::after {
    content: "";
    position: absolute;
    z-index: 999999;
    inset: 0 0 auto 0;
    height: 70px;
    background: linear-gradient(to bottom, var(--tilt-sea-seam), transparent);
    opacity: 0;
    transition: opacity var(--tilt-glide);
    pointer-events: none;
  }

  html.film-on .film-stage[data-tilt] #s5-under .scene__stage {
    transform: translateY(var(--tilt));
  }

  html.film-on .film-stage[data-tilt] #s5-under .scene__stage::after {
    opacity: 1;
  }

  html:not(.film-on) #s5-under {
    --tilt: 30%;
    background:
      linear-gradient(to bottom, var(--tilt-sea-top) 0, var(--tilt-sea-mid) calc(var(--tilt) * 0.6), var(--tilt-sea-seam) var(--tilt)),
      var(--bg);
  }

  html:not(.film-on) #s5-under .scene__stage {
    transform: translateY(var(--tilt));
  }

  html:not(.film-on) #s5-under .scene__stage::after {
    content: "";
    position: absolute;
    z-index: 1;
    inset: 0 0 auto 0;
    height: 70px;
    background: linear-gradient(to bottom, var(--tilt-sea-seam), transparent);
    pointer-events: none;
  }
}

/* a short tall screen (a small phone with its toolbars showing) has less
   height to fit four steps in, so the glide runs to the top of its range */
@media (max-aspect-ratio: 1/1) and (max-height: 44rem) {
  html.film-on .film-stage,
  html:not(.film-on) #s5-under {
    --tilt: 36%;
  }
}

/* placed at once (the loop is about to show, or is hidden) */
html.film-on .film-stage[data-tilt-jump] .film-screen__clip,
html.film-on .film-stage[data-tilt-jump] .film-screen__feather,
html.film-on .film-stage[data-tilt-jump] #s5-under .scene__stage,
html.film-on .film-stage[data-tilt-jump] #s5-under .scene__stage::after {
  transition: none;
}

/* ---------- a touch screen: the film holds the screen (2026-10-13) ----------
   Owner decision, 8:30 PM: "yes, the film takes over swiping on
   phones". js/film.js adds html.film-touch on a touch screen. There the
   track is one screen tall with no markers and no snap, so the page's
   own scrolling never runs through the film. While the film runs it
   adds html.film-take: the page does not scroll at all (no momentum,
   no bounce) and the stage is fixed over the whole screen, so neither
   the page nor the toolbar can move it; js/film.js reads every swipe.
   When it lets go (Skip, a link into the page, a swipe on from the last stop)
   the stage sits at the top of the page, one screen tall, and the page
   scrolls as normal. At scroll 0 the fixed and the resting stage sit in
   the same place, so taking and letting go move nothing. */
html.film-on.film-touch {
  scroll-snap-type: none;
  scrollbar-gutter: auto;
}

html.film-on.film-touch .film-track {
  height: 100vh;
  height: 100lvh;
}

html.film-on.film-touch.film-take,
html.film-on.film-touch.film-take body {
  overflow: hidden;
  overscroll-behavior: none;
}

html.film-on.film-touch.film-take .film-stage {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1;
  touch-action: pinch-zoom;
}
