/* ==========================================================================
   CRIT — entrance.
   The site is already alive before ENTER: dust drifting, one dim light source,
   depth. The mark then emerges out of that darkness rather than loading on top
   of it. ENTER turns the mark into a portal.
   ========================================================================== */

.entrance {
  position: fixed;
  inset: 0;
  z-index: var(--z-entrance);
  display: grid;
  grid-template-rows: 1fr auto;
  place-items: center;
  background: transparent;
  isolation: isolate;
}

/* The entrance's own atmosphere lives behind the shared engine, so the space
   is continuous — entering is a change of light, not a change of page. */
.entrance__field {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}

/* The room's light is not a lamp hung over the mark — it is the air itself. A
   full-viewport wash keeps the whole frame alive, so the light never reads as a
   lit box floating in the void, and it breathes on its own clock. */
.entrance__ambient {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  background:
    radial-gradient(
      135% 105% at 50% 38%,
      rgba(170, 192, 234, 0.17) 0%,
      rgba(124, 150, 202, 0.06) 48%,
      rgba(92, 114, 162, 0) 78%
    ),
    radial-gradient(
      95% 75% at 50% 92%,
      rgba(118, 140, 190, 0.09) 0%,
      rgba(88, 106, 148, 0) 72%
    );
  transition: opacity 2800ms var(--ease-out-expo);
}

.entrance[data-stage='field'] .entrance__ambient { opacity: 0.55; }
.entrance[data-stage='mark'] .entrance__ambient,
.entrance[data-stage='settled'] .entrance__ambient { opacity: 1; }
.entrance[data-leaving='1'] .entrance__ambient { opacity: 0.6; transition-duration: 520ms; }

/* The ignition wash. When the bar completes the room itself answers — a single
   bloom of light centred on the mark that spills to every edge of the frame, so
   the CRIT "switching on" is an event in the room, not a lamp above the mark. */
.entrance__pulse {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(
    closest-side at 50% 46%,
    rgba(222, 234, 255, 0.5) 0%,
    rgba(184, 206, 248, 0.16) 44%,
    rgba(150, 176, 226, 0) 78%
  );
  will-change: opacity;
}

.entrance[data-ignite='1'] .entrance__pulse {
  animation: ignitePulse 1400ms var(--ease-out-expo) forwards;
}

@keyframes ignitePulse {
  0%   { opacity: 0;   }
  9%   { opacity: 0.9; }
  46%  { opacity: 0.35; }
  100% { opacity: 0;   }
}


.entrance__core {
  position: relative;
  display: grid;
  place-items: center;
  /* Capped by viewport height too, so a tall mark can never push the loader
     and the PRESS START prompt below the fold. */
  width: min(84vw, 900px, 82vh);
  padding: 0 var(--gutter);
  transform: translate3d(0, -1vh, 0);
}

/* ---------------------------------------------------------------- the mark */

.entrance__mark {
  position: relative;
  isolation: isolate;
  width: 100%;
  display: grid;
  place-items: center;
}

.entrance__bloom {
  position: absolute;
  inset: -22% -12%;
  z-index: 2;
  /* `?v` is a cache key: /assets is immutable for a year at the edge, so a
     re-feathered bloom must arrive under a new URL or returning visitors keep
     the old, edge-clipped copy. */
  background: url('/assets/brand/crit-mark-bloom.webp?v=2') center / contain no-repeat;
  opacity: 0;
  transform: scale(1.16);
  /* The bloom artwork is already soft, so the extra 14px blur only bought the
     compositor a full-surface filter pass each frame (measured change without it:
     0.9/255 mean, 1.8% of pixels). Screen over this near-black room reads as
     source-over to the eye, so the backdrop blend is dropped as well. */
  transition: opacity 2600ms var(--ease-out-expo), transform 3400ms var(--ease-out-expo);
  mix-blend-mode: normal;
  pointer-events: none;
}

.entrance__plate {
  position: absolute;
  inset: -14% -6%;
  z-index: 1;
  /* Same cache-key reason as the bloom above. */
  background: url('/assets/brand/crit-mark-plate.webp?v=2') center / contain no-repeat;
  opacity: 0;
  transform: scale(0.94);
  transition: opacity 3000ms var(--ease-out-expo), transform 3600ms var(--ease-out-expo);
  mix-blend-mode: normal;
  pointer-events: none;
}

