/* ============================================================
   MERCE & IAN - gate.css
   The Percy gate: the full-screen welcome you tap to come in.

   Percy rises into frame, presses his nose to the glass, the glass ripples
   and takes a print, and a bubble says hello. Played as joy - the gold halo
   is the only thing that says he's watching over the day, and it stays
   quiet. Nothing on this screen names him.

   THE BOOP IS PURE CSS. No GSAP. Two reasons: the house rule is that
   everything degrades without GSAP, so a GSAP version would have to be
   written twice; and a CSS animation can be frozen mid-flight with
   animation-play-state, which is exactly what a skippable intro needs when
   somebody taps at t=0.4s. GSAP keeps its one job, the hero zoom.

   GEOMETRY: everything hangs off --nose-x / --nose-y, the nose tip as a
   percentage of the image box. They are the lean origin, the ripple centre,
   the smudge anchor and the bubble's tail target all at once, so re-dialling
   a new photo of Percy is a one-line change. Append ?pgdebug to the URL for
   a crosshair on the nose point.

   Depends on: tokens.css   ·   behaviour in js/gate.js
   ============================================================ */

/* ---- The gate is opt-in via a class only JS can add (see the inline head
        script). No JS → never armed → never painted, and scroll is never
        locked, so the page degrades to an ordinary one-pager. ---- */
html:not(.gate-armed) .pg{display:none}
html.gate-armed,html.gate-armed body{overflow:hidden;height:100%}

.pg{
  /* THE PAIR. Percy is 904x1240, Jackson 659x1000. Laid out with Percy at
     height 100 (width 72.9) and Jackson at 88 (width 58), overlapping by 14 - so the box is 117 wide by 100 tall, and these are the shares of it.
     Jackson at 88 rather than 78: at 78 he read as an extra rather than the
     other half of the pair.
     Everything that belongs to Percy alone (halo, wings) is expressed
     against --percy-cx so it tracks him rather than the whole pair. */
  /* THE ARTWORK'S RELATIONSHIP TO THE DOGS. Everything below is a share of
     the stage or of Percy - never of the viewport. Mixing in vw was what
     broke this: the dogs were sized from the stage while the wings were
     capped in vw, so the wingspan drifted from 3.2x Percy's width on a
     laptop down to 1.5x on a phone. Measured with the sweep in the commit
     message; these ratios now hold to 3 decimal places at every size. */
  --wing-span:202%;                          /* of the stage */
  --wing-x:calc(var(--percy-cx) + 4%);
  --wing-y:32%;
  --halo-span:.76;                           /* of Percy's width */
  --halo-y:-5%;

  /* The caption's size and position are variables because the hint has to
     follow it, and because the clearance above the halo has to be measured
     in the caption's OWN font size - measuring it in inherited ems put the
     caption a whole line lower than intended and back on top of the halo. */
  --cap-fs:clamp(1.08rem,min(3.1vw,3.3vh),2rem);
  /* never higher than just under the boop prompt, which now sits at the top */
  --prompt-floor:calc(clamp(10px,2.4vh,22px) + 44px + 12px + env(safe-area-inset-top,0px));
  --caption-top:max(calc(clamp(14px,3vh,44px) + env(safe-area-inset-top,0px)),
                    /* one line to clear now, not three, so it sits just over the halo */
                    calc(var(--stage-top) - 0.105 * var(--stage-w) - 2.2 * var(--cap-fs)));

  /* the last term keeps room above the halo for the prompt and one line of
     dialogue: the halo reaches .105 of the stage width above it, and the
     stage is width / 1.17 tall, so the pair takes .96 of its width in height */
  --stage-w:min(92vw,88svh,760px,calc((100svh - 110px) / .96));
  --stage-h:calc(var(--stage-w) / 1.17);
  --stage-top:calc(100svh - var(--stage-h));

  --percy-w:62.4%;
  --jack-w:49.6%;
  --percy-cx:31.2%;   /* Percy's centre, for the halo and wings */
  --nose-x:26.5%;     /* Percy's nose in pair coordinates */
  --nose-y:40%;
  /* Jackson's, measured the same way. Both are shares of the FIGURE, which is
     what the boop buttons sit in; each dog's own transform-origin below is
     the same point re-expressed inside his own box, because a wrapper cannot
     take an origin in its parent's coordinates. */
  --jack-nose-x:70%;
  --jack-nose-y:52%;
  /* ONE KNOB FOR THE WHOLE SEQUENCE. Every delay and duration is
     calc(<beat> * var(--tempo)), so the whole thing slows or quickens
     together without re-deriving a dozen numbers. 1 = ~5.5s end to end. */
  --tempo:1;
  position:fixed;inset:0;z-index:var(--z-gate);background:#16110c;
  overflow:hidden;cursor:pointer;touch-action:none;
  transition:opacity .55s ease,transform .55s ease}
.pg.is-leaving{opacity:0;transform:scale(1.04);pointer-events:none}

.pg-veil{position:absolute;inset:0;z-index:1;
  /* Now a vignette OVER the photo rather than an opaque field under it:
     transparent-ish in the middle so the venue reads, closing to near-solid
     at the edges so nothing competes with the pair. */
  background:radial-gradient(126% 106% at 50% 46%,
    rgba(28,21,15,0.26) 0%, rgba(22,17,12,0.68) 52%, rgba(13,9,6,0.96) 100%)}

.pg-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);
  clip-path:inset(50%);white-space:nowrap}

/* ============================================================
   THE VENUE, WASHED INTO THE PALETTE
   Same recipe as the couple photo in the hero (scene.css .cs-photo):
   grayscale the photograph, recolour it with a burgundy/olive/gold gradient
   in mix-blend-mode:color, then soft-light the whole thing over the veil.
   That way a real photograph reads as part of this palette rather than as a
   picture pasted behind everything.

   Knobs: --photo-wash for how present it is, the mask focus point, and the
   ::after tint.
   ============================================================ */
.pg-photo{position:absolute;inset:0;z-index:0;pointer-events:none;overflow:hidden;
  /* The hero soft-lights its photo because it sits over a mesh with bright
     gold in it. The gate's backdrop is uniformly dark, and soft-light over a
     dark backdrop can only stay dark - the venue vanished. Here the photo is
     the base layer and .pg-veil vignettes it from above. */
  --photo-wash:.9;opacity:var(--photo-wash)}
.pg-photo::before{content:"";position:absolute;inset:0;
  background:url("../assets/venue-dusk.webp") center / cover no-repeat;
  /* Scaled and panned rather than repositioned: the photo's aspect matches a
     landscape viewport almost exactly, so background-position has no slack to
     work with. This drops the horizon - and the sunset with it - above their
     heads instead of behind them. */
  transform:scale(1.2) translateY(7%);
  /* Only PART way to grayscale, unlike the hero. That photo is a dusk sky
     that is already burgundy and gold - desaturating it completely threw
     away the one thing worth having, and the tint gradient below then had
     to invent the colour back. */
  filter:grayscale(.5) contrast(1.06) brightness(.82) saturate(1.2)}
