/* --------------------------------------------------------------- the film ---
   Le grand départ — the film behind all eighteen finds in the lounge. Three
   separable things live here and it is worth knowing which is which:

     1. THE PROJECTOR (.film and everything under it). A stage of cross-fading
        scenes, a caption plate, a transport bar with the route as its progress
        rail, and one card at a time over the top — the act titles, the forks,
        the questions, the end titles. Used in two places — the premiere and the
        tenth tile — and identically in both: always the whole viewport, always the
        same entrance. See the note on `.film` below for why.

     2. THE SCENERY'S MOTION (the `fx-*` block). Thirty-one scenes of SVG are
        drawn by js/film/art.js, which never writes a transform or an animation —
        it writes CLASS NAMES. Clouds drift, balloons float, stars twinkle,
        candles gutter, flags sway, snow falls. Keeping all of it here is what
        lets the reduced-motion block at the bottom of this file switch off
        every moving thing in the film in nine selectors instead of thirty-one
        scenes' worth of special cases.

     3. THE PREMIERE'S PAGE SWEEP (`body.filming` and `.map-card.filmscreen`).
        The header, the pass, the lounge, both panels and the footer leave in
        five different directions, and the flight map goes fixed and fills the
        screen. The travel is a FLIP driven from js/film/takeover.js; what is
        here is the destination and what each piece does on its way out.

   PALETTE. The film is lit, not themed: it is dark in both colour schemes,
   because a projected picture is dark and the scenes carry their own light. The
   only token it takes from the page is `--accent`, for focus rings. `--hue` is
   set per scene by the projector and is what tints the rail, the card rules and
   the stamp marks, so the chrome drifts from Mexican orange to French indigo
   along with the pictures.
   ------------------------------------------------------------------------- */

/* ---------------------------------------------------------- the projector -- */
.film {
  --hue: #F0843F;
  --film-ink: #F2F6FA;
  --film-dim: #A9B6C6;
  --film-bg: #05080F;
  --bar-h: calc(clamp(44px, 7vh, 56px) + env(safe-area-inset-bottom));

  /* ALWAYS THE WHOLE VIEWPORT. There used to be a 16:10 variant for when the
     film was opened from its tile rather than earned, and it was a mistake: the
     same twenty scenes in a quarter of the screen read as a preview of the
     premiere rather than as the film. Both paths go through the same
     presentation now — see js/film/takeover.js. */
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: var(--film-bg);
  color: var(--film-ink);
  isolation: isolate;
  user-select: none;
  -webkit-user-select: none;
}

/* ---- the stage ---------------------------------------------------------- */
.film-stage { position: absolute; inset: 0; z-index: 0; }

.fscene {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 900ms ease;
}
.fscene.in  { opacity: 1; }
.fscene.out { opacity: 0; }

.fscene-svg {
  display: block;
  width: 100%;
  height: 100%;
  transform-origin: 50% 50%;
}
/* A slow push in on every scene. Fourteen seconds is longer than any scene
   runs, on purpose — the move must never arrive, or it reads as a zoom. */
.fscene.in .fscene-svg { animation: film-kb 15s ease-out both; }
@keyframes film-kb {
  from { transform: scale(1.005); }
  to   { transform: scale(1.07) translateY(-.8%); }
}
/* the forks alternate direction, so two scenes running back to back do not
   both drift the same way */
.fscene:nth-child(even).in .fscene-svg { animation-name: film-kb-alt; }
@keyframes film-kb-alt {
  from { transform: scale(1.06) translateX(-1%); }
  to   { transform: scale(1.005) translateX(.4%); }
}