.entrance__glyph {
  position: relative;
  z-index: 3;
  width: 100%;
  height: auto;
  opacity: 0;
  transform: translate3d(0, 14px, 0) scale(1.085);
  /* The mark reads as lit metal. The gain is a single static pass; the halo is
     the pre-baked bloom behind it. Nothing here animates `filter` or carries a
     drop-shadow, so the compositor never re-rasters the artwork per frame. */
  filter: brightness(1.3) contrast(1.06) saturate(1.04);
  transition:
    opacity 2400ms var(--ease-out-expo),
    transform 2600ms var(--ease-out-expo);
  will-change: transform, opacity;
  user-select: none;
  pointer-events: none;
}

/* A single light sweep crossing the metal — once, then never again. It is an
   overlay above the artwork, not a clipped window around it. */
.entrance__sweep {
  position: absolute;
  inset: 0;
  z-index: 2;
  overflow: hidden;
  opacity: 0;
  transition: opacity 220ms linear;
  pointer-events: none;
  /* No mask here: masking a layer whose child animates its transform forces a
     full-surface mask pass every frame on the software compositor (measured:
     p90 21.1ms -> 0.4ms without it). The band fades to nothing on every side by
     its own gradient, so there is no clipped edge to hide. */
}

.entrance__sweep::after {
  content: '';
  position: absolute;
  top: -22%;
  bottom: -22%;
  width: 46%;
  /* Soft edges come from the gradient itself — never a runtime blur or a mask:
     a blurred or masked moving layer is re-rastered every frame on a CPU
     compositor. A radial streak fades to nothing on all four sides, so the
     element's own box can never print a line across the mark. */
  background: radial-gradient(
    ellipse 26% 70% at 50% 50%,
    rgba(255, 255, 255, 0.55) 0%,
    rgba(255, 255, 255, 0.14) 38%,
    rgba(255, 255, 255, 0) 70%
  );
  transform: translate3d(-160%, 0, 0);
}

.entrance[data-sweep='1'] .entrance__sweep { opacity: 1; }
.entrance[data-sweep='1'] .entrance__sweep::after {
  animation: sweepCross 1500ms var(--ease-in-out-quart) forwards;
}

@keyframes sweepCross {
  from { transform: translate3d(-170%, 0, 0); }
  to   { transform: translate3d(360%, 0, 0); }
}

/* --------------------------------------------------------------- the light
   Ignition and exit are built entirely from transforms and opacity over
   pre-baked gradients and artwork — no animated filters, no drop-shadows, no
   clip-path. The compositor can run every one of these without touching the
   raster pipeline. */

/* A radial swell of light living behind the mark. It is invisible at rest and
   fires once when the archive finishes "loading". */
.entrance__ignite {
  position: absolute;
  inset: -46% -34%;
  z-index: 0;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    rgba(214, 226, 250, 0.85) 0%,
    rgba(170, 192, 234, 0.4) 38%,
    rgba(130, 154, 200, 0.12) 62%,
    rgba(110, 130, 176, 0) 78%
  );
  opacity: 0;
  transform: scale(0.5);
  pointer-events: none;
  will-change: transform, opacity;
}

.entrance[data-ignite='1'] .entrance__ignite {
  animation: igniteBurst 1700ms var(--ease-out-expo) forwards;
}

@keyframes igniteBurst {
  0%   { opacity: 0;    transform: scale(0.5); }
  10%  { opacity: 0.95; transform: scale(0.74); }
  42%  { opacity: 0.5;  transform: scale(1.12); }
  100% { opacity: 0;    transform: scale(2.15); }
}

/* A quiet aura that leans toward the visitor once START is under the pointer
   or keyboard focus — the room answering before it opens. */
.entrance__aura {
  position: absolute;
  inset: -30% -20%;
  z-index: 2;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    rgba(190, 208, 244, 0.5) 0%,
    rgba(150, 174, 220, 0.2) 46%,
    rgba(120, 142, 190, 0) 74%
  );
  opacity: 0;
  transform: scale(0.96);
  transition: opacity 560ms var(--ease-out-expo), transform 720ms var(--ease-out-expo);
  pointer-events: none;
}

.entrance:has(.entrance__enter:hover) .entrance__aura,
.entrance:has(.entrance__enter:focus-visible) .entrance__aura {
  opacity: 1;
  transform: scale(1.05);
}