.pg-photo::after{content:"";position:absolute;inset:0;mix-blend-mode:color;
  background:linear-gradient(152deg, var(--burgundy) 0%, var(--olive) 52%, var(--gold) 100%);
  opacity:.5}

/* ============================================================
   STAGE - the image box. Sized off three axes so Percy keeps a sensible
   share of the screen on a tall phone, a laptop and a wide short window.
   The stage IS the image box, which is what lets --nose-* mean the same
   thing as a transform-origin and as a ripple centre.
   ============================================================ */
/* Sized off three axes so he keeps a sensible share of a tall phone, a
   laptop and a wide short window. Big on purpose: the moment only works
   if his face genuinely fills the glass.
   The svh term is the one that matters: the halo sits above his crown, and
   measured, its top lands near (viewport - 1.10 x stage height). Anything
   over ~58svh pushes it off the top of a 768- or 800-tall laptop screen. */
.pg-stage{position:absolute;left:50%;bottom:0;z-index:2;transform:translateX(-50%);
  width:var(--stage-w);aspect-ratio:117/100}

/* Gentle life. Pivots at his feet, so he rocks the way a standing dog does.
   Its own layer because .pg-figure's transform belongs to the approach. */
.pg-sway{position:absolute;inset:0;z-index:4;
  transform-origin:50% 100%;
  animation:pgSway calc(9s * var(--tempo)) ease-in-out infinite}

/* He walks up out of the dark rather than lunging at the glass: the origin
   is his feet, so growing reads as closing distance rather than zooming. */
.pg-figure{position:absolute;inset:0;
  transform-origin:50% 100%;
  animation:pgApproach calc(3.2s * var(--tempo)) linear both}

/* Nothing starts until Percy has decoded, or the drawing gets ahead of him
   on a slow connection. animation-play-state is not inherited, so every
   animated class has to be named here. */
.pg-figure,.pg-draw,.pg-line,.pg-dog,.pg-hint,.pg-ps,.pg-spark,
.pg-names,.pg-glint,
.pg-halo-bloom,.pg-halo-lift{animation-play-state:paused}
.pg.is-ready .pg-figure,.pg.is-ready .pg-draw,.pg.is-ready .pg-line,
.pg.is-ready .pg-dog,.pg.is-ready .pg-hint,.pg.is-ready .pg-ps,.pg.is-ready .pg-spark,
.pg.is-ready .pg-names,.pg.is-ready .pg-glint,
.pg.is-ready .pg-halo-bloom,.pg.is-ready .pg-halo-lift{animation-play-state:running}
.pg.is-leaving .pg-figure{animation-play-state:paused}   /* freeze him where he is */

/* The shadow lives in a variable because the spotlight keyframes below
   rewrite `filter` wholesale and would otherwise drop it. */
/* The wrapper is the one that is placed; the image just fills it. */
.pg-dog-wrap{position:absolute;bottom:0;
  transform-origin:var(--boop-ox,50%) var(--boop-oy,50%)}
/* THE SHADOW IS ITS OWN LAYER, NOT A FILTER. It was drop-shadow(0 18px 40px),
   animated with the spotlight, and iOS Safari can paint a blurred filter like
   that as a faint grey box around the image instead of a shadow: on Percy's
   cut-out it read as a grey background. A soft dark ellipse behind each dog
   does the same job with no filter to go wrong. --dog-shadow stays in the
   spotlight keyframes as a no-op so their filter lists still match. */
.pg-dog{width:100%;height:auto;display:block;position:relative;z-index:1;
  --dog-shadow:brightness(1);
  filter:var(--dog-shadow);
  transform-origin:50% 100%}
.pg-dog-wrap::before{content:"";position:absolute;z-index:0;left:6%;right:6%;top:12%;bottom:-8%;
  border-radius:50%;pointer-events:none;
  background:radial-gradient(closest-side,rgba(0,0,0,.5),rgba(0,0,0,.28) 55%,rgba(0,0,0,0));
  translate:0 18px}

/* SPOTLIGHT. Whoever is speaking stays lit and leans in a little; the other
   drops back. Pivot is at their feet, so the lean reads as stepping forward
   rather than floating.

   The 4.9s window starting at 4.55s is cut to the dialogue, and the six
   animations that share it (both dogs, the halo, its bloom, its lift and
   the wings) must stay on the same clock or the light and the voice come
   apart. The stops: 8% Percy lit as his line starts - 47% his line begins
   to clear - 53% the light crosses to Jackson - 88% his line clears - 100%
   both neutral, in time for the line they share. Change the line delays in
   index.html and these move with them. */
.pg-percy  {animation:pgSpotPercy calc(4.9s * var(--tempo)) ease-in-out both calc(4.55s * var(--tempo))}
.pg-jackson{animation:pgSpotJack  calc(4.9s * var(--tempo)) ease-in-out both calc(4.55s * var(--tempo))}
@keyframes pgSpotPercy{
  0%      {filter:var(--dog-shadow);transform:none}
  8%,47%  {filter:var(--dog-shadow) brightness(1.04);transform:translateY(-8px) scale(1.03)}
  53%,88% {filter:var(--dog-shadow) brightness(.52) saturate(.72);transform:none}
  100%    {filter:var(--dog-shadow);transform:none}}
@keyframes pgSpotJack{
  0%      {filter:var(--dog-shadow);transform:none}
  8%,47%  {filter:var(--dog-shadow) brightness(.52) saturate(.72);transform:none}
  53%,88% {filter:var(--dog-shadow) brightness(1.04);transform:translateY(-8px) scale(1.03)}
  100%    {filter:var(--dog-shadow);transform:none}}
/* --boop-ox/oy are each dog's nose as a share of HIS OWN box, not the
   figure's: Percy's box is 62.6% of the figure and starts at its left edge,
   Jackson's is 49.7% and starts at 50.3%. Measured, not guessed - an origin
   a few per cent out makes the squash look like a wobble. */
.pg-percy-wrap  {left:0; width:var(--percy-w);z-index:5;--boop-ox:42%;--boop-oy:40%}
.pg-jackson-wrap{right:0;width:var(--jack-w); z-index:2;--boop-ox:40%;--boop-oy:46%}

/* Percy throws a little shade onto Jackson where they overlap. Almost
   invisible against the veil, but over Jackson's pale chest it is the cue
   that says one of them is in front of the other. */
