/* ==========================================================================
   PULSE — the motion vocabulary.

   THE RULE THIS FILE ENFORCES: there is no fade-up on this page. Not one
   heading, paragraph or button arrives from opacity 0 and translateY. The brief
   forbids it and it is also the single most tired gesture on the web. Text is
   already in the composition; when something has to arrive it arrives as a CUT
   — a hard-edged cover in the page's own colour that leaves.

   HOW THINGS ARE HIDDEN, AND WHY IT MATTERS.

   core/reveal.js observes with threshold 0.08. An element hidden with
   `clip-path: inset(...)` or `transform: scaleX(0)` therefore NEVER REVEALS,
   silently: IntersectionObserver measures an element's VISUAL rectangle, and
   Chrome applies clip-path and transforms to it, so a geometrically hidden box
   reads as ratio 0 wherever it is on screen. That trap cost a debugging session
   on VITA. The fix is not a smaller threshold in the core — the core is not
   forked — it is to hide by COVER: the element keeps its real box, and a
   pseudo-element painted in the background colour sits on top and slides off.
   Every reveal in this file works that way.

   The covers are painted in var(--bg), which assets/js/site/night.js is moving
   as the reader descends. That is not a coincidence to be tolerated — it is why
   a cover is invisible at every point on the ramp without anybody maintaining a
   list of per-section cover colours.

   Nothing here runs under prefers-reduced-motion: the covers are simply not
   drawn, and every reveal is already in its finished state.
   ========================================================================== */

/* ---------------------------------------------------------------- the cut --
   A horizontal cover leaves to the right. Used on eyebrows, headlines and
   leads — the ordinary arrival on this page. */
.js [data-reveal="cut"] {
  position: relative;
  overflow: hidden;
  padding-bottom: 0.06em;
}
.js [data-reveal="cut"]::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--bg);
  transform: translateX(0);
  transition: transform 520ms cubic-bezier(0.76, 0, 0.14, 1);
  transition-delay: var(--delay, 0ms);
}
.js [data-reveal="cut"].is-in::after { transform: translateX(101%); }

/* ------------------------------------------------------------- the band ----
   Two covers retract vertically, so a photograph opens out of a horizontal
   frequency band rather than fading in. The live-music chapter's arrival. */
.band {
  position: relative;
  /* the covers park OUTSIDE the frame when it is open; without this they sat
     over the headline and lead above the photograph, painted in the page
     colour, and hid the last line of the headline */
  overflow: hidden;
}
.band__cover {
  position: absolute;
  left: 0; right: 0;
  height: 50%;
  background: var(--bg);
  pointer-events: none;
  transition: transform 900ms cubic-bezier(0.16, 1, 0.3, 1);
}
/* OPEN is the default, so a reader with no JavaScript gets the photograph and
   not a letterbox slit. Each cover sits fully outside the frame. */
.band__cover--top { top: 0; transform: translateY(-101%); }
.band__cover--bot { bottom: 0; transform: translateY(101%); }
/* CLOSED: each cover slides back over its half and stops a few per cent short
   of the centre line, which is the lit frequency band the photograph opens
   out of. (Getting these two states the wrong way round is easy and silent:
   the section then shows a permanent horizontal slit and looks like a broken
   image rather than a closed one.) */
.js .band:not(.is-in) .band__cover--top { transform: translateY(-4%); }
.js .band:not(.is-in) .band__cover--bot { transform: translateY(4%); }

/* -------------------------------------------------------------- the flash --
   One brief pale overlay, at one moment, on one section. Never a sequence, and
   never full-screen white: 0.34 opacity of bone over a photograph for 90ms,
   which reads as a camera flash and cannot read as a strobe. */
.turn__flash {
  position: absolute;
  inset: 0;
  z-index: 3;
  background: var(--bone);
  opacity: 0;
  pointer-events: none;
}
.turn__flash.is-flash {
  animation: flash 260ms cubic-bezier(0.2, 0, 0, 1) 1;
}
@keyframes flash {
  0%   { opacity: 0; }
  18%  { opacity: 0.34; }
  100% { opacity: 0; }
}

/* --------------------------------------------------------- the group step --
   reveal.js writes --i and --delay on the children of a [data-reveal-group].
   The peak's three words use it, 220ms apart, so they land like three beats
   rather than a stagger. */
.js [data-reveal-group] [data-reveal] { --delay: calc(var(--i, 0) * 220ms); }

/* ------------------------------------------------------------- reduced -----
   Everything above is decoration on top of content that is already there. With
   motion reduced the covers are removed outright rather than being given a
   0ms transition, so nothing can be left mid-flight by a resize or a locale
   change. */
@media (prefers-reduced-motion: reduce) {
  .js [data-reveal="cut"]::after { display: none; }
  .js .band__cover { display: none; }
  .turn__flash.is-flash { animation: none; }
  .btn, .btn__arrow, .cursor { transition: none; }
  body { transition: none; }
}