/* ---- the captions ------------------------------------------------------- */
.film-cap {
  position: absolute;
  z-index: 2;
  left: 0; right: 0;
  bottom: var(--bar-h);
  display: flex;
  /* a column now: the subtitle, and under it the way on — see .film-next */
  flex-direction: column;
  align-items: center;
  gap: clamp(8px, 1.6vh, 14px);
  padding: 0 clamp(14px, 4.4vw, 54px) clamp(10px, 2.2vh, 22px);
}
.film-cap p {
  margin: 0;
  max-width: 44ch;
  text-align: center;
  font-size: clamp(13.5px, 2.4vw, 21px);
  line-height: 1.44;
  letter-spacing: .002em;
  color: var(--film-ink);
  /* two shadows: a tight one for legibility on light sky, a wide one to lift the
     line off a busy horizon. A subtitle has to work over every scene. */
  text-shadow: 0 1px 2px rgba(0, 0, 0, .95), 0 3px 18px rgba(0, 0, 0, .8);
  opacity: 0;
  transform: translateY(9px);
  transition: opacity 420ms ease, transform 420ms cubic-bezier(.3, 0, .2, 1);
}
.film-cap[data-on="1"] p { opacity: 1; transform: none; }

/* the way on. A dialog holds until this is pressed; it fades in once the line
   has had time to be read (js/film/engine.js decides when). Disabled — and
   transparent — the rest of the time, so it cannot be tabbed to or tapped
   before the line has landed. */
.film-next {
  padding: 8px 22px;
  min-height: 38px;
  border: 1px solid color-mix(in srgb, var(--hue) 55%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--hue) 16%, rgba(4, 7, 15, .55));
  color: var(--film-ink);
  font-size: 13px;
  font-weight: 650;
  letter-spacing: .04em;
  cursor: pointer;
  opacity: 0;
  transform: translateY(6px);
  pointer-events: none;
  transition: opacity 340ms ease, transform 340ms ease, background 160ms ease, border-color 160ms ease;
}
.film-next::after { content: " ▸"; }
.film-cap[data-next="1"] .film-next { opacity: 1; transform: none; pointer-events: auto; }
.film-next:hover {
  background: color-mix(in srgb, var(--hue) 30%, rgba(4, 7, 15, .5));
  border-color: var(--hue);
}
.film-next:active { transform: scale(.96); }

/* ---- the transport ------------------------------------------------------ */
.film-bar {
  position: absolute;
  z-index: 3;
  left: 0; right: 0; bottom: 0;
  height: var(--bar-h);
  display: flex;
  align-items: center;
  gap: clamp(6px, 1.4vw, 12px);
  padding: 0 clamp(8px, 2vw, 18px);
  background: linear-gradient(to top, rgba(4, 7, 15, .9), rgba(4, 7, 15, .55) 62%, transparent);
}

.film-btn {
  flex: 0 0 auto;
  width: 34px; height: 34px;
  display: grid; place-items: center;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, .18);
  border-radius: 50%;
  background: rgba(255, 255, 255, .08);
  color: var(--film-ink);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  transition: background 160ms ease, border-color 160ms ease, transform 120ms ease;
}
.film-btn:hover  { background: rgba(255, 255, 255, .16); border-color: rgba(255, 255, 255, .3); }
.film-btn:active { transform: scale(.92); }
.film-btn.quit:hover { background: rgba(226, 112, 90, .26); border-color: rgba(226, 112, 90, .5); }

.film-tally {
  flex: 0 0 auto;
  font-family: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
  font-variant-numeric: tabular-nums;
  font-size: clamp(10.5px, 1.6vw, 12.5px);
  letter-spacing: .08em;
  color: var(--hue);
  white-space: nowrap;
}
.film-tally.hit { animation: film-hit 620ms ease-out; }
@keyframes film-hit {
  0%   { transform: scale(1); }
  28%  { transform: scale(1.42); filter: brightness(1.7); }
  100% { transform: scale(1); }
}

/* the route, as the progress bar. It is the same line the map draws, and the
   aircraft on it is the same silhouette — the film is the flight. */
.film-rail { flex: 1 1 auto; min-width: 40px; height: 26px; }
.film-rail svg { display: block; width: 100%; height: 100%; overflow: visible; }
.fr-line  { stroke: rgba(255, 255, 255, .26); }
.fr-flown { stroke: var(--hue); transition: stroke-dashoffset 900ms cubic-bezier(.4, 0, .2, 1), stroke 900ms ease; }
.fr-end   { fill: rgba(255, 255, 255, .5); }
.fr-plane { fill: var(--film-ink); transition: transform 900ms cubic-bezier(.4, 0, .2, 1); }