.pg-shade{position:absolute;z-index:4;bottom:0;
  left:calc(var(--percy-w) - 11%);width:15%;height:78%;pointer-events:none;
  background:linear-gradient(90deg,rgba(10,7,5,.5),rgba(10,7,5,0) 78%);
  filter:blur(9px)}

.pg.is-nopercy .pg-dog-wrap,.pg.is-nopercy .pg-shade,
.pg.is-nopercy .pg-boop-j{display:none}
.pg-ph{display:none}
.pg.is-nopercy .pg-ph{display:block;position:absolute;z-index:2;
  left:var(--nose-x);top:var(--nose-y);width:46%;height:auto;
  transform:translate(-50%,-50%);
  fill:rgba(201,162,75,.22);stroke:rgba(201,162,75,.6);stroke-width:2.5;
  filter:drop-shadow(0 0 26px rgba(201,162,75,.3))}

/* warm bloom behind the head so he separates from the veil */
.pg-glow{position:absolute;z-index:1;left:50%;top:calc(var(--nose-y) - 2%);
  width:104%;height:70%;transform:translate(-50%,-50%);pointer-events:none;
  background:radial-gradient(closest-side, rgba(201,162,75,.30) 0%, rgba(201,162,75,.10) 46%, transparent 76%);
  filter:blur(6px)}

/* ---- DRAWN HALO ----
   Hand-drawn rather than geometric: the paths carry a small random wobble and
   the halo is stroked twice, slightly out of register, the way a marker
   doubles back. Glow is stacked drop-shadows rather than an SVG filter - it
   composites on the GPU and survives the figure being scaled. */
