/* =============================================================================
   23-intro.css — the opening sequence
   -----------------------------------------------------------------------------
   One black overlay, one WebGL canvas, one logo. It plays once per tab and then
   deletes itself. Logic lives in 23-intro.js; this sheet owns only appearance
   and the timing curve of the four phases.

   Owns:      .intro and everything inside it.
   Does NOT own: the masthead, the home view, or any page motion. Nothing here
                 may leak onto the page — every selector is scoped to .intro.

   THE FAILSAFE  (read before changing anything)
   ---------------------------------------------
   The overlay is painted by CSS, opaque, from the very first frame. That is
   deliberate: the page underneath must never flash before the overlay lands.
   The cost of that is real — if the JS is disabled, blocked, or throws before
   it can wire itself up, a black rectangle would sit over the site forever.
   So the base rule carries `animation: intro-failsafe` which clears the overlay
   after 5s with no JS involved at all. 23-intro.js cancels that animation (by
   adding .is-js) only once it has successfully taken responsibility for the
   teardown. If you remove the failsafe you are one exception away from a site
   that never renders.

   PHASES  (class names set by the JS, in order)
   ---------------------------------------------
   .is-js    JS is driving; the CSS failsafe is off.
   .is-play  0ms      shader fades up, mark draws, wordmark rises.
   .is-hand  ~1950ms  hand-off: black becomes --paper, shader and logo leave.
   .is-out   ~2470ms  the now-paper overlay fades to nothing over the page.
   .is-fast  set with the last two when the reader interrupts; same phases,
             compressed. Interruption must never look like a bug.
   ============================================================================= */

.intro{
  position:fixed; inset:0; z-index:9999;
  display:flex; align-items:center; justify-content:center;
  background-color:#07060A;
  overflow:hidden;
  /* the no-JS escape hatch — see header */
  animation:intro-failsafe 5s linear forwards;
}
@keyframes intro-failsafe{
  0%,88%{opacity:1; visibility:visible}
  100%{opacity:0; visibility:hidden; pointer-events:none}
}
.intro.is-js{animation:none}

/* ---------- the shader canvas ----------------------------------------------
   Inserted by the JS, never present in the markup, always the first child so
   the logo stacks over it without a z-index war.
   The radial mask is not decoration: the shader is at its brightest dead centre,
   which is exactly where the wordmark sits. Punching the middle out buys the
   type its contrast back without touching a line of GLSL. */