/* paused: the picture desaturates a little, so the state is visible without a
   badge sitting over the scene */
.film[data-held="1"] .film-stage { filter: saturate(.55) brightness(.82); transition: filter 260ms ease; }

/* ---- the cards ---------------------------------------------------------- */
.film-over {
  position: absolute;
  z-index: 4;
  inset: 0;
  display: grid;
  place-items: center;
  padding: clamp(10px, 3vw, 34px);
  background: rgba(4, 7, 15, .8);
  opacity: 0;
  pointer-events: none;
  transition: opacity 380ms ease;
}
@supports (backdrop-filter: blur(3px)) {
  .film-over { background: rgba(4, 7, 15, .66); backdrop-filter: blur(4px) saturate(.8); }
}
.film-over.in { opacity: 1; pointer-events: auto; }

.film-card {
  width: min(100%, 640px);
  max-height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  text-align: center;
  transform: translateY(14px) scale(.985);
  transition: transform 460ms cubic-bezier(.2, .9, .2, 1);
}
.film-over.in .film-card { transform: none; }

.fc-kicker {
  margin: 0 0 10px;
  font-family: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
  font-size: clamp(9.5px, 1.5vw, 11px);
  font-weight: 700;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--hue);
}
.fc-title {
  margin: 0 0 12px;
  font-family: Georgia, "Iowan Old Style", "Times New Roman", serif;
  font-weight: 400;
  font-size: clamp(26px, 6.4vw, 54px);
  line-height: 1.05;
  letter-spacing: -.015em;
}
.fc-sub, .fc-ask {
  margin: 0;
  font-size: clamp(14px, 2.5vw, 20px);
  line-height: 1.45;
  color: var(--film-ink);
}
.fc-sub { color: var(--film-dim); font-style: italic; }
.fc-dots {
  margin: 20px 0 0;
  font-size: 13px;
  letter-spacing: .5em;
  color: var(--hue);
  opacity: .8;
}

/* ---- an act card ------------------------------------------------------- */
.is-act .fc-title { text-transform: none; }
.is-act::after {
  content: "";
  display: block;
  width: 54px; height: 2px;
  margin: 18px auto 0;
  background: var(--hue);
  opacity: .7;
}

/* ---- a fork ------------------------------------------------------------ */
.fc-picks {
  display: grid;
  gap: 10px;
  margin: 22px 0 0;
}
@media (min-width: 620px) {
  /* three places sit side by side where there is room; two story choices never
     do — a binary read as a row looks like a dialog, and it is a decision */
  .k-place .fc-picks { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.fc-pick {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 14px 16px;
  min-height: 56px;
  text-align: left;
  border: 1px solid rgba(255, 255, 255, .2);
  border-radius: 12px;
  background: rgba(255, 255, 255, .06);
  cursor: pointer;
  transition: background 180ms ease, border-color 180ms ease, transform 160ms ease;
}
.k-place .fc-pick { text-align: center; align-items: center; }
.fc-pick:hover { background: rgba(255, 255, 255, .13); border-color: var(--hue); }
.fc-pick:active { transform: scale(.985); }
.fc-pick.took {
  border-color: var(--hue);
  background: color-mix(in srgb, var(--hue) 26%, transparent);
  animation: film-took 420ms ease-out;
}
@keyframes film-took {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.03); }
  100% { transform: scale(1); }
}
.fp-l { font-size: clamp(15px, 2.3vw, 18px); font-weight: 650; }
.fp-b { font-size: clamp(11.5px, 1.8vw, 13px); color: var(--film-dim); line-height: 1.35; }
/* a scene this viewer has never taken. The whole point of thirty-one scenes and
   a twenty-scene run is that the second viewing can be aimed. */
.fp-new {
  position: absolute;
  top: 7px; right: 9px;
  font-size: 10px;
  color: var(--hue);
}