.pg-ink{fill:none;stroke:#f0d189;stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round}
/* Each path carries pathLength="1", so its real length is normalised and one
   dasharray value traces a 40-unit vein and a 600-unit outline alike. */
/* --len is the path's measured length, set by gate.js; 1 matches pathLength
   for the moment before it runs */
.pg-draw{stroke-dasharray:var(--len,1);stroke-dashoffset:var(--len,1);
  animation:pgDraw var(--dd,.5s) cubic-bezier(.45,.05,.3,1) forwards var(--d,0s)}
@keyframes pgDraw{to{stroke-dashoffset:0}}
/* drawn: a plain solid line from here on (gate.js) */
.pg .pg-draw.is-drawn{animation:none;stroke-dasharray:none;stroke-dashoffset:0}

.pg-halo{position:absolute;z-index:5;left:var(--percy-cx);top:var(--halo-y);
  /* no px cap: a cap makes the halo proportionally smaller the bigger the
     screen gets, which is exactly the drift this whole block avoids */
  width:calc(var(--percy-w) * var(--halo-span));
  height:auto;transform:translate(-50%,-50%);pointer-events:none;
  overflow:visible;
  /* Five stacked shadows rather than three: a tight white-hot core, then
     gold falling away a long way out. On a phone there are no wings, so this
     is the only thing saying angel and it has to carry more. */
  /* Five stacked shadows, and the job is split: the first three make the ring
     itself read as lit, the last two are the haze it throws. The haze is what
     reached up into the couple's names, so the outer two are tight and faint - the ring stays exactly as bright, the cloud around it does not. */
  --halo-glow:drop-shadow(0 0 2px rgba(255,248,224,1))
              drop-shadow(0 0 7px rgba(240,209,137,.95))
              drop-shadow(0 0 18px rgba(201,162,75,.72))
              drop-shadow(0 0 34px rgba(201,162,75,.30))
              drop-shadow(0 0 58px rgba(201,162,75,.12));
  filter:var(--halo-glow);
  /* Two animations, no conflict: pgHalo owns opacity and transform, the dim
     owns filter. The dim uses filter's opacity() rather than the opacity
     property for exactly that reason. */
  animation:pgHalo calc(7s * var(--tempo)) ease-in-out infinite,
            pgAura calc(4.9s * var(--tempo)) ease-in-out both calc(4.55s * var(--tempo))}
/* Percy's halo is part of Percy: when he leans in to speak it goes with him.
   It needs its own layer because .pg-halo's transform is already carrying the
   centring and the breathe, and .pg-halo-bloom's the centring. */
.pg-halo-lift{position:absolute;inset:0;z-index:5;pointer-events:none;
  transform-origin:50% 100%;
  animation:pgLiftHalo calc(4.9s * var(--tempo)) ease-in-out both calc(4.55s * var(--tempo))}
@keyframes pgLiftHalo{
  0%      {transform:none}
  8%,47%  {transform:translateY(-8px) scale(1.03)}
  53%,100%{transform:none}}

/* A ring of light behind the ring of ink. Stacked drop-shadows on a 4px
   stroke can only bloom so far; this is the soft light the halo is meant to
   be casting, and it dims with Percy like the rest of his aura. */
/* Sized and shaped to HUG the ring rather than sit behind it as a cloud: 1.62
   rather than 2.15 of the halo's width, and 3.1/1 rather than 2.1/1, which is
   close to the ellipse's own proportion. A round bloom around a flat ring is
   what read as a thick haze - most of it was above and below the ink, where
   the ring is not. */
.pg-halo-bloom{position:absolute;z-index:5;left:var(--percy-cx);top:var(--halo-y);
  width:calc(var(--percy-w) * var(--halo-span) * 1.62);aspect-ratio:3.1/1;
  transform:translate(-50%,-50%);pointer-events:none;opacity:0;
  --bloom-blur:blur(9px);filter:var(--bloom-blur);
  background:radial-gradient(closest-side,
    rgba(255,244,212,.26) 0%, rgba(232,196,110,.14) 38%,
    rgba(201,162,75,.06) 62%, transparent 78%);
  animation:pgBloomIn calc(1.2s * var(--tempo)) ease-out forwards calc(4.6s * var(--tempo)),
            pgAuraB calc(4.9s * var(--tempo)) ease-in-out both calc(4.55s * var(--tempo))}
@keyframes pgBloomIn{to{opacity:1}}
@keyframes pgAuraB{
  0%,47% {filter:var(--bloom-blur)}
  53%,88%{filter:var(--bloom-blur) brightness(.5) opacity(.45)}
  100%   {filter:var(--bloom-blur)}}

.pg-halo .pg-ink{stroke-width:4.2}
.pg-halo-2{stroke-opacity:.55;stroke-width:3}

.pg-wings{position:absolute;z-index:0;left:var(--wing-x);top:var(--wing-y);
  /* THE WINGS ARE GONE (the couple, 2026-09-28: the halo is enough), but
     this box stays: the sparkles are placed in its coordinates, and all the
     placement below still decides where they fall around Percy. */
  /* Rooted on Percy so both wing roots converge behind HIM - centred on the
     pair they converged between the two dogs and the right one read as
     Jackson's. The +4% is because Percy sits left of the viewport centre:
     perfectly centred on him, a symmetric pair runs the left wing off the
     screen edge before the right one has cleared Jackson.
     Lifted to head height rather than shoulder height, because
     that is where the open space is: at chest level Jackson stands exactly
     where the right wing wants to be, and it either vanished behind him or
     had to cross his face.

     34% is not arbitrary. The wings are hollow line art, so they only show
     in the sliver of background between Percy's jowl and Jackson's ear when
     a STROKE happens to fall in it - the hollow interior shows nothing.
     Measured across the range, 32% is where most of the wing body falls in
     that gap, which is what makes the right wing read as one continuous wing
     passing behind Jackson rather than a shape stuck to his back. */
  width:var(--wing-span);height:auto;transform:translate(-50%,-50%);pointer-events:none;
  overflow:visible;
  --wing-glow:drop-shadow(0 0 3px rgba(240,209,137,.8))
              drop-shadow(0 0 16px rgba(201,162,75,.6))
              drop-shadow(0 0 40px rgba(201,162,75,.34));
  filter:var(--wing-glow);
  animation:pgAuraW calc(4.9s * var(--tempo)) ease-in-out both calc(4.55s * var(--tempo))}
@keyframes pgAura{
  0%      {filter:var(--halo-glow)}
  8%,47%  {filter:var(--halo-glow)}
  53%,88% {filter:var(--halo-glow) brightness(.5) opacity(.45)}
  100%    {filter:var(--halo-glow)}}
@keyframes pgAuraW{
  0%      {filter:var(--wing-glow)}
  8%,47%  {filter:var(--wing-glow)}
  53%,88% {filter:var(--wing-glow) brightness(.5) opacity(.45)}
  100%    {filter:var(--wing-glow)}}
/* The sparkles are placed in the wings' viewBox, which on a phone is far
   wider than the screen - so most of them landed outside it. --spark-spread
   pulls their positions in toward the viewBox origin, and --spark-size gives
   The origin is 0 0 in user space,
   NOT the viewBox's top-left corner - Chromium measures transform-origin from
   the SVG's coordinate origin here, and using the corner scaled everything
   sideways instead of inward. */
.pg-sparks{transform-box:view-box;transform-origin:0 0;
  transform:scale(var(--spark-spread,.80))}

.pg-spark{fill:#fff6dc;transform-box:fill-box;transform-origin:50% 50%;opacity:0;
  animation:pgSpark var(--st,6s) ease-in-out infinite var(--sd,0s)}

/* ---- fireflies gathering near him. Eight spans, not a canvas: a second rAF
        loop behind an opaque overlay is battery spent on nothing. ---- */
.pg-mote{position:absolute;z-index:3;left:50%;top:38%;width:4px;height:4px;border-radius:50%;
  background:rgba(231,220,198,.9);box-shadow:0 0 9px 2px rgba(201,162,75,.75);
  pointer-events:none;opacity:0;
  animation:pgMote var(--mt,7s) ease-in-out infinite var(--mtd,0s)}

/* ============================================================
   THE BOOP - the prompt says to boop their noses, so the noses are the way
   in. A click anywhere else on the gate still enters plainly; booping a nose
   is the same journey with the joke attached.

   Only one boop ever lands, because it is also the way in. That is why the
   squash can be a one-shot class rather than a re-armable animation.
   ============================================================ */
/* max(), not a flat per-cent: 12% of the figure is a comfortable 91px target
   on a laptop but only 45px on a 390px phone, which is under the 44px floor
   once you allow for a fingertip landing off-centre. */
.pg-boop{position:absolute;z-index:7;
  left:var(--nose-x);top:var(--nose-y);
  width:max(12%,48px);aspect-ratio:1;
  transform:translate(-50%,-50%);
  padding:0;border:0;border-radius:50%;background:none;cursor:pointer;
  -webkit-tap-highlight-color:transparent}
.pg-boop-j{left:var(--jack-nose-x);top:var(--jack-nose-y)}
/* The only resting cue is on hover, and it is deliberately faint: on a phone
   there is no hover at all, so this can never be what tells someone the
   noses are boopable - the prompt has to do that, and does. */
.pg-boop::before{content:"";position:absolute;inset:14%;border-radius:50%;
  background:radial-gradient(closest-side,rgba(255,244,212,.22),transparent 72%);
  opacity:0;transition:opacity .3s ease}
.pg-boop:hover::before{opacity:1}

/* Squash and stretch about the nose, CUT TO THE SOUND. The clip is a double
   pop: measured off the decoded buffer, the pops land at +13ms and +239ms
   from the press. The first version of this peaked once, at 166ms - between
   the two, matching neither, so you heard a pop instantly and saw the dog
   squash a sixth of a second later, then heard a second pop with nothing on
   screen at all. Hence two bounces, on the beats:

     5%  =  32ms   first contact   (pop at 13ms)
     37% = 237ms   second, softer  (pop at 239ms)

   If the sound is ever replaced, re-measure and move these two. */
.pg-dog-wrap.is-booped{animation:pgBoop .64s cubic-bezier(.25,.9,.35,1) 1}
@keyframes pgBoop{
  0%  {transform:none}
  5%  {transform:scale(1.075,1.018)}
  14% {transform:scale(.985,1.008)}
  24% {transform:scale(1.004,.998)}
  37% {transform:scale(1.05,1.012)}
  48% {transform:scale(.99,1.005)}
  62% {transform:scale(1.004,.999)}
  100%{transform:none}}

/* One ring per pop. --rd/--rt are set inline per ring, the house idiom, so
   the second one leaves on the second pop and is done before the gate does. */
.pg-boop-ring{position:absolute;inset:0;border-radius:50%;opacity:0;
  /* bright rather than gold-dark: it fires over a black nose, which is the
     one place on either dog where a gold hairline disappears */
  border:1.5px solid rgba(255,244,212,.85);
  box-shadow:0 0 12px rgba(240,209,137,.45);pointer-events:none}
.pg-boop.is-booped .pg-boop-ring{animation:pgBoopRing var(--rt,.56s) ease-out 1 var(--rd,0s)}
@keyframes pgBoopRing{
  from{opacity:.9;transform:scale(.28)}
  to  {opacity:0; transform:scale(2.1)}}

/* ============================================================
   CONTACT - rings, wave and the nose print. Centred on the nose point, and
   sharing one coordinate system so the print lands where the ripple starts.
   ============================================================ */
.pg-contact{position:absolute;z-index:5;left:var(--nose-x);top:var(--nose-y);
  width:min(86%,400px);aspect-ratio:1;transform:translate(-50%,-50%);pointer-events:none}

/* The ripple, flash and nose print belonged to the boop and went with it.
   .pg-contact survives only to anchor the exit ring at the nose point. */

/* ============================================================
   CAPTION - what they say, as one line of type rather than a bubble.
   A bubble has to be anchored to a face, and anything anchored to a face
   drifts on some screen; this sits in the same place at every size. Who is
   speaking is carried by the spotlight on the dogs instead.
   ============================================================ */
/* Anchored to the COMPOSITION, not to the top of the window: sitting a fixed
   distance from the viewport top left it stranded halfway up a large monitor
   while the dogs stayed near the bottom. This keeps it a constant distance
   above the halo, and the max() stops it running off the top of a short one.
   0.105 x stage width is how far the halo reaches above the stage (measured). */
.pg-caption{position:absolute;z-index:6;left:50%;top:var(--caption-top);
  font-size:var(--cap-fs);
  transform:translateX(-50%);width:min(88vw,40ch);min-height:3.2em;
  text-align:center;pointer-events:none}
.pg-line{position:absolute;left:0;right:0;top:0;margin:0;
  /* opacity:1 - the WORDS carry the entrance now, so a line hidden here would
     never come back; the line's own animation only handles the exit */
  opacity:1;
  font-family:var(--font-display);font-style:italic;font-weight:500;
  /* sized against BOTH axes: on a short landscape window the halo rides high
     and a width-only clamp made the caption collide with it */
  font-size:inherit;line-height:1.16;letter-spacing:.012em;
  color:var(--tan-soft);
  /* the same gold the halo casts, so the voices belong to the light above
     them. Kept under a third opacity - any more and the italics smear. */
  text-shadow:0 0 18px rgba(240,209,137,.30),
              0 0 44px rgba(201,162,75,.16),
              0 2px 18px rgba(0,0,0,.80)}

/* Word by word, each one rising out of a little blur - the line assembles
   itself rather than arriving all at once. The words carry the entrance; the
   line itself only handles the exit.
   Each word also FLARES as it lands and settles back to the quiet glow above:
   the shadow list is three deep at both ends of pgWord because a list that
   changes length interpolates as a cross-fade rather than a bloom. */
.pg-line span{display:inline-block;opacity:0;
  transform:translateY(14px);filter:blur(8px);
  text-shadow:0 0 26px rgba(240,209,137,.85),
              0 0 60px rgba(201,162,75,.50),
              0 2px 18px rgba(0,0,0,.80);
  animation:pgWord calc(.78s * var(--tempo)) cubic-bezier(.2,.7,.25,1) forwards
            calc(var(--d,0s) + var(--w,0) * .11s * var(--tempo))}
@keyframes pgWord{
  to{opacity:1;transform:none;filter:blur(0);
     text-shadow:0 0 18px rgba(240,209,137,.30),
                 0 0 44px rgba(201,162,75,.16),
                 0 2px 18px rgba(0,0,0,.80)}}

/* --hold is how long the finished line sits before it clears. It is a
   variable because the last line trails off on an ellipsis and wants a
   longer beat than the two introductions. */
.pg-line-say{animation:pgLineOut calc(.4s * var(--tempo)) ease-in forwards
             calc(var(--d,0s) + var(--hold,1.95s) * var(--tempo))}
@keyframes pgLineOut{to{opacity:0;transform:translateY(-8px)}}

/* ---- THE NAMES. The dialogue trails off on "pawparents..." and the couple
        arrive in the space it leaves - same slot in the caption, so it reads
        as the sentence resolving rather than a new element appearing.

        Styling is .cs-title's, shared from invite.css so the two pages can
        never drift: MI Ornament, the gold ampersand in MI Display italic,
        the same warm text-shadow. Only the ceiling is lowered here - 5.8rem
        is hero scale and this is a beat inside a sequence.

        It breaks out of .pg-caption's measure (40ch / 26ch, set for running
        italic text) with width:max-content, and stays centred on the same
        axis because the caption is itself centred. ---- */
.pg-names{font-family:var(--font-script);
  position:absolute;top:0;left:50%;margin:0;
  width:max-content;max-width:98vw;
  transform:translateX(-50%);opacity:0;
  font-size:clamp(2rem,8.6vw,4.6rem);
  /* how far apart the letters start. A variable because the tracked-out
     state is WIDER than the set title, and on a phone the set title already
     fills most of the width - .42em there would start it off both edges. */
  --names-track:.18em;
  animation:pgNames calc(1.8s * var(--tempo)) cubic-bezier(.16,.84,.3,1) forwards var(--d,0s)}
/* The tracking-in title: letters wide apart and out of focus, closing to
   their set width. margin-right cancels the trailing letter-space - without
   it the block is half a gap too wide and the title slides right as it
   settles, which is the classic centring bug with animated letter-spacing. */
/* The flare is carried by text-shadow, which is cast from the glyph outlines - so the light traces the letters instead of sitting behind them as a
   shape. It rides in the same keyframes rather than a second animation
   because two animations on one element cannot both write text-shadow.

   It stays dark through 0% on purpose: the letters are under blur(14px)
   there, and a glow applied to a blurred glyph is just fog. It comes up as
   the blur clears, peaks at 58%, and settles to .cs-title's own resting
   shadow - the glow the names have on the coming-soon page, and where they
   are left once the arrival is over.

   Three shadows at every stop, including the ones that are transparent: a
   list that changes length cross-fades instead of blooming. */
@keyframes pgNames{
  0%  {opacity:0;letter-spacing:var(--names-track);
       margin-right:calc(-1 * var(--names-track));
       transform:translateX(-50%) scale(1.09);filter:blur(14px);
       text-shadow:0 0 0 rgba(255,248,230,0),
                   0 0 0 rgba(240,209,137,0),
                   0 0 0 rgba(201,162,75,0)}
  26% {opacity:1}
  58% {text-shadow:0 0 11px rgba(255,248,230,.62),
                   0 0 34px rgba(240,209,137,.42),
                   0 0 76px rgba(201,162,75,.22)}
  100%{opacity:1;letter-spacing:.03em;margin-right:-.03em;
       transform:translateX(-50%) scale(1);filter:blur(0);
       text-shadow:0 0 34px rgba(201,162,75,.40),
                   0 2px 44px rgba(110,36,54,.28),
                   0 0 0 rgba(201,162,75,0)}}

/* ---- GLINTS. What keeps the names alive once the flare has gone: four
        catchlights that take turns, never more than about one at a time.
        Drawn rather than lit - a glint is a hard little cross of light, so
        it stays legible at 12px where another soft glow would just be more
        haze, and it cannot compete with the halo the way a second bloom did.

        Three gradients make the star: a wide flat lens, a tall narrow one
        crossing it, and a small core where they meet. Sized in em so they
        track the names at every breakpoint, and positioned in per-cent of
        the names box, so they stay on the same letters as it scales. ---- */
.pg-glints{position:absolute;inset:0;pointer-events:none}
.pg-glint{position:absolute;left:var(--gx);top:var(--gy);
  /* .44em, not the .23em this started at: the spikes are a few per cent of
     the box, so at 17px they were half a pixel thick and the star collapsed
     into a dot. Big enough for the spikes to taper is the floor here. */
  width:calc(var(--gs,1) * .44em);aspect-ratio:1;
  transform:translate(-50%,-50%) scale(0);opacity:0;
  background:
    radial-gradient(ellipse 50% 6% at 50% 50%, rgba(255,252,242,1), transparent 74%),
    radial-gradient(ellipse 6% 50% at 50% 50%, rgba(255,252,242,1), transparent 74%),
    radial-gradient(closest-side, rgba(255,247,226,.75) 0%, rgba(255,244,212,.28) 26%, transparent 58%);
  /* --d is the names' own delay and inherits; +1.5s lands the first one just
     as the letters come into focus. --gd spreads the rest evenly around the
     cycle, which is what keeps them taking turns instead of blinking together. */
  animation:pgGlint calc(5.4s * var(--tempo)) ease-in-out infinite
            calc(var(--d,0s) + 1.5s * var(--tempo) + var(--gd,0s))}
/* on for under a fifth of the cycle: it should read as catching the light as
   you move, not as something flashing at you */
@keyframes pgGlint{
  0%,100%{opacity:0;transform:translate(-50%,-50%) scale(.15) rotate(0deg)}
  5%     {opacity:1;transform:translate(-50%,-50%) scale(1)   rotate(9deg)}
  18%    {opacity:0;transform:translate(-50%,-50%) scale(.25) rotate(20deg)}}

/* ============================================================
   CONTROLS - the hint text IS the enter button, so there is one visible,
   focusable control whose label is already the instruction. Tab order is
   exactly two stops. A handler on .pg still makes the whole screen tappable.
   ============================================================ */
/* IN THE LINE'S PLACE, just over the halo: once the two of them have said
   hello, the prompt to come in is the next thing said, in the same spot. It
   arrives as Jackson's line clears, centred on the line it replaces. */
.pg-hint{position:absolute;z-index:8;left:50%;
  top:calc(var(--caption-top) + .6 * var(--cap-fs) - 34px);
  transform:translateX(-50%);opacity:0;
  font-family:var(--font-ui);font-size:11px;font-weight:500;letter-spacing:.3em;
  text-transform:uppercase;color:var(--tan-mute);
  /* it sits over his light chest, where tan-on-tan disappears - so it gets
     its own dark pill, which also makes the affordance obvious */
  cursor:pointer;padding:14px 26px;min-height:44px;border-radius:999px;
  background:rgba(18,14,10,.6);border:1px solid rgba(201,162,75,.3);
  -webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);
  text-shadow:0 2px 10px rgba(0,0,0,.9);
  animation:pgHint .35s ease-out forwards var(--d,0s),
            pgHintPulse 2.4s ease-in-out infinite calc(var(--d,0s) + .35s)}