/* The core flash. Fired on ignition and again as the door opens. */
.entrance__flash {
  position: absolute;
  inset: -12% -8%;
  z-index: 5;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    rgba(255, 255, 255, 0.92) 0%,
    rgba(226, 236, 255, 0.42) 34%,
    rgba(198, 214, 250, 0.1) 58%,
    rgba(198, 214, 250, 0) 74%
  );
  opacity: 0;
  pointer-events: none;
  will-change: opacity, transform;
}

.entrance[data-ignite='1'] .entrance__flash {
  animation: flashPulse 1100ms var(--ease-out-expo) forwards;
}

@keyframes flashPulse {
  0%   { opacity: 0;   transform: scale(0.86); }
  12%  { opacity: 0.9; transform: scale(1); }
  100% { opacity: 0;   transform: scale(1.24); }
}

/* An expanding hairline ring marking the ignition. */
.entrance__ring {
  position: absolute;
  inset: -2%;
  z-index: 6;
  border-radius: 50%;
  border: 1px solid rgba(198, 214, 248, 0.55);
  box-shadow: 0 0 26px rgba(170, 192, 234, 0.4);
  opacity: 0;
  transform: scale(0.55);
  pointer-events: none;
  will-change: transform, opacity;
}

.entrance[data-ignite='1'] .entrance__ring {
  animation: ringOut 1650ms var(--ease-out-expo) forwards;
}

@keyframes ringOut {
  0%   { opacity: 0;   transform: scale(0.55); }
  9%   { opacity: 0.85; }
  100% { opacity: 0;   transform: scale(1.55); }
}

/* A soft shaft of light that swells up through the mark as it opens — an
   elliptical glow rather than a drawn line, so it never reads as a rule. */
.entrance__streak {
  position: absolute;
  left: 50%;
  top: 8%;
  bottom: 8%;
  z-index: 4;
  width: 8vmin;
  transform: translateX(-50%) scaleY(0);
  transform-origin: 50% 50%;
  background: radial-gradient(
    ellipse 50% 48% at 50% 50%,
    rgba(244, 248, 255, 0.9) 0%,
    rgba(206, 222, 255, 0.42) 40%,
    rgba(190, 208, 255, 0) 74%
  );
  opacity: 0;
  pointer-events: none;
  will-change: transform, opacity;
}

.entrance[data-leaving='1'] .entrance__streak {
  animation: streakUp 620ms var(--ease-out-expo) forwards;
}

@keyframes streakUp {
  0%   { opacity: 0; transform: translateX(-50%) scaleY(0); }
  22%  { opacity: 1; transform: translateX(-50%) scaleY(1); }
  100% { opacity: 0; transform: translateX(-50%) scaleY(1.16); }
}

/* The full-frame bloom as the door opens. It sits above the iris so the light
   reads at the moment of entry instead of being swallowed by it. */
.entrance__outburst {
  position: absolute;
  inset: 0;
  z-index: 4;
  background: radial-gradient(
    closest-side at 50% 44%,
    rgba(255, 255, 255, 0.72) 0%,
    rgba(214, 228, 255, 0.3) 40%,
    rgba(190, 208, 246, 0) 72%
  );
  opacity: 0;
  pointer-events: none;
  will-change: opacity;
}

.entrance[data-leaving='1'] .entrance__outburst {
  animation: outburst 700ms var(--ease-out-expo) forwards;
}

@keyframes outburst {
  0%   { opacity: 0; }
  15%  { opacity: 1; }
  100% { opacity: 0; }
}

/* A shockwave of light that leaves the mark — once at ignition, again as the
   door opens. It is a soft radial ring, never a drawn circle, so it carries no
   edge of its own into the frame. */
.entrance__shock {
  position: absolute;
  left: 50%;
  top: 47%;
  width: 104vmin;
  height: 104vmin;
  z-index: 4;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    rgba(210, 226, 255, 0) 56%,
    rgba(228, 240, 255, 0.5) 74%,
    rgba(186, 208, 252, 0.14) 87%,
    rgba(186, 208, 252, 0) 100%
  );
  opacity: 0;
  transform: translate3d(-50%, -50%, 0) scale(0.3);
  pointer-events: none;
  will-change: transform, opacity;
}

.entrance[data-ignite='1'] .entrance__shock {
  animation: shockOut 1550ms var(--ease-out-expo) forwards;
}

/* The exit restates the same shockwave, harder and faster — the door throws the
   light outward. It needs its own keyframe name: an animation that has already
   finished does not restart just because its duration changed. */