/* ---- a question -------------------------------------------------------- */
.fc-opts { display: grid; gap: 8px; margin: 20px 0 0; }
.fc-opt {
  padding: 13px 16px;
  min-height: 48px;
  text-align: left;
  font-size: clamp(14px, 2.1vw, 16px);
  border: 1px solid rgba(255, 255, 255, .2);
  border-radius: 11px;
  background: rgba(255, 255, 255, .06);
  cursor: pointer;
  transition: background 160ms ease, border-color 160ms ease, opacity 220ms ease;
}
.fc-opt:hover:not(:disabled) { background: rgba(255, 255, 255, .14); border-color: rgba(255, 255, 255, .38); }
.fc-opt:disabled { cursor: default; opacity: .5; }
.fc-opt.good {
  opacity: 1;
  border-color: #4FBF8B;
  background: rgba(79, 191, 139, .2);
  animation: film-good 520ms ease-out;
}
.fc-opt.bad {
  opacity: 1;
  border-color: #E2705A;
  background: rgba(226, 112, 90, .18);
  animation: film-bad 420ms ease-out;
}
@keyframes film-good {
  0% { transform: scale(1); } 35% { transform: scale(1.02); } 100% { transform: scale(1); }
}
@keyframes film-bad {
  0%, 100% { transform: translateX(0); }
  22% { transform: translateX(-6px); }
  58% { transform: translateX(5px); }
  80% { transform: translateX(-2px); }
}