.pg-hint:hover{color:var(--tan-soft);border-color:var(--gold)}

/* THE P.S. A small aside under the prompt, on phones only: that is where the
   gate is a squeeze, and a desktop visitor is already where it points. The
   prompt moves up by --ps-room to make space for it, and it arrives just
   after the prompt. */
.pg-ps{display:none}
@media (max-aspect-ratio:5/4) and (max-width:600px){
  .pg{--ps-room:30px}
  .pg-ps{display:block;position:absolute;z-index:8;left:50%;width:max-content;max-width:88vw;margin:0;
    top:calc(var(--caption-top) + .6 * var(--cap-fs) - 34px - var(--ps-room) + 44px + 9px);
    transform:translateX(-50%);opacity:0;pointer-events:none;text-align:center;
    font-family:var(--font-display);font-style:italic;font-size:13.5px;letter-spacing:.01em;
    color:var(--tan-mute);text-shadow:0 1px 10px rgba(0,0,0,.9),0 0 18px rgba(0,0,0,.7);
    animation:pgPs .6s ease-out forwards var(--d,0s)}
}
@keyframes pgPs{to{opacity:.8}}

/* env() for the notch and the home indicator - a device emulator has
   neither, so these only ever show up on real hardware. */
.pg-skip{position:absolute;z-index:9;
  top:calc(clamp(10px,2.4vh,22px) + env(safe-area-inset-top,0px));
  right:calc(clamp(10px,2.4vw,26px) + env(safe-area-inset-right,0px));
  font-family:var(--font-ui);font-size:10.5px;font-weight:500;letter-spacing:.24em;
  text-transform:uppercase;color:var(--tan-faint);
  background:none;border:0;cursor:pointer;padding:12px 16px;min-height:44px;
  transition:color .3s ease}