.intro-gl{
  position:absolute; inset:0; width:100%; height:100%;
  display:block; opacity:0;
  -webkit-mask-image:radial-gradient(circle at 50% 50%,
      rgba(0,0,0,0) 0%, rgba(0,0,0,.16) 20%, rgba(0,0,0,.72) 38%, #000 60%);
          mask-image:radial-gradient(circle at 50% 50%,
      rgba(0,0,0,0) 0%, rgba(0,0,0,.16) 20%, rgba(0,0,0,.72) 38%, #000 60%);
  /* a transition, not a keyframe: the hand-off has to interrupt this fade
     mid-flight, and animation -> transition hand-overs jump in some engines */
  transition:opacity 900ms cubic-bezier(.33,.9,.5,1) 60ms;
}
.intro.is-play .intro-gl{opacity:.92}

/* ---------- the identity ---------------------------------------------------
   Same mark and same two lines as the masthead .plate, at display size. The
   fills are lightened for a black ground; they are never seen against paper,
   because the logo has left before the background turns. */
.intro-stage{
  position:relative;
  display:flex; flex-direction:column; align-items:center;
  gap:.9rem; padding:0 1.5rem; text-align:center;
  font-family:'Libre Franklin',system-ui,-apple-system,'Segoe UI',sans-serif;
  color:#F6F3EC;
}
.intro-mark{width:clamp(52px,7vw,74px);height:auto;display:block;opacity:0}
.intro-frame{stroke-dasharray:104;stroke-dashoffset:104}   /* 2*(28+24) — the rect's own perimeter */
.intro-fill,.intro-base{opacity:0}

.intro-name{
  display:block; font-weight:650; letter-spacing:-.018em;
  font-size:clamp(1.32rem,3.2vw,2.15rem); line-height:1.1;
  opacity:0;
}
.intro-sub{
  display:block; font-style:normal; font-size:clamp(.6rem,1.15vw,.72rem);
  text-transform:uppercase; color:#C9BFA6; letter-spacing:.42em; text-indent:.42em;
  opacity:0;
}

/* ---------- entrance -------------------------------------------------------
   The mark draws itself, then the plate fills, then the name, then the strapline
   opens its tracking. Staggered so it reads as one gesture assembling rather
   than four things appearing. */
.intro.is-play .intro-mark {animation:intro-fade 420ms ease-out 120ms forwards}
.intro.is-play .intro-frame{animation:intro-draw 760ms cubic-bezier(.5,0,.2,1) 180ms forwards}
.intro.is-play .intro-fill {animation:intro-fade 520ms ease-out 620ms forwards}
.intro.is-play .intro-base {animation:intro-fade 520ms ease-out 720ms forwards}
.intro.is-play .intro-name {animation:intro-rise 760ms cubic-bezier(.2,.7,.25,1) 640ms forwards}
.intro.is-play .intro-sub  {animation:intro-track 900ms cubic-bezier(.2,.7,.25,1) 880ms forwards}

@keyframes intro-fade{to{opacity:1}}
@keyframes intro-draw{to{stroke-dashoffset:0}}
@keyframes intro-rise{
  from{opacity:0;transform:translateY(10px);filter:blur(6px)}
  to  {opacity:1;transform:none;filter:blur(0)}
}
@keyframes intro-track{
  from{opacity:0;letter-spacing:.86em;text-indent:.86em}
  to  {opacity:1;letter-spacing:.42em;text-indent:.42em}
}

/* ---------- hand-off -------------------------------------------------------
   The one moment that could go wrong. The shader is saturated RGB on black and
   the site is #F6F3EC paper; cutting between them would strobe. So the overlay
   itself becomes paper WHILE the shader and the logo fade out on top of it, and
   only then does the (now paper-coloured) overlay fade away over a page that is
   already paper-coloured. The reader sees the content resolve, not a flash. */
.intro.is-hand{transition:background-color 560ms cubic-bezier(.4,0,.2,1);background-color:var(--paper,#F6F3EC)}
.intro.is-hand .intro-gl{opacity:0;transition:opacity 400ms ease-in}
/* the stage is faded as a whole; its children keep their own entrance
   animations running underneath, which is why they are separate elements */
.intro.is-hand .intro-stage{
  opacity:0;transform:translateY(-8px) scale(.985);
  transition:opacity 360ms ease-in,transform 520ms cubic-bezier(.4,0,.2,1);
}

.intro.is-out{opacity:0;pointer-events:none;transition:opacity 500ms cubic-bezier(.4,0,1,1)}

/* interrupted: same journey, roughly a third of the distance */
.intro.is-fast.is-hand{transition:background-color 150ms linear}
.intro.is-fast.is-hand .intro-gl{transition:opacity 130ms linear}
.intro.is-fast.is-hand .intro-stage{transition:opacity 130ms linear,transform 160ms linear}
.intro.is-fast.is-out{transition:opacity 200ms linear}

/* ---------- opt-outs -------------------------------------------------------
   Non-negotiable: reduce means the overlay never exists as far as the reader is
   concerned. This rule stands alone — it does not depend on the JS running, so
   it holds even if the script is blocked. The JS also removes the node. */
@media (prefers-reduced-motion:reduce){
  .intro{display:none !important;animation:none !important}
}
/* .is-skip is set the instant the JS decides not to play at all (already seen
   this tab, or no WebGL). Belt and braces with the removeChild that follows. */
.intro.is-skip{display:none !important;animation:none !important}