.entrance[data-leaving='1'] .entrance__shock {
  animation: shockExit 680ms var(--ease-out-expo) forwards;
}

@keyframes shockExit {
  0%   { opacity: 0;    transform: translate3d(-50%, -50%, 0) scale(0.3); }
  10%  { opacity: 0.9; }
  100% { opacity: 0;    transform: translate3d(-50%, -50%, 0) scale(2.05); }
}

@keyframes shockOut {
  0%   { opacity: 0;    transform: translate3d(-50%, -50%, 0) scale(0.28); }
  11%  { opacity: 0.85; }
  100% { opacity: 0;    transform: translate3d(-50%, -50%, 0) scale(1.95); }
}

/* The lit rim of the aperture. As the iris closes, its edge glows — the light
   that opened the door stands at the threshold for a beat. */
.entrance__portal {
  position: absolute;
  left: 50%;
  top: 48%;
  width: 170vmax;
  height: 170vmax;
  z-index: 4;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    rgba(214, 230, 255, 0) 95.6%,
    rgba(232, 242, 255, 0.85) 98.4%,
    rgba(255, 255, 255, 0) 100%
  );
  opacity: 0;
  transform: translate3d(-50%, -50%, 0) scale(0);
  pointer-events: none;
  will-change: transform, opacity;
}

.entrance[data-leaving='1'] .entrance__portal {
  animation: portalRim 700ms var(--ease-in-tight) forwards;
}

@keyframes portalRim {
  0%   { opacity: 0;   transform: translate3d(-50%, -50%, 0) scale(0); }
  16%  { opacity: 0.9; }
  62%  { opacity: 0.75; }
  100% { opacity: 0;   transform: translate3d(-50%, -50%, 0) scale(1); }
}

/* -------------------------------------------------------------- wordmarks */

.entrance__type {
  margin-top: clamp(1.4rem, 3.4vh, 2.8rem);
  display: grid;
  justify-items: center;
  gap: clamp(0.5rem, 1.1vh, 0.9rem);
  text-align: center;
}

.entrance__title {
  font-family: var(--font-display);
  font-variation-settings: 'wdth' 84, 'wght' 500;
  font-size: clamp(1.1rem, 2.05vw, 1.9rem);
  letter-spacing: var(--track-max);
  text-indent: var(--track-max);
  text-transform: uppercase;
  color: var(--bone);
  opacity: 0;
  transform: translate3d(0, 8px, 0);
  transition: opacity 1500ms var(--ease-out-expo), transform 1700ms var(--ease-out-expo);
}

.entrance__sub {
  font-family: var(--font-meta);
  font-size: clamp(0.74rem, 0.86vw, 0.94rem);
  letter-spacing: var(--track-meta);
  text-transform: uppercase;
  color: var(--fg-soft);
  opacity: 0;
  transform: translate3d(0, 6px, 0);
  transition: opacity 1400ms var(--ease-out-expo) 160ms, transform 1600ms var(--ease-out-expo) 160ms;
}

/* ------------------------------------------------------------------- enter */

.entrance__gate {
  position: relative;
  padding-bottom: clamp(2.2rem, 7vh, 5rem);
  display: grid;
  justify-items: center;
  gap: clamp(0.9rem, 2vh, 1.5rem);
  width: 100%;
}

/* The loader and the PRESS START prompt share one cell so they cross-fade in
   place — the bar completes and the prompt is simply already there. */
.entrance__gate-inner {
  display: grid;
  place-items: center;
}

/* ---------------------------------------------------------------- loader
   An honest progress line: it tracks the stored dwell, so the archive is
   never "ready" before it actually is. */
.entrance__loader {
  grid-area: 1 / 1;
  width: min(64vw, 440px);
  display: grid;
  gap: 0.8rem;
  opacity: 0;
  transform: translate3d(0, 6px, 0);
  transition: opacity 900ms var(--ease-out-expo), transform 900ms var(--ease-out-expo);
}