.pg-skip:hover{color:var(--tan-mute)}

/* THE MONOGRAM. .mono (site.css) draws it; here it only needs a place:
   centred in the sky above the prompt, which is where the gate has room.
   On a wide screen that room is the band between the top of the screen and
   the prompt (--pt, the prompt's own top, from .pg-hint), so the monogram is
   sized to that band and centred in it: large on a tall monitor, smaller on
   a short laptop, and never touching the prompt. On a phone the whole upper
   sky is free, and it simply sits in it. It fades in as the gate opens,
   before anything else speaks. */
.pg-mono{position:absolute;z-index:8;pointer-events:none;color:var(--gold);opacity:0;
  --pt:calc(var(--caption-top) + .6 * var(--cap-fs) - 34px);
  --mh:clamp(38px,calc(var(--pt) - 34px),84px);
  left:50%;translate:-50% 0;height:var(--mh);
  top:max(8px,calc((var(--pt) - var(--mh)) / 2));
  animation:pgMono calc(1.4s * var(--tempo)) ease-out forwards calc(.3s * var(--tempo))}
@keyframes pgMono{to{opacity:.9}}
/* a short laptop screen has the prompt almost at the top edge and no band
   above it to speak of, so there the monogram is a small crest in the corner
   opposite Skip instead */
@media (min-aspect-ratio:5/4) and (max-height:880px){
  .pg-mono{left:calc(clamp(14px,2.4vw,26px) + env(safe-area-inset-left,0px));translate:none;
    top:calc(clamp(12px,2.6vh,24px) + env(safe-area-inset-top,0px));height:clamp(44px,6vh,54px)}
}
@media (max-aspect-ratio:5/4){
  .pg-mono{left:50%;top:calc(9svh + env(safe-area-inset-top,0px));translate:-50% 0;
    height:auto;width:min(30vw,128px)}
}

.pg :focus-visible{outline:2px solid var(--gold);outline-offset:3px;border-radius:2px}

/* ---- ?pgdebug - crosshair on the nose point, for dialling in a new photo ---- */
.pg-cross{display:none}
.pg.is-debug .pg-cross{display:block;position:absolute;z-index:20;
  left:var(--nose-x);top:var(--nose-y);width:60px;height:60px;
  transform:translate(-50%,-50%);pointer-events:none}