.fc-verdict {
  margin: 18px 0 6px;
  font-family: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: #E2705A;
}
.fc-verdict[data-ok="1"] { color: #4FBF8B; }
/* the line the gate exists for */
.fc-fact {
  margin: 0 auto;
  max-width: 46ch;
  font-size: clamp(13px, 2vw, 15.5px);
  line-height: 1.5;
  color: var(--film-dim);
}

.fc-on {
  margin: 20px auto 0;
  padding: 12px 26px;
  min-height: 46px;
  display: block;
  border: 0;
  border-radius: 999px;
  background: var(--hue);
  color: #0B1018;
  font-size: 14.5px;
  font-weight: 700;
  letter-spacing: .02em;
  cursor: pointer;
  transition: filter 160ms ease, transform 140ms ease;
}
.fc-on:hover  { filter: brightness(1.12); }
.fc-on:active { transform: scale(.96); }
.fc-on.ghost {
  background: transparent;
  border: 1px solid rgba(255, 255, 255, .28);
  color: var(--film-ink);
  font-weight: 600;
}
.fc-on.ghost:hover { background: rgba(255, 255, 255, .1); }

/* ---- the end titles ---------------------------------------------------- */
/* THE DENSEST OF THE FOUR CARDS, and it has to be: a title, three lines, up to
   three lines of coda, twelve stamp slots, a score, a rank and two buttons — and
   on a phone held sideways that is under 400 px of height. It scrolls if it has
   to —
   `overflow-y: auto` on .film-card — but a card the viewer has to scroll to find
   the buttons on is a card that has failed, so everything below is a size down
   from the other three and the vertical rhythm is tight on purpose. */
.is-end .fc-title { font-size: clamp(22px, 4.8vw, 40px); margin-bottom: 12px; }
.fc-line {
  margin: 0 auto 6px;
  max-width: 52ch;
  font-size: clamp(12.5px, 1.8vw, 15px);
  line-height: 1.45;
  color: var(--film-ink);
}
.fc-coda {
  margin: 10px auto 0;
  max-width: 52ch;
  font-size: clamp(11.5px, 1.6vw, 13.5px);
  line-height: 1.45;
  font-style: italic;
  color: var(--film-dim);
}

/* the passport page: twelve slots, one per question asked */
.fc-page {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 5px;
  margin: 15px 0 10px;
}
.fc-slot {
  width: 23px; height: 28px;
  display: grid; place-items: center;
  border: 1px dashed rgba(255, 255, 255, .22);
  border-radius: 5px;
  font-size: 13px;
  font-style: normal;
  color: transparent;
}
.fc-slot.got {
  border-style: solid;
  border-color: var(--hue);
  background: color-mix(in srgb, var(--hue) 20%, transparent);
  color: var(--hue);
}

.fc-score {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: 6px 14px;
  margin: 0;
  font-family: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
  font-variant-numeric: tabular-nums;
  font-size: clamp(14px, 2.2vw, 18px);
}
.fc-rank {
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size: clamp(11px, 1.7vw, 13px);
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--film-dim);
}
.fc-rank.star { color: #F0C24A; }
.fc-new { font-size: 11px; letter-spacing: .1em; color: #4FBF8B; }

.fc-more {
  margin: 11px auto 0;
  max-width: 42ch;
  font-size: clamp(11.5px, 1.8vw, 13px);
  line-height: 1.5;
  color: var(--film-dim);
}
.fc-row { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; }
.fc-row .fc-on { margin-top: 14px; padding: 10px 22px; min-height: 42px; }

/* ------------------------------------------------------- the premiere ---- */
/* The full-viewport layer the film plays in during the takeover. */
.filmhost {
  position: fixed;
  z-index: 94;
  inset: 0;
  display: grid;
  background: #04070E;
  opacity: 0;
  transition: opacity 520ms ease;
}
.filmhost.on { opacity: 1; }
.film-bar { padding-bottom: env(safe-area-inset-bottom); }

/* ---- a phone held upright --------------------------------------------- */
/* The scenes are 1000 x 560 and they letterbox into whatever frame they are
   given (see the note on `canvas()` in js/film/art.js). In landscape that costs a
   couple of bars at the sides and nobody notices. In PORTRAIT, centring a 16:9
   picture in an 844px-tall viewport leaves a 218px band of picture between two
   313px bands of nothing.

   So portrait does not centre it. The stage is cut down to exactly the height the
   picture needs at full width, pinned to the top; the caption goes directly
   underneath it, in space that was black; and the transport stays at the foot.
   That is the layout every video on a phone has, and it uses the whole screen
   instead of a fifth of it.

   `56vw` is 560/1000 of the width — the scene's own aspect, written as a viewport
   unit, so the stage is exactly the height the picture needs and needs no
   measuring. */
@media (orientation: portrait) {
  /* One measurement, named once and used twice. `--pic-h` is the scene's own
     aspect written as a viewport unit — 560/1000 of the width — so the stage is
     exactly the height the picture needs and there are no bars inside it at all.
     `--pic-top` centres the picture-and-subtitle block in the space above the
     transport bar.

     THE PICTURE'S POSITION DOES NOT DEPEND ON THE CAPTION. `16vh` is reserved for
     the subtitle whether this beat is three words or two lines; a long line grows
     downward into black. Laying the two out as a centred flex column instead
     would have been shorter to write and would have hopped the picture up the
     screen every time a caption ran to a second line. */
  .film {
    --pic-h: 56vw;
    --cap-h: 16vh;
    --pic-top: calc((100% - var(--bar-h) - var(--pic-h) - var(--cap-h)) / 2);
  }
  .film-stage {
    top: var(--pic-top);
    bottom: auto;
    height: var(--pic-h);
  }
  .film-cap {
    top: calc(var(--pic-top) + var(--pic-h) + 2.4vh);
    bottom: auto;
    justify-content: flex-start;
  }
  /* with room to spare, the subtitle can be a size up and hold more of a line */
  .film-cap p {
    font-size: clamp(15px, 4.4vw, 22px);
    max-width: 34ch;
    /* it sits on the film's own black now, not over a picture — the wide shadow
       was there to lift it off a busy horizon and has nothing to do here */
    text-shadow: 0 1px 2px rgba(0, 0, 0, .9);
  }
}
}

/* ---- the room clears --------------------------------------------------- */
/* THE PAGE COMES APART. Forty-odd pieces, forty-odd trajectories, ONE keyframe:
   js/film/sweep.js measures where each element is, works out a vector from the
   middle of the viewport through it, and writes the result on the element as
   --fx / --fy / --fr / --fd. The rule below is the only animation involved.

   Generating a keyframe per element from JavaScript would have been the obvious
   alternative and it is worse twice over: the animation ends up defined in two
   places, and there is nothing static left for the reduced-motion block at the
   foot of this file to switch off.

   `both` on the fill matters — a flung piece has to STAY off screen for the ten
   minutes the film is running, not snap back when its animation ends. */
body.filming { overflow: hidden; }
body.filming > * { pointer-events: none; }
/* …except the projector itself. `#film` is ALSO a direct child of body, so the
   blanket rule above was eating every click on the transport bar — pause, skip
   and quit were dead for as long as the film ran, and only the cards survived
   because `.film-over.in` re-enables its own pointer-events. The flung page
   pieces stay inert; the film must not. */
body.filming > .filmhost { pointer-events: auto; }

.flung {
  animation: fling 760ms cubic-bezier(.36, 0, .66, -0.4) var(--fd, 0ms) both;
  will-change: transform, opacity;
}
@keyframes fling {
  0%   { transform: none; opacity: 1; }
  /* a beat of wind-up in the opposite direction, so it reads as thrown rather
     than as deleted — 8% of 760ms is 60ms, which is about one blink */
  8%   { transform: translate(calc(var(--fx, 0px) * -.035), calc(var(--fy, 0px) * -.035))
                    rotate(calc(var(--fr, 0deg) * -.06)); opacity: 1; }
  100% { transform: translate(var(--fx, 0px), var(--fy, 0px)) rotate(var(--fr, 0deg)) scale(.72);
         opacity: 0; }
}

/* and back, in reverse order. Overshoot on the way in — things that fly back
   into place and stop dead look magnetic; things that overshoot look thrown. */
.landing {
  animation: landing 720ms cubic-bezier(.18, .9, .28, 1.06) var(--fd, 0ms) both;
}
@keyframes landing {
  0%   { transform: translate(var(--fx, 0px), var(--fy, 0px)) rotate(var(--fr, 0deg)) scale(.72);
         opacity: 0; }
  60%  { opacity: 1; }
  100% { transform: none; opacity: 1; }
}

/* the beat before it all goes: the page braces. Small, fast, and on the whole
   body so every piece shakes in agreement — which is what makes the scatter a
   second later read as one event and not forty. */
body.shudder { animation: shudder 400ms cubic-bezier(.36, .07, .19, .97) both; }
@keyframes shudder {
  0%, 100% { transform: none; }
  12%  { transform: translate(-5px, 2px) rotate(-.25deg); }
  28%  { transform: translate(6px, -3px) rotate(.3deg); }
  44%  { transform: translate(-7px, 3px) rotate(-.35deg); }
  62%  { transform: translate(5px, -2px) rotate(.25deg); }
  82%  { transform: translate(-2px, 1px) rotate(-.1deg); }
}

/* ---- and the map becomes the screen ----------------------------------- */
/* Pinned to the viewport. The travel from its old rectangle to this one is a
   FLIP written from js/film/takeover.js — nothing here animates, because the
   start position is only knowable at runtime. */
.map-card.filmscreen {
  position: fixed;
  z-index: 90;
  inset: 0;
  width: auto;
  max-width: none;
  margin: 0;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  display: flex;
  flex-direction: column;
}
.map-card.filmscreen .map-stage {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  place-items: center;
}
.map-card.filmscreen .map-svg { width: 100%; height: 100%; }
.map-card.filmscreen :is(.map-head, .map-foot) {
  opacity: .45;
  transition: opacity 600ms ease;
}

/* --------------------------------------------------- the scenery moves ---- */
/* js/film/art.js writes these class names and nothing else. `--d` is the
   per-element delay, so nine clouds on one horizon are not one cloud nine
   times. Every one of them is switched off in the block at the foot of this
   file when motion is unwelcome. */
.fx-drift   { animation: fx-drift 11s ease-in-out var(--d, 0s) infinite alternate; }
.fx-float   { animation: fx-float 6.4s ease-in-out var(--d, 0s) infinite alternate; }
.fx-twinkle { animation: fx-twinkle 3.6s ease-in-out var(--d, 0s) infinite alternate; }
.fx-rise    { animation: fx-rise 6.5s ease-out var(--d, 0s) infinite; }
.fx-sway    { animation: fx-sway 5.2s ease-in-out var(--d, 0s) infinite alternate;
              transform-box: fill-box; transform-origin: 50% 0; }
.fx-flick   { animation: fx-flick 2.6s ease-in-out var(--d, 0s) infinite; }
/* Rain and snow move with the `translate` PROPERTY, not `transform` — the
   petals carry a rotation as a presentation attribute, which maps to the
   transform property, and a keyframe on transform would wipe it out. The
   individual properties compose ahead of it instead. */
.fx-rain    { animation: fx-rain 1.15s linear var(--d, 0s) infinite; }
.fx-snow    { animation: fx-snow 8s linear var(--d, 0s) infinite; }

@keyframes fx-drift {
  from { transform: translateX(-1.6%); }
  to   { transform: translateX(2.2%); }
}
@keyframes fx-float {
  from { transform: translateY(0); }
  to   { transform: translateY(-2.4%); }
}
@keyframes fx-twinkle {
  from { opacity: .22; }
  to   { opacity: 1; }
}
@keyframes fx-rise {
  0%   { opacity: 0; transform: translateY(6%) scale(.86); }
  30%  { opacity: 1; }
  100% { opacity: 0; transform: translateY(-16%) scale(1.3); }
}
@keyframes fx-sway {
  from { transform: rotate(-1.1deg); }
  to   { transform: rotate(1.4deg); }
}
@keyframes fx-flick {
  0%, 100% { opacity: 1; }
  42%      { opacity: .48; }
  61%      { opacity: .92; }
  73%      { opacity: .34; }
}
@keyframes fx-rain {
  from { translate: 0 -12%; opacity: 0; }
  18%  { opacity: .6; }
  to   { translate: -3% 112%; opacity: 0; }
}
@keyframes fx-snow {
  from { translate: 0 -14%; opacity: 0; }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  to   { translate: 4% 116%; opacity: 0; }
}

/* ------------------------------------------------- no unwanted motion ---- */
/* The film is content the viewer went and earned, so ALL OF IT STILL PLAYS, at
   the same reading speed, with the same twenty scenes, the same words and the
   same questions. What goes is every movement nobody asked for: scenes cut
   instead of dissolving, the push-in stops, nothing in the scenery drifts, and
   the page leaves without travelling.

   `[data-motion]` and not the media query — js/core/util.js writes it, and it
   is the single source of truth for the whole site, since a media query cannot
   see the override the diagnostic panel sets. */
:root[data-motion="reduce"] .fscene { transition: none; }
:root[data-motion="reduce"] .fscene.in .fscene-svg,
:root[data-motion="reduce"] .fscene:nth-child(even).in .fscene-svg { animation: none; transform: none; }
:root[data-motion="reduce"] :is(.fx-drift, .fx-float, .fx-twinkle, .fx-rise, .fx-sway, .fx-flick) { animation: none; }
/* falling things are removed rather than frozen: a static field of snow pinned
   mid-air is a rendering bug, not a winter */
:root[data-motion="reduce"] :is(.fx-rain, .fx-snow) { display: none; }
:root[data-motion="reduce"] .film-cap p { transition: opacity 160ms ease; transform: none; }
:root[data-motion="reduce"] .film-next { transition: opacity 160ms ease; transform: none; }
:root[data-motion="reduce"] .film-card { transition: none; transform: none; }
:root[data-motion="reduce"] :is(.fc-opt.good, .fc-opt.bad, .fc-pick.took, .film-tally.hit) { animation: none; }
:root[data-motion="reduce"] :is(.fr-flown, .fr-plane) { transition: none; }
:root[data-motion="reduce"] body.shudder { animation: none; }
/* the same pieces still go, in the same order, and the page is still cleared —
   they simply do not travel or spin to do it */
:root[data-motion="reduce"] .flung {
  animation: fling-calm 260ms ease-out var(--fd, 0ms) both;
}
@keyframes fling-calm { to { opacity: 0; visibility: hidden; } }
:root[data-motion="reduce"] .landing { animation: none; }