.entrance[data-stage='field'] .entrance__loader,
.entrance[data-stage='mark'] .entrance__loader {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

.entrance[data-stage='settled'] .entrance__loader { opacity: 0; }

.entrance__loader-track {
  position: relative;
  height: 2px;
  background: rgba(178, 190, 214, 0.2);
  overflow: hidden;
  box-shadow: 0 0 18px rgba(150, 172, 214, 0.25);
}

.entrance__loader-fill {
  position: absolute;
  inset: 0;
  display: block;
  background: linear-gradient(90deg, rgba(150, 172, 214, 0.35), rgba(234, 240, 252, 0.96));
  transform: scaleX(0);
  transform-origin: left center;
  box-shadow: 0 0 18px rgba(206, 220, 252, 0.85);
}

.entrance__loader-meta {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
}

.entrance__loader-label,
.entrance__loader-pct {
  font-family: var(--font-meta);
  font-size: 0.78rem;
  letter-spacing: var(--track-meta);
  text-transform: uppercase;
}

.entrance__loader-label {
  color: var(--fg-soft);
  text-shadow: 0 0 16px rgba(178, 192, 220, 0.22);
}

.entrance__loader-pct {
  color: var(--fg-faint);
  font-variant-numeric: tabular-nums;
}

.entrance__enter {
  position: relative;
  grid-area: 1 / 1;
  display: grid;
  justify-items: center;
  gap: 0.55rem;
  padding: 0.7rem 1.6rem;
  opacity: 0;
  transform: translate3d(0, 6px, 0);
  pointer-events: none;
  transition: opacity 900ms var(--ease-out-expo), transform 900ms var(--ease-out-expo);
}

.entrance__enter-label {
  font-family: var(--font-display);
  font-variation-settings: 'wdth' 76, 'wght' 500;
  font-size: clamp(0.84rem, 0.98vw, 1.02rem);
  letter-spacing: var(--track-max);
  text-indent: var(--track-max);
  text-transform: uppercase;
  color: var(--bone);
  /* No rule, no underline: the wordmark and the prompt stand on their own. */
  text-shadow: 0 0 22px rgba(178, 196, 236, 0.28);
  transition: color var(--t-fast) var(--ease-out-expo), text-shadow var(--t-fast) var(--ease-out-expo);
}

.entrance__enter-alt {
  font-family: var(--font-meta);
  font-size: 0.8rem;
  letter-spacing: var(--track-meta);
  text-transform: uppercase;
  color: var(--fg-faint);
}

.entrance__enter:hover .entrance__enter-label,
.entrance__enter:focus-visible .entrance__enter-label {
  color: var(--white);
  text-shadow: 0 0 30px rgba(206, 222, 255, 0.5);
}

.entrance[data-locked='1'] .entrance__enter { pointer-events: none; }

/* Staged states — the engine advances these, never a timer-driven animation
   on a single long keyframe. That way the shortened returning-visit entrance
   is genuinely the same choreography, simply faster. */
.entrance[data-stage='field'] .entrance__bloom { opacity: 0.34; transform: scale(1.1); }
.entrance[data-stage='field'] .entrance__plate { opacity: 0.2; transform: scale(1); }

.entrance[data-stage='mark'] .entrance__glyph,
.entrance[data-stage='settled'] .entrance__glyph {
  opacity: 1;
  transform: translate3d(0, 0, 0) scale(1);
  /* The mark reads as lit metal: the original artwork, lifted and haloed. The
     source is fine line-art, so it needs real gain to read against the room.
     The gain is a single static pass; the halo itself is the pre-baked bloom
     behind it, never a per-frame drop-shadow. */
  filter: brightness(1.38) contrast(1.08) saturate(1.05);
}

.entrance[data-stage='mark'] .entrance__bloom { opacity: 0.5; transform: scale(1.04); }
.entrance[data-stage='mark'] .entrance__plate { opacity: 0.34; transform: scale(1); }

.entrance[data-stage='settled'] .entrance__bloom { opacity: 0.28; transform: scale(1.02); }
.entrance[data-stage='settled'] .entrance__plate { opacity: 0.18; transform: scale(1); }

/* ------------------------------------------------------ the mark is alive
   Once the visitor has arrived the mark never freezes: it breathes, its halo
   pulses, the plate drifts, and a light crosses the metal every few seconds.
   Under reduced motion the global rule collapses all of this to one frame. */
@keyframes critBreath {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50%      { transform: translate3d(0, 0, 0) scale(1.016); }
}
@keyframes critHalo {
  0%, 100% { opacity: 0.46; transform: scale(1); }
  50%      { opacity: 0.78; transform: scale(1.06); }
}
@keyframes critPlate {
  0%, 100% { transform: scale(1) rotate(-0.12deg); }
  50%      { transform: scale(1.02) rotate(0.12deg); }
}
@keyframes critSheen {
  0%        { transform: translate3d(-170%, 0, 0); opacity: 0; }
  3%        { opacity: 1; }
  15%       { transform: translate3d(360%, 0, 0); opacity: 1; }
  16%, 100% { transform: translate3d(360%, 0, 0); opacity: 0; }
}

.entrance[data-stage='settled'] .entrance__mark { animation: critBreath 7.2s var(--ease-out-soft) infinite; }
.entrance[data-stage='mark'] .entrance__bloom,
.entrance[data-stage='settled'] .entrance__bloom { animation: critHalo 5.6s var(--ease-out-soft) infinite; }
.entrance[data-stage='mark'] .entrance__plate,
.entrance[data-stage='settled'] .entrance__plate { animation: critPlate 8.4s var(--ease-out-soft) infinite; }
.entrance[data-stage='settled'] .entrance__sweep { opacity: 1; }
.entrance[data-stage='settled'] .entrance__sweep::after {
  animation: critSheen 9s linear infinite 1.6s;
}
.entrance[data-leaving='1'] .entrance__mark { animation: none; }

/* --------------------------------------------------------- the mark glints
   A loose constellation of light points around the lockup. Each one flares on
   its own slow clock, so the metal always reads as catching a shifting light
   rather than sitting as a flat picture. */
.entrance__sparkles {
  position: absolute;
  inset: -10% -5%;
  z-index: 3;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1600ms var(--ease-out-expo);
}
.entrance__sparkles i {
  position: absolute;
  width: var(--s, 2px);
  height: var(--s, 2px);
  border-radius: 50%;
  background: radial-gradient(circle, #ffffff 0%, #d6e4ff 44%, rgba(150, 184, 255, 0) 100%);
  box-shadow: 0 0 7px 1px rgba(188, 212, 255, 0.8);
  opacity: 0;
  transform: scale(0.35);
  animation: critSparkle var(--d, 4.4s) var(--ease-in-out-quart) infinite;
  animation-delay: var(--p, 0s);
}
@keyframes critSparkle {
  0%, 38%   { opacity: 0; transform: scale(0.3); }
  50%       { opacity: 1; transform: scale(1); }
  58%       { opacity: 0.25; transform: scale(0.7); }
  64%, 100% { opacity: 0; transform: scale(0.35); }
}
.entrance[data-stage='mark'] .entrance__sparkles,
.entrance[data-stage='settled'] .entrance__sparkles { opacity: 1; }
.entrance[data-leaving='1'] .entrance__sparkles { opacity: 0; }

/* ------------------------------------------------------- the ignition throw
   The burst proper: points of light at the centre of the mark, thrown outward
   once when the bar completes and again, wider, when the door opens. Every one
   is composited and runs on transform + opacity only. */
.entrance__sparks {
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
}
.entrance__sparks i {
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--sz, 3px);
  height: var(--sz, 3px);
  margin: 0;
  border-radius: 50%;
  background: radial-gradient(circle, #ffffff 0%, #cfe0ff 46%, rgba(150, 184, 255, 0) 100%);
  box-shadow: 0 0 8px 1px rgba(188, 212, 255, 0.85);
  opacity: 0;
  transform: translate3d(0, 0, 0) scale(0.2);
  will-change: transform, opacity;
}

.entrance[data-ignite='1'] .entrance__sparks i {
  animation: sparkIgnite 1150ms var(--ease-out-expo) forwards;
  animation-delay: var(--p, 0s);
}

@keyframes sparkIgnite {
  0%   { opacity: 0; transform: translate3d(0, 0, 0) scale(0.2); }
  10%  { opacity: 1; }
  100% { opacity: 0; transform: translate3d(var(--tx), var(--ty), 0) scale(1); }
}

/* The exit throws the same ring almost twice as far and brighter. */
.entrance[data-leaving='1'] .entrance__sparks i {
  animation: sparkExit 880ms var(--ease-out-expo) forwards;
  animation-delay: var(--p, 0s);
}

@keyframes sparkExit {
  0%   { opacity: 0; transform: translate3d(0, 0, 0) scale(0.3); }
  12%  { opacity: 1; }
  100% { opacity: 0; transform: translate3d(calc(var(--tx) * 1.95), calc(var(--ty) * 1.95), 0) scale(1.25); }
}

.entrance[data-stage='mark'] .entrance__title,
.entrance[data-stage='settled'] .entrance__title {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

.entrance[data-stage='mark'] .entrance__sub,
.entrance[data-stage='settled'] .entrance__sub { opacity: 1; transform: translate3d(0, 0, 0); }

.entrance[data-stage='settled'] .entrance__enter { opacity: 1; transform: translate3d(0, 0, 0); pointer-events: auto; }

/* ------------------------------------------------------------------ exit */

/* Pressing ENTER: the mark rushes the viewer, the room's light throws outward,
   the aperture's rim glows and closes, and the archive is uncovered from the
   same point — one continuous movement, not a cut. */
.entrance[data-leaving='1'] .entrance__core {
  /* The independent `scale` property composes with the core's vertical offset
     transform, so the push never fights the responsive -1vh/-3vh nudge. */
  scale: 1.09;
  transition: scale 660ms var(--ease-in-out-quart);
}

.entrance[data-leaving='1'] .entrance__glyph {
  transform: translate3d(0, 0, 0) scale(1.5);
  opacity: 0.45;
  transition: opacity 480ms var(--ease-out-expo),
    transform 620ms var(--ease-in-out-quart);
}

/* The halo and the plate take the same push. These are animations rather than
   transitions because the settled state is an infinite breathing animation,
   and only an animation can outrank a running animation. */
.entrance[data-leaving='1'] .entrance__bloom {
  animation: bloomLeave 700ms var(--ease-out-expo) forwards;
}

@keyframes bloomLeave {
  0%   { opacity: 0.5;  transform: scale(1.04); }
  16%  { opacity: 1;    transform: scale(1.2);  }
  100% { opacity: 0.78; transform: scale(1.3);  }
}

.entrance[data-leaving='1'] .entrance__plate {
  animation: plateLeave 700ms var(--ease-out-expo) forwards;
}

@keyframes plateLeave {
  0%   { opacity: 0.32; transform: scale(1);    }
  18%  { opacity: 0.66; transform: scale(1.08); }
  100% { opacity: 0.52; transform: scale(1.14); }
}

.entrance[data-leaving='1'] .entrance__title,
.entrance[data-leaving='1'] .entrance__sub,
.entrance[data-leaving='1'] .entrance__enter {
  opacity: 0;
  transform: translate3d(0, -6px, 0);
  transition-duration: 320ms;
}

.entrance__iris {
  position: absolute;
  left: 50%;
  top: 48%;
  width: 170vmax;
  height: 170vmax;
  z-index: 3;
  border-radius: 50%;
  background: var(--void);
  /* As the disc grows, its lit edge is what reads as the aperture closing. The
     shadow is part of the layer and scales with the transform — no extra pass. */
  box-shadow:
    0 0 0 1px rgba(206, 220, 250, 0.5),
    0 0 90px 10px rgba(150, 175, 220, 0.42);
  /* At rest the iris is fully open — a zero-scale disc, so it covers nothing.
     It closes with a transform, not a clip-path: a scaled layer is composited,
     whereas animating clip-path repaints the whole screen every frame. */
  transform: translate3d(-50%, -50%, 0) scale(0);
  will-change: transform;
  pointer-events: none;
}

.entrance[data-leaving='1'] .entrance__iris {
  animation: irisClose 700ms var(--ease-in-tight) forwards;
}

@keyframes irisClose {
  from { transform: translate3d(-50%, -50%, 0) scale(0); }
  to   { transform: translate3d(-50%, -50%, 0) scale(1); }
}

.entrance[data-done='1'] {
  pointer-events: none;
  opacity: 0;
  transition: opacity 260ms linear;
}

/* ------------------------------------------------------------ small print */

.entrance__foot {
  position: absolute;
  left: var(--edge);
  right: var(--edge);
  bottom: clamp(0.9rem, 2.6vh, 1.7rem);
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--gap);
  opacity: 0;
  transition: opacity 1200ms var(--ease-out-expo);
  pointer-events: none;
}

.entrance[data-stage='settled'] .entrance__foot { opacity: 1; }

.entrance__foot span { display: block; }

/* ---------------------------------------------------------- weak machines
   The choreography stays whole — only the least affordable ambient pass is
   traded away. Every layer here is already composited; this simply stops the
   recurring sheen from scheduling frames on the lightest tier. */
[data-tier='low'] .entrance[data-stage='settled'] .entrance__sweep::after {
  animation: none;
}