.pg.is-debug .pg-cross::before,.pg.is-debug .pg-cross::after{content:"";position:absolute;
  background:var(--gold)}
.pg.is-debug .pg-cross::before{left:50%;top:0;width:1px;height:100%}
.pg.is-debug .pg-cross::after{top:50%;left:0;height:1px;width:100%}

/* ============================================================
   KEYFRAMES
   ============================================================ */
@keyframes pgApproach{
  /* He holds at distance for a beat, then closes in.

     The hold used to jolt into motion: CSS applies the timing function to
     each keyframe INTERVAL, so the move out of the hold restarted the ease
     from its steepest part and he lurched. The animation is linear now and
     the easing lives on the keyframe that starts the move, so he accelerates
     from a standstill.

     The stage is bottom-anchored and the origin is his feet, so scaling alone
     would leave the pair small at the BOTTOM of the screen. To sit them in
     the middle instead, their centre has to move to 50svh:
     translateY(calc(13% - 50svh)) at scale .26. Riding that back to zero as
     they grow is also what carries them down the frame, the way something
     coming toward you does. */
  0%      {opacity:0;transform:translateY(calc(13% - 50svh)) scale(.26);
           filter:blur(5px) brightness(.5)}
  6%      {opacity:1}
  13%     {transform:translateY(calc(13% - 50svh)) scale(.26);
           filter:blur(4.6px) brightness(.52);
           animation-timing-function:cubic-bezier(.34,0,.18,1)}
  74%     {filter:blur(0px) brightness(1)}
  100%    {transform:translateY(0) scale(1);filter:blur(0px) brightness(1)}
}
@keyframes pgHint  {to{opacity:1}}
@keyframes pgHintPulse{0%,100%{opacity:1}50%{opacity:.55}}
@keyframes pgHalo{
  0%,100%{opacity:.82;transform:translate(-50%,-50%) scale(.985) rotate(-.6deg)}
  50%    {opacity:1;  transform:translate(-50%,-53%) scale(1.02) rotate(.6deg)}}
@keyframes pgSway{
  0%,100%{transform:rotate(-1.15deg)}
  50%    {transform:rotate(1.15deg)}}
/* scaleY only: the roots stay put and the tips lift, like a slow breath */
@keyframes pgSpark{
  0%,72%,100%{opacity:0;transform:scale(.3) rotate(0deg)}
  80%        {opacity:1;transform:scale(1) rotate(22deg)}
  88%        {opacity:.25;transform:scale(.72) rotate(40deg)}}
@keyframes pgMote{
  0%     {opacity:0;   transform:translate(0,0) scale(.6)}
  25%    {opacity:.85}
  70%    {opacity:.5}
  100%   {opacity:0;   transform:translate(var(--mx,0),var(--my,-40%)) scale(1.1)}}

/* ---- the exit ring, fired from the nose as the gate leaves ---- */
.pg-exit{position:absolute;inset:0;border-radius:50%;opacity:0;
  border:1px solid rgba(201,162,75,.6)}
.pg.is-leaving .pg-exit{animation:pgExit .7s ease-out both}
.pg.is-leaving .pg-line,.pg.is-leaving .pg-hint,.pg.is-leaving .pg-ps,
.pg.is-leaving .pg-names,
.pg.is-leaving .pg-halo,.pg.is-leaving .pg-mote,
.pg.is-leaving .pg-skip{transition:opacity .3s ease;opacity:0;animation-play-state:paused}
@keyframes pgExit{from{opacity:.75;transform:scale(.3)} to{opacity:0;transform:scale(3.2)}}

/* ============================================================
   RESPONSIVE - on a phone Percy fills the width, so the bubble has nowhere
   to sit beside him and must go above his head.
   ============================================================ */
/* ---- The composition switches on ASPECT, not width. What decides whether a
        two-dog-wide wingspan fits is how much horizontal slack there is beside
        the pair, and that is an aspect question: a 1024x768 desktop has slack,
        a 768x1024 tablet does not, and a width breakpoint cannot tell them
        apart. Within each mode the ratios below are fixed, so the artwork can
        no longer drift away from the dogs as the window changes. ---- */
/* Narrow landscape: the wings box is wider than the window here, so the
   outermost sparkles sit outside it. Pulling their spread in is enough - this is about the wings being wide relative to the SCREEN, which is why it
   keys off width rather than aspect like the composition does. */
@media (min-aspect-ratio:5/4) and (max-width:1200px){
  .pg{--spark-spread:.62}
}

@media (max-aspect-ratio:5/4){
  /* ---- PORTRAIT ----
     The composition switches on aspect, not width: what decides whether a
     two-dog wingspan fits is the horizontal slack beside the pair, and a
     1024x768 desktop has it where a 768x1024 tablet does not. */

  .pg{--stage-w:min(96vw,88svh,760px)}

  /* No wings in portrait. A phone cannot hold a wingspan: stretched to fill
     the width they end up cut at both edges with their roots floating above
     the pair, and sized to fit whole they disappear behind the dogs. Shrinking
     the pair does buy a correct composition (measured: 0.80 left/right balance
     at 64vw against 0.23 at 96vw) but at the cost of small dogs on the one
     screen where the dogs matter most. The halo alone already says angel, so
     here it carries it and the dogs stay big. */
  /* Only part of each wing is meant to be visible here. A phone cannot hold
     a full wingspan, so rather than shrink them until they read as small
     wings, they stay large and steep and leave the frame through the top
     corners - a cropped shape reads as bigger than the frame, which is the
     truth of it. Their roots still sit behind the pair, so the position is
     honest even though the shape is cut. */
  .pg-wings{display:block;left:50%}
  .pg{--wing-span:260%;--wing-y:28%;--wing-rot:44deg;
      /* The right wing is nudged LEFT only - same height, same angle - so its
         root sits nearer Percy and it reads as his rather than as Jackson's.
         Per-wing rather than shared, because moving both would take the left
         one off the screen. Moving it down as well was a mistake: it lost the
         rising, cropped quality that makes these work. */
      /* -58 rather than a token nudge: measured against the sliver of
         background between Percy's ear and Jackson's, wing ink there goes
         from 650px to 1680px of the 1986 available. */
      --wing-rx:-58px;
      --spark-spread:.36}

  /* Bigger type than the shared clamp gives: on a phone the caption is the
     only text on screen and can carry the weight. It stays high rather than
     hugging the composition, because the prompt sits under it here and there
     is no room for it between the caption and the halo. */
  .pg{--cap-fs:clamp(1.22rem,5.4vw,1.9rem);
      /* One line now, so it sits just over the halo, near the dogs who are
         saying it, as it does on a desktop; never tucked under the prompt. */
      --prompt-floor:calc(clamp(10px,2.4vh,22px) + 50px + 44px + 14px + env(safe-area-inset-top,0px));
      --caption-top:max(calc(clamp(10px,2.4vh,22px) + 50px + env(safe-area-inset-top,0px)),
                        calc(var(--stage-top) - 0.105 * var(--stage-w) - 2.2 * var(--cap-fs)))}
  .pg-caption{width:min(90vw,26ch)}
  /* the names carry a bigger share of the width here than the shared 8.6vw
     gives them: on a phone they are the only thing on this half of the
     screen, and at 8.6vw they read as a caption rather than the payoff. */
  .pg-names{font-size:clamp(2.5rem,12vw,4rem);--names-track:.12em}

  /* The prompt follows the conversation rather than sitting at the foot of
     the screen, so it reads as the last thing said. It already waits for the
     final line, so the caption is settled by the time it appears. */
  /* nowrap: a box centred with left:50% only gets half the screen to wrap in,
     so it broke onto two lines with room to spare */
  /* up by --ps-room, which is where the P.S. goes */
  .pg-hint{top:calc(var(--caption-top) + .6 * var(--cap-fs) - 34px - var(--ps-room,0px));bottom:auto;
    font-size:10.5px;letter-spacing:.18em;padding:13px 20px;white-space:nowrap}

  /* cover on a 16:9 photo in a portrait window shows only about a quarter of
     its width, so the zoom comes off and it pans to the stretch that still
     reads as the venue - sunset behind the palm line, pool edge below. */
  .pg-photo::before{transform:none;background-position:44% 52%}
}


@media (max-width:900px){
  .pg-hint{font-size:10px;letter-spacing:.2em;padding:13px 18px}
}

/* ---- reduced motion: this file's selectors only.
        Percy, the bubble, the print and the hint show at once and static;
        the ripple and wave are motion by definition and are dropped. ---- */
@media (prefers-reduced-motion:reduce){
  .pg-figure{animation:none;opacity:1;transform:none}   /* transform-origin stays */
  /* With no motion there is no sequence, so the conversation cannot take
     turns in one slot - all three lines would show at once, on top of each
     other. They flow as a short block instead, which needs the caption to
     size to its content and sit at the top of the screen rather than a fixed
     distance above the halo. */
  /* the same place it takes with motion: under the prompt, over the halo */
  /* both lines at once here, so a line higher, but never above the prompt */
  .pg-caption{top:max(var(--prompt-floor,0px), calc(var(--caption-top) - 1.3 * var(--cap-fs)));
    min-height:0;display:flex;flex-direction:column;align-items:center;gap:.12em}
  .pg-line{position:static;animation:none;opacity:1;transform:none}
  /* with no sequence the lines and the prompt show together: the prompt goes
     back to the top, where there is room for it, and keeps its centring */
  .pg-hint{animation:none;opacity:1;transform:translateX(-50%);
    top:calc(clamp(10px,2.4vh,22px) + env(safe-area-inset-top,0px))}
  .pg-line span{animation:none;opacity:1;transform:none;filter:none;
    text-shadow:0 0 18px rgba(240,209,137,.30),0 0 44px rgba(201,162,75,.16),0 2px 18px rgba(0,0,0,.80)}
  /* the names still need their centring transform, so this one is not `none` */
  /* relative, not static: it has to return to normal flow AND stay the
     containing block for .pg-glints, which is absolutely positioned inside
     it. Static would hand the glints to .pg-caption and scatter them across
     the dialogue. left/top are reset because the flowed box must not take
     the absolute rule's 50% offset. */
  .pg-names{position:relative;left:auto;top:auto;
    animation:none;opacity:1;filter:none;
    letter-spacing:.03em;margin-right:0;transform:none;
    font-size:clamp(1.8rem,7vw,3.2rem);margin-top:.15em}
  .pg-glint{animation:none;opacity:.6;transform:translate(-50%,-50%) scale(1)}
  .pg-halo{animation:none;filter:var(--halo-glow)}
  .pg-halo-bloom{animation:none;opacity:1;filter:var(--bloom-blur)}
  .pg-halo-lift{animation:none;transform:none}
  .pg-wings{animation:none;filter:var(--wing-glow)}
  .pg-percy,.pg-jackson{animation:none;filter:var(--dog-shadow);transform:none}
  .pg-halo{animation:none;opacity:.9;transform:translate(-50%,-50%)}
  .pg-sway{animation:none;transform:none}
  .pg-draw{animation:none;stroke-dashoffset:0}
  .pg-spark{animation:none;opacity:.7}
  .pg-mono{animation:none;opacity:.9}
  .pg-mote{animation:none;opacity:.5}
  .pg-exit,.pg-boop-ring{display:none}
  /* the boop still sounds and still lets you in; only the squash goes */
  .pg-dog-wrap.is-booped{animation:none;transform:none}
  .pg,.pg.is-leaving{transition:none}
}

/* Landscape reduced motion is the one shape where the flowed caption does not
   fit: the halo rides highest there, leaving about 140px between the top of
   the screen and the top of the halo. Three lines plus the names at the
   animated sizes need 177px, so this block - and only this one - shrinks
   them to suit. Portrait has three times the room and keeps its own sizes. */
/* a phone has no room beside Skip, so the static prompt sits a step under it */
@media (prefers-reduced-motion:reduce) and (max-aspect-ratio:5/4){
  .pg-hint{top:calc(clamp(10px,2.4vh,22px) + 50px + env(safe-area-inset-top,0px))}
  .pg-ps{top:calc(clamp(10px,2.4vh,22px) + 50px + 44px + 10px + env(safe-area-inset-top,0px));animation:none;opacity:.8}
  /* the prompt takes the top of the sky here, so the monogram goes back to the corner */
  .pg-mono{left:calc(clamp(14px,2.4vw,26px) + env(safe-area-inset-left,0px));translate:none;width:auto;height:44px;
    top:calc(clamp(12px,2.6vh,24px) + env(safe-area-inset-top,0px))}
}
@media (prefers-reduced-motion:reduce) and (min-aspect-ratio:5/4){
  .pg{--cap-fs:clamp(.95rem,min(2.4vw,2.5vh),1.4rem)}
  .pg-names{font-size:clamp(1.5rem,5vw,2.5rem)}
}

/* ---- while the gate is up, the forest behind it is animating for nobody.
        Pausing costs one rule and resumes for free when the class goes. ---- */
.gate-armed .cs-gradient::before,.gate-armed .glow,.gate-armed .cs-rays,
.gate-armed .fog,.gate-armed .sway,.gate-armed .sway-hang,
.gate-armed .bloom-tw,.gate-armed .cs-frame{animation-play-state:paused}
