/* ==========================================================================
   Rykore Games — holding page for THIS SHOULD GO WELL
   --------------------------------------------------------------------------
   1. Tokens (colours, type, intro timeline)
   2. Base
   3. Stage (rig, light wash, floor, grain)
   4. Title card
   5. Footer
   6. Pre-show (the studio in standby the page opens on, with JavaScript)
   7. Intro timeline (only runs when <html> has the "intro" class)
   8. Reduced motion, forced colours, print
   ========================================================================== */


/* 1. Tokens ---------------------------------------------------------------- */

:root {
  color-scheme: dark;

  /* Ink: the studio in the dark. */
  --ink-0: #09060b;
  --ink-1: #130b15;
  --ink-2: #1d1121;

  /* Cream: tungsten-lit paper. */
  --cream: #f5ecdc;
  --cream-80: rgba(245, 236, 220, 0.8);
  --cream-65: rgba(245, 236, 220, 0.65);
  --cream-45: rgba(245, 236, 220, 0.45);
  --cream-25: rgba(245, 236, 220, 0.25);
  --cream-15: rgba(245, 236, 220, 0.15);

  /* Amber: the lights. Red: the tally. */
  --amber: #e9b45c;
  --amber-deep: #c98a2a;
  --tally: #ff4b3e;
  --tally-glow: rgba(255, 75, 62, 0.55);

  /* System stacks only: no font files, nothing external.
     Display: a firm, slightly condensed grotesque where the platform has one
     (Bahnschrift on Windows, Avenir Next Condensed on Apple), otherwise the
     heaviest weight of the system face. */
  --font-display: "Bahnschrift", "Avenir Next Condensed", "Helvetica Neue", "Segoe UI",
    system-ui, -apple-system, Roboto, Arial, sans-serif;
  --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-serif: "Iowan Old Style", Georgia, "Palatino Linotype", Charter, Cambria,
    "Times New Roman", serif;

  /* Intro timeline. Every cue is an offset from the moment the page starts,
     and marks when that element's animation begins. script.js reads the same
     variables to place the sound and to run the loose fixture's physics, so
     retiming here retimes picture, physics and sound together.
     The one exception is --t-snag: when the safety cable catches the falling
     fixture is decided by the physics (it falls for about 0.23s after
     --t-drop), so script.js writes the real value onto <html> before each run.
     The value here is only what it comes out as with the settings shipped. */
  --t-tally: 0.25s;      /* standby LED on the rig wakes             */
  --t-click: 0.75s;      /* contactor: flash, truss draws outward     */
  --t-lights: 0.95s;     /* fixtures fade up, centre outward          */
  --t-wash: 1.1s;        /* stage warms                               */
  --t-ident: 1.3s;       /* RYKORE GAMES studio card                  */
  --t-ident-out: 2s;     /* studio card racks out of focus            */
  --t-title: 2.25s;      /* THIS SHOULD lands                         */
  --t-land: 2.43s;       /* GO WELL lands                             */
  --t-flicker: 2.8s;     /* centre lamp hesitates, chord wavers       */
  --t-mark: 2.85s;       /* small studio wordmark settles above       */
  --t-tagline: 2.95s;    /* Different games. Same idiots.             */
  --t-desc: 3.2s;        /* description                               */
  --t-warn: 3.4s;        /* centre fixture sputters, beam drops out, rattles */
  --t-foot: 3.55s;       /* footer                                    */
  --t-slip: 3.85s;       /* its clamp slips: lurch, stem tears        */
  --t-drop: 4.2s;        /* clamp lets go                             */
  --t-snag: 4.46s;       /* safety cable catches it (set by script.js) */
  --t-status: 6.25s;     /* IN DEVELOPMENT, after an awkward silence  */
  --t-punch: 6.8s;       /* We'll fix that.                           */
  --t-tally-off: 7.2s;   /* standby LED goes dark; at rest by about 8s */

  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-land: cubic-bezier(0.2, 0.85, 0.25, 1);
  --ease-in: cubic-bezier(0.55, 0, 0.85, 0.35);
}


/* 2. Base ------------------------------------------------------------------ */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  background: var(--ink-0);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  font-family: var(--font-sans);
  font-size: 100%;
  line-height: 1.5;
  color: var(--cream);
  background: var(--ink-0);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

::selection {
  background: var(--amber-deep);
  color: var(--ink-0);
}


/* 3. Stage ----------------------------------------------------------------- */

.stage {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  background: var(--ink-0);
}

.stage > * {
  position: absolute;
  inset: 0;
}

/* Aubergine depth that only shows once the lights are on. */
.stage__base {
  background:
    radial-gradient(ellipse 90% 70% at 50% 0%, var(--ink-2), transparent 70%),
    linear-gradient(180deg, var(--ink-1) 0%, var(--ink-0) 100%);
}

/* Warm wash from the rig. */
.stage__wash {
  opacity: 0.85;
  background:
    radial-gradient(ellipse 75% 55% at 50% -6%, rgba(233, 180, 92, 0.34), rgba(233, 180, 92, 0) 70%);
}

/* The rig is one SVG that always covers the viewport; the truss stays pinned
   to the top and the sides crop on narrow screens. */
.stage__rig {
  display: block;
  width: 100%;
  height: 100%;
}

/* Pool of light on the stage floor. */
.stage__floor {
  top: auto;
  height: 42vh;
  background: radial-gradient(ellipse 60% 70% at 50% 100%, rgba(233, 180, 92, 0.11), transparent 70%);
}

.stage__vignette {
  background: radial-gradient(ellipse 85% 80% at 50% 42%, transparent 50%, rgba(4, 2, 5, 0.72) 100%);
}

/* Fine static grain: keeps the gradients from banding and adds a little film. */
.stage__grain {
  opacity: 0.055;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

/* Rig parts (inline SVG in index.html). Resting state: everything lit. */
.rig-rail {
  stroke: var(--cream);
  stroke-opacity: 0.4;
  stroke-width: 3;
  stroke-linecap: round;
}

.rig-rail--lower {
  stroke-opacity: 0.2;
  stroke-width: 2;
}

.rig-relay {
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
}

.lamp-glow {
  transform-box: fill-box;
  transform-origin: center;
}

.lamp-flash {
  opacity: 0;
}

.beam {
  filter: url(#f-soft);
}

/* Standby LED: only ever lit during the intro. */
.tally {
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
}

/* The loose fixture (centre). Resting state, which is also what the page shows
   without JavaScript, with reduced motion, and when the intro ends: off its
   clamp, hanging 80 units down the safety cable from its wrap on the pipe,
   turned until its weight sits under the cable's eye (clipped round the
   yoke's right arm at 31,36), which works out at -57.17deg. The clamp is left twisted on
   the pipe. script.js animates the same three transforms and ends on exactly
   these values (its REST constant); change them together.
   All transforms are in the fixture's own units, about its origin (the pipe). */
.safety,
.clamp,
.loose,
.spark {
  transform-box: view-box;
  transform-origin: 0 0;
}

.safety {
  stroke: var(--cream);
  stroke-opacity: 0.6;
  stroke-width: 1.8;
  stroke-linecap: round;
  /* From its wrap at (14, 2), 80 units drawn, stretched by the fixture's
     weight to 80.43. */
  transform: translate(14px, 2px) scale(1, 1.0054);
}

.safety-wrap {
  fill: #1c1120;
  stroke: var(--cream);
  stroke-opacity: 0.35;
}

.clamp {
  transform: rotate(-38deg);
}

.loose {
  transform: translate(14px, 82.43px) rotate(-57.17deg) translate(-31px, -36px);
}

/* Sparks are created by script.js, and only ever seen mid-flight. */
.spark {
  opacity: 0;
}

/* Each streak is a unit line stretched along its flight, so its strokes are
   kept out of the stretch (and measured in screen pixels). */
.spark__core,
.spark__halo {
  vector-effect: non-scaling-stroke;
  stroke-linecap: round;
}

.spark__core {
  stroke: #fff4d8;
  stroke-width: 1.4;
}

.spark__halo {
  stroke: #f4b85a;
  stroke-opacity: 0.4;
  stroke-width: 4;
}


/* 4. Title card ------------------------------------------------------------ */

.card {
  position: relative;
  z-index: 1;
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100%;
  max-width: 64rem;
  margin: 0 auto;
  /* Top padding clears the fixtures: their bottom edge sits at 140/900 of the
     rig's viewBox, and the rig scales with max(width/1600, height/900).
     Vertical rhythm below is viewport-height aware so short laptop screens
     still hold the whole card without scrolling. */
  padding: calc(max(8.75vw, 15.5vh) + 1.75rem) 1.5rem clamp(1.25rem, 3vh, 2.5rem);
  padding-top: calc(max(8.75vw, 15.5svh) + 1.75rem);
  text-align: center;
}

/* Studio wordmark: small brass plate above the title. */
.mark {
  display: flex;
  align-items: center;
  gap: 1.1rem;
  margin: 0 0 clamp(1.1rem, 3vh, 2.6rem);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.8rem;
  letter-spacing: 0.42em;
  text-indent: 0.42em;
  text-transform: uppercase;
  color: var(--amber);
}

.mark::before,
.mark::after {
  content: "";
  flex: 0 0 2.25rem;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--amber-deep));
  opacity: 0.8;
}

.mark::after {
  background: linear-gradient(270deg, transparent, var(--amber-deep));
}

.title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 800;
  font-stretch: 87.5%;
  font-size: clamp(2.2rem, min(11.2vw, 13.5vh), 7.25rem);
  line-height: 0.9;
  letter-spacing: 0;
  text-transform: uppercase;
  color: var(--cream);
  text-shadow: 0 2px 36px rgba(233, 180, 92, 0.16);
}

.title .l {
  display: block;
}

.tagline {
  margin: clamp(0.9rem, 2.2vh, 1.75rem) 0 0;
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.15rem, 0.95rem + 1.4vw, 2rem);
  line-height: 1.25;
  letter-spacing: 0.005em;
  color: var(--cream);
  opacity: 0.94;
}

.rule {
  width: min(16rem, 42%);
  height: 1px;
  margin: clamp(1.25rem, 3.2vh, 2.4rem) auto 0;
  background: linear-gradient(90deg, transparent, var(--cream-25) 30%, var(--cream-25) 70%, transparent);
  transform-origin: center;
}

.desc {
  max-width: 46ch;
  margin: clamp(1rem, 2vh, 1.35rem) 0 0;
  font-size: clamp(0.95rem, 0.9rem + 0.25vw, 1.08rem);
  line-height: 1.55;
  letter-spacing: 0.01em;
  text-wrap: balance;
  color: var(--cream-80);
}

/* IN DEVELOPMENT: an ON AIR plate, minus the drama. */
.status {
  position: relative;
  display: inline-flex;
  align-items: center;
  margin: clamp(1.1rem, 2.4vh, 1.6rem) 0 0;
  padding: 0.66em 1.15em 0.6em;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.74rem;
  line-height: 1;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: var(--cream);
}

.status::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border: 1px solid var(--cream-15);
  border-radius: 4px;
  background: rgba(6, 3, 8, 0.4);
}

.status__label {
  /* Compensate the trailing tracking so the text sits centred in the plate. */
  margin-right: -0.32em;
}

/* The punchline: the plate's dry footnote, in the tagline's voice, about the
   light hanging off the rig. */
.punch {
  margin: clamp(0.6rem, 1.4vh, 0.85rem) 0 0;
  font-family: var(--font-serif);
  font-style: italic;
  font-size: clamp(1rem, 0.94rem + 0.3vw, 1.15rem);
  line-height: 1.3;
  letter-spacing: 0.005em;
  color: var(--cream-80);
}


/* 5. Footer ---------------------------------------------------------------- */

.foot {
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.4rem 1.5rem;
  padding: 0.9rem 1.5rem 1.15rem;
  padding-bottom: max(1.15rem, env(safe-area-inset-bottom));
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 0.68rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--cream-45);
}

.foot small {
  font-size: inherit;
}

.foot__controls {
  display: flex;
  align-items: center;
  gap: 1.35rem;
  margin-left: auto;
}

.control {
  display: none; /* shown once JavaScript is confirmed (html.js) */
  align-items: center;
  gap: 0.55em;
  margin: 0;
  padding: 0.45em 0.2em;
  border: 0;
  border-radius: 2px;
  background: none;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
  cursor: pointer;
  transition: color 0.2s ease;
}

html.js .control {
  display: inline-flex;
}

.control[hidden] {
  display: none;
}

.control svg {
  width: 0.95em;
  height: 0.95em;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.control:hover {
  color: var(--cream);
}

.control:focus-visible {
  outline: 2px solid var(--amber);
  outline-offset: 4px;
  color: var(--cream);
}

/* Sound toggle: the label and icon always state the current setting. */
.sound .sound__off,
.sound.is-off .sound__on {
  display: none;
}

.sound.is-off .sound__off {
  display: initial;
}

.sound.is-off {
  color: var(--cream-25);
}

.sound.is-off:hover,
.sound.is-off:focus-visible {
  color: var(--cream-65);
}

/* OWNER QA HOTSPOT — remove before commercial launch (with its element in
   index.html and its handler in script.js). An invisible tap target that
   opens the owner's QA lab; nothing about it may show. It sits bottom-middle
   in empty space, clear of the footer's text and controls in every state:
   on narrow screens, where the footer's controls fill its middle once the
   show has played, just above the footer (dipping only into its empty top
   padding); on wider ones, in the middle of the footer row. */
.owner-qa-hotspot {
  display: none; /* it only does anything with JavaScript (html.js) */
  position: absolute;
  left: 50%;
  bottom: calc(100% - 0.5rem);
  width: 44px;
  height: 40px;
  margin-left: -22px;
  cursor: default;
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  touch-action: manipulation;
}

html.js .owner-qa-hotspot {
  display: block;
}

@media (min-width: 560px) {
  .owner-qa-hotspot {
    top: 50%;
    bottom: auto;
    height: 44px;
    margin-top: -22px;
  }
}


/* Short laptop viewports: keep the footer on screen. */
@media (max-height: 700px) {
  .card {
    padding-bottom: 1rem;
  }

  .foot {
    padding-top: 0.5rem;
    padding-bottom: max(0.7rem, env(safe-area-inset-bottom));
  }
}


/* 6. Pre-show -------------------------------------------------------------- */

/* With JavaScript the page opens on the same stage in standby
   (html.in-preshow; .preshow is the layer): the rig unlit, drawn on the stage
   rig's own geometry, one lamp left warm, the standby light blinking where
   the tally will wake, haze, the studio's name where the intro's studio card
   will appear, a power box on the truss, and one light misbehaving: the light
   that fails in the intro (script.js puts the fault on it). Behind it the
   intro is already built, held on its first frame (html.held). The visitor's
   first touch on the light, the name or the power switch (html.going) starts
   the show: the stage answers for a moment, then hands over (html.leaving) as
   the intro and its sound start together from the top, the real rig powering
   up over this standby copy of itself, which goes as each part of the real
   one comes on. Without JavaScript none of this shows. */
.preshow {
  display: none;
}

html.in-preshow .preshow,
html.leaving .preshow {
  position: fixed;
  inset: 0;
  z-index: 3;
  display: block;
  overflow: hidden;
  pointer-events: none;
  container-type: size;
  /* Where the misbehaving light hangs, in rig units, and its ring from the
     centre (0 or 1), which is when its real lamp comes on. Set by script.js. */
  --gx: 620;
  --gi: 1;
}

/* Held on the intro's first frame: every intro animation paused at time
   zero. The pre-show's own animations keep playing. */
html.held *,
html.held *::before,
html.held *::after {
  animation-play-state: paused !important;
}

html.held .preshow *,
html.held .preshow *::before,
html.held .preshow *::after {
  animation-play-state: running !important;
}

/* The standby rig covers the layer exactly as the stage rig covers the
   stage, so every part of it sits on its real counterpart. */
.preshow__rig {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

/* Haze, with a little light left in it: a warm spill under the rig and a
   pool on the floor. */
.preshow__air {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background:
    radial-gradient(ellipse 70% 34% at 50% -4%, rgba(233, 180, 92, 0.08), transparent 70%),
    radial-gradient(ellipse 60% 40% at 50% 106%, rgba(214, 196, 170, 0.1), transparent 70%);
}

.preshow__air::before {
  content: "";
  position: absolute;
  inset: -12%;
  background:
    radial-gradient(ellipse 34% 24% at 34% 58%, rgba(245, 236, 220, 0.06), transparent 70%),
    radial-gradient(ellipse 38% 22% at 68% 50%, rgba(245, 236, 220, 0.055), transparent 70%),
    radial-gradient(ellipse 30% 18% at 52% 80%, rgba(245, 236, 220, 0.045), transparent 70%);
}

/* The rig, unlit: outlines only, just readable. */
.sb-truss line {
  stroke: var(--cream);
  stroke-opacity: 0.17;
  stroke-width: 3;
  stroke-linecap: round;
}

.sb-truss .sb-truss__lower {
  stroke-opacity: 0.09;
  stroke-width: 2;
}

.sb-truss rect {
  opacity: 0.3;
}

.sb-fixture {
  fill: none;
  stroke: var(--cream);
  stroke-opacity: 0.2;
  stroke-width: 1.1;
  transition: stroke-opacity 0.25s ease;
}

.sb-fixture.is-near {
  stroke-opacity: 0.45;
}

/* One lamp left on preheat: a faint warmth, nothing more. */
.sb-warm__glow {
  opacity: 0.2;
}

.sb-warm__lens {
  opacity: 0.12;
}

.sb-led {
  transform-box: fill-box;
  transform-origin: center;
}

/* The misbehaving light: an ember that keeps trying to take. */
.sb-glitch__beam {
  opacity: 0;
  filter: url(#f-soft);
}

.sb-glitch__glow {
  opacity: 0.07;
  transform-box: fill-box;
  transform-origin: center;
}

.sb-glitch__lens {
  opacity: 0.16;
}

.sb-glitch__beam,
.sb-glitch__glow,
.sb-glitch__lens {
  transition: opacity 0.25s ease;
}

.sb-glitch__flash {
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
}

/* Near it (hover, focus): it steadies and warms, as if about to take. */
.sb-glitch.is-near .sb-glitch__lamp,
.sb-glitch.is-near .sb-glitch__lens,
.sb-glitch.is-near .sb-glitch__glow,
.sb-glitch.is-near .sb-glitch__beam {
  animation: none;
}

.sb-glitch.is-near .sb-glitch__lens {
  opacity: 0.8;
}

.sb-glitch.is-near .sb-glitch__glow {
  opacity: 0.5;
}

.sb-glitch.is-near .sb-glitch__beam {
  opacity: 0.22;
}

/* Pressed: it catches with a flash and a spit of sparks (script.js), and
   stays on. */
.sb-glitch.is-press .sb-glitch__lens {
  opacity: 1;
}

.sb-glitch.is-hit .sb-glitch__lamp {
  animation: none;
}

.sb-glitch.is-hit .sb-glitch__lens {
  opacity: 1;
  animation: none;
}

.sb-glitch.is-hit .sb-glitch__glow {
  opacity: 0.9;
  animation: bloom 0.6s var(--ease-out) both;
}

.sb-glitch.is-hit .sb-glitch__beam {
  opacity: 0.75;
  animation: take 0.5s linear both;
}

.sb-glitch.is-hit .sb-glitch__flash {
  animation: pop 0.4s ease-out both;
}

/* The power box: a rotary isolator on a box under the truss. Off. */
.sb-box__feed {
  fill: none;
  stroke: var(--cream);
  stroke-opacity: 0.14;
  stroke-width: 1.4;
}

.sb-box__straps {
  fill: none;
  stroke: var(--cream);
  stroke-opacity: 0.24;
  stroke-width: 1.6;
}

.sb-box__body {
  stroke: var(--cream);
  stroke-opacity: 0.32;
  transition: stroke-opacity 0.25s ease;
}

.sb-box__plate {
  fill: #0f0810;
  stroke: var(--cream);
  stroke-opacity: 0.14;
}

.sb-box__bolt {
  fill: var(--amber);
  opacity: 0.6;
}

.sb-box__pilot {
  fill: #6b4a24;
}

.sb-box__halo {
  fill: var(--amber);
  opacity: 0;
}

.sb-box__arc {
  fill: #eef3ff;
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
}

.sb-box__switch {
  fill: #1d1320;
  stroke: var(--cream);
  stroke-opacity: 0.5;
  stroke-width: 1;
  transform-box: fill-box;
  transform-origin: center;
  transform: rotate(-90deg);
}

.sb-box__switch rect {
  fill: #2c1f2d;
}

.sb-box__notch {
  fill: var(--cream);
  fill-opacity: 0.7;
  stroke: none;
}

.sb-box.is-near .sb-box__body {
  stroke-opacity: 0.62;
}

.sb-box.is-near .sb-box__switch {
  animation: none;
  stroke-opacity: 1;
}

.sb-box.is-near .sb-box__switch rect {
  fill: #3d2b3e;
}

.sb-box.is-near .sb-box__bolt {
  opacity: 0.95;
}

.sb-box.is-near .sb-box__pilot {
  fill: #9a6c34;
}

/* Thrown: the handle snaps round to ON, a flash inside, the pilot lights. */
.sb-box.is-on .sb-box__switch {
  transform: none;
  stroke-opacity: 0.9;
  animation: throw 0.22s var(--ease-out) both;
}

.sb-box.is-on .sb-box__arc {
  animation: arc 0.3s ease-out both;
}

.sb-box.is-on .sb-box__pilot {
  fill: #ffd28a;
}

.sb-box.is-on .sb-box__halo {
  opacity: 0.55;
}

/* The studio's name on the set: exactly where the intro's studio card
   (.ident) will appear, in the same type, dim, waiting to be lit. */
.preshow__name {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 1.5rem 20vh;
}

/* Anything the visitor can touch: scenery, not buttons to look at. */
.target {
  position: absolute;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  pointer-events: auto;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

.target:focus {
  outline: none;
}

.target:focus-visible {
  outline: 2px solid var(--amber);
  outline-offset: 4px;
  border-radius: 12px;
}

html.going .target {
  pointer-events: none;
  cursor: default;
}

/* The light and the power switch sit on the rig: --s is one rig unit in
   pixels (the rig scales to cover the layer). */
.target--light,
.target--power {
  --s: max(calc(100cqw / 1600), calc(100cqh / 900));
  transform: translate(-50%, -50%);
}

.target--light {
  left: calc(50cqw + (var(--gx) - 800) * var(--s));
  top: calc(83 * var(--s));
  width: max(56px, calc(80 * var(--s)));
  height: max(64px, calc(100 * var(--s)));
}

.target--power {
  left: calc(50cqw + (890 - 800) * var(--s));
  top: calc(93 * var(--s));
  width: max(48px, calc(54 * var(--s)));
  height: max(56px, calc(66 * var(--s)));
}

/* The name: the .ident's type, but a button, dim until it is lit. */
.target--name {
  position: static;
  display: flex;
  align-items: center;
  padding: 0.7em 0.5em;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1rem, 0.8rem + 1.5vw, 1.95rem);
  line-height: 1.5;
  letter-spacing: 0.5em;
  text-indent: 0.5em;
  text-transform: uppercase;
  white-space: nowrap;
  color: rgba(245, 236, 220, 0.42);
  transition: color 0.3s ease, text-shadow 0.3s ease;
}

.target--name::before,
.target--name::after {
  content: "";
  flex: 0 0 clamp(1.25rem, 5vw, 4.5rem);
  height: 1px;
  margin-right: 1em;
  background: linear-gradient(90deg, transparent, var(--amber-deep));
  opacity: 0.5;
  transition: opacity 0.3s ease;
}

.target--name::after {
  margin-right: 0;
  margin-left: 1em;
  background: linear-gradient(270deg, transparent, var(--amber-deep));
}

@media (max-width: 400px) {
  .target--name {
    letter-spacing: 0.36em;
    text-indent: 0.36em;
  }
}

.target--name:hover,
.target--name:focus-visible {
  color: rgba(245, 236, 220, 0.8);
  text-shadow: 0 0 18px rgba(233, 180, 92, 0.3);
}

.target--name:hover::before,
.target--name:hover::after,
.target--name:focus-visible::before,
.target--name:focus-visible::after {
  opacity: 0.95;
}

.target--name:focus-visible {
  outline-offset: 2px;
}

/* Lit: the house catches it, and it becomes the studio card. */
.target--name.is-lit {
  color: var(--cream);
  text-shadow: 0 0 14px rgba(233, 180, 92, 0.5), 0 0 40px rgba(233, 180, 92, 0.26);
  animation: wake 0.55s ease-out both;
}

.target--name.is-lit::before,
.target--name.is-lit::after {
  opacity: 1;
}

/* The fallback, for a visitor who has found nothing to touch: CLICK HERE,
   just under the name, in the studio plate's small warm type, with no box.
   Hidden (and out of the tab order) until script.js shows it, about ten
   seconds into the pre-show; it fades up, then breathes. Gone once the show
   starts. Its top is the name's bottom edge: the name is centred in
   .preshow__name's content box (50% less half its 20vh bottom padding) and
   is 2.9em of its own size tall (see .target--name), so these follow it. */
.target--cue {
  left: 50%;
  top: calc(50% - 10vh + 1.45 * clamp(1rem, 0.8rem + 1.5vw, 1.95rem));
  transform: translateX(-50%);
  padding: 1.05em 1.3em; /* a 44px-tall touch target */
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.78rem;
  line-height: 1.5;
  letter-spacing: 0.42em;
  text-indent: 0.42em;
  text-transform: uppercase;
  white-space: nowrap;
  color: rgba(233, 180, 92, 0.78);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.35s ease-in, visibility 0s linear 0.35s, color 0.3s ease, text-shadow 0.3s ease;
}

.target--cue.is-shown {
  opacity: 1;
  visibility: visible;
  transition: opacity 1.8s ease-out, visibility 0s, color 0.3s ease, text-shadow 0.3s ease;
}

html.in-preshow .target--cue.is-shown {
  animation: cue-breathe 3.6s ease-in-out 1.8s infinite;
}

html.in-preshow .target--cue.is-shown:hover,
html.in-preshow .target--cue.is-shown:focus-visible {
  animation: none;
  color: var(--cream);
  text-shadow: 0 0 16px rgba(233, 180, 92, 0.5);
}

@keyframes cue-breathe {
  0%, 100% { color: rgba(233, 180, 92, 0.78); text-shadow: 0 0 0 rgba(233, 180, 92, 0); }
  50%      { color: rgba(247, 214, 150, 0.98); text-shadow: 0 0 16px rgba(233, 180, 92, 0.42); }
}

/* The stage waits: the standby light blinks, the haze drifts, the warm lamp
   breathes, the misbehaving light keeps trying, now and then a glint on the
   power switch. Staggered, so nothing moves in step. */
html.in-preshow .preshow__air {
  animation: fade-in 1.4s ease-out both;
}

html.in-preshow .preshow__air::before {
  animation: haze 14s ease-in-out infinite alternate;
}

html.in-preshow .sb-truss,
html.in-preshow .sb-fixtures,
html.in-preshow .sb-box {
  animation: fade-in 1s ease-out 0.2s both;
}

html.in-preshow .sb-led {
  animation:
    fade-in 0.5s ease-out 0.3s both,
    standby 2.4s ease-in-out 1.6s infinite;
}

html.in-preshow .sb-warm {
  animation: fade-in 1.2s ease-out 0.6s both;
}

html.in-preshow .sb-warm__glow {
  animation: warm 6.8s ease-in-out 2.2s infinite;
}

html.in-preshow .sb-glitch {
  animation: fade-in 0.8s ease-out 0.8s both;
}

html.in-preshow .sb-glitch__lamp {
  animation: sag 7.7s linear 2.6s infinite;
}

html.in-preshow .sb-glitch__lens {
  animation: glitch-lens 5.2s linear -1.2s infinite;
}

html.in-preshow .sb-glitch__glow {
  animation: glitch-glow 5.2s linear -1.2s infinite;
}

html.in-preshow .sb-glitch__beam {
  animation: glitch-beam 5.2s linear -1.2s infinite;
}

html.in-preshow .sb-box__switch {
  animation: glint 6.3s ease-in-out 4.1s infinite;
}

html.in-preshow .preshow__name {
  animation: fade-in 1.2s ease-out 0.5s both;
}

html.in-preshow .target--name > span {
  animation: name-breathe 8.4s ease-in-out 3.2s infinite;
}

/* Handing over, on the intro's clock (script.js starts these with it): the
   standby light gives way to the tally as it wakes (--t-tally); the unlit
   rig goes as the real one draws out and resolves over it (--t-click,
   --t-lights); each lamp still glowing goes as its real lamp comes on; the
   name holds until just before the studio card takes its place (--t-ident),
   so the two never overlap; the power
   box and the haze go as the stage warms (--t-wash). */
html.leaving .sb-led {
  animation: fade-out 0.12s linear calc(var(--t-tally) - 0.02s) both;
}

html.leaving .sb-truss {
  animation: fade-out 0.5s ease-in calc(var(--t-click) + 0.15s) both;
}

html.leaving .sb-fixtures {
  animation: fade-out 0.45s ease-in calc(var(--t-lights) + 0.05s) both;
}

html.leaving .sb-warm {
  animation: fade-out 0.3s linear calc(var(--t-lights) + 0.16s) both;
}

html.leaving .sb-glitch {
  animation: fade-out 0.3s linear calc(var(--t-lights) + var(--gi) * 0.11s + 0.05s) both;
}

html.leaving .sb-box {
  animation: fade-out 0.7s ease-in calc(var(--t-wash) + 0.1s) both;
}

html.leaving .preshow__air {
  animation: fade-out 0.9s ease-in var(--t-wash) both;
}

html.leaving .preshow__name {
  animation: fade-out 0.18s ease-in calc(var(--t-ident) - 0.2s) both;
}

/* The footer is there from the start, so the Sound control can be set
   before the show starts; Replay only once there is something to replay. */
html.in-preshow body .foot {
  animation: none !important;
  opacity: 1;
}

html.in-preshow .replay {
  display: none;
}

/* After the show starts, focus moves to the card, for keyboard and screen
   reader users; it needs no ring. */
.card:focus {
  outline: none;
}

/* Text for screen readers only. */
.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

@keyframes fade-out {
  to { opacity: 0; }
}

@keyframes haze {
  from { transform: translate(-3%, 1%); }
  to   { transform: translate(3%, -1.5%); }
}

@keyframes standby {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.3; }
}

@keyframes warm {
  50% { opacity: 0.27; }
}

/* Every 5.2s the misbehaving light flickers up, twice, with a stab of beam
   into the haze, and dies back to an ember; in between it catches for a
   moment and loses it. No more than three dips in any second, like every
   flicker on the page. */
@keyframes glitch-lens {
  0%, 17%, 21%, 35%, 39%, 57%, 72%, 100% { opacity: 0.16; }
  18.2% { opacity: 0.48; }
  36.4% { opacity: 0.34; }
  58%   { opacity: 0.95; }
  59.6% { opacity: 0.14; }
  62%   { opacity: 0.9; }
  63.5% { opacity: 0.24; }
  66%   { opacity: 0.5; }
}

@keyframes glitch-glow {
  0%, 17%, 21%, 35%, 39%, 57%, 72%, 100% { opacity: 0.07; }
  18.2% { opacity: 0.22; }
  36.4% { opacity: 0.15; }
  58%   { opacity: 0.6; }
  59.6% { opacity: 0.05; }
  62%   { opacity: 0.55; }
  63.5% { opacity: 0.1; }
  66%   { opacity: 0.28; }
}

@keyframes glitch-beam {
  0%, 57.5%, 64%, 100% { opacity: 0; }
  58%   { opacity: 0.5; }
  59.4% { opacity: 0; }
  62%   { opacity: 0.42; }
  63.4% { opacity: 0; }
}

/* ...and, on a slower cycle of its own, its supply sags for a moment. */
@keyframes sag {
  0%, 30%, 36%, 100% { opacity: 1; }
  32% { opacity: 0.35; }
}

@keyframes glint {
  0%, 88%, 100% { stroke-opacity: 0.5; }
  93%           { stroke-opacity: 1; }
}

@keyframes name-breathe {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.8; }
}

/* The answers. */
@keyframes pop {
  0%   { opacity: 0; transform: scale(0.5); }
  15%  { opacity: 1; transform: scale(1.35); }
  100% { opacity: 0; transform: scale(2.1); }
}

@keyframes bloom {
  from { transform: scale(1.9); }
  to   { transform: none; }
}

@keyframes take {
  0%   { opacity: 0.95; }
  20%  { opacity: 0.3; }
  40%  { opacity: 0.85; }
  100% { opacity: 0.75; }
}

@keyframes throw {
  0%   { transform: rotate(-90deg); }
  70%  { transform: rotate(9deg); }
  100% { transform: none; }
}

@keyframes arc {
  0%   { opacity: 0; transform: scale(0.4); }
  15%  { opacity: 0.95; transform: scale(1.1); }
  100% { opacity: 0; transform: scale(1.8); }
}

@keyframes wake {
  0%   { color: rgba(245, 236, 220, 0.42); text-shadow: 0 0 0 rgba(233, 180, 92, 0); }
  28%  { color: #fffaf0; text-shadow: 0 0 22px rgba(255, 214, 150, 0.85), 0 0 60px rgba(233, 180, 92, 0.45); }
  100% { color: var(--cream); text-shadow: 0 0 14px rgba(233, 180, 92, 0.5), 0 0 40px rgba(233, 180, 92, 0.26); }
}


/* 7. Intro timeline -------------------------------------------------------- */

/* The studio card that appears alone, before the title. */
.ident {
  position: fixed;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 1.5rem 20vh; /* sits where the title is about to land, not dead centre */
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1rem, 0.8rem + 1.5vw, 1.95rem);
  letter-spacing: 0.5em;
  text-indent: 0.5em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--cream);
}

.ident::before,
.ident::after {
  content: "";
  flex: 0 0 clamp(1.25rem, 5vw, 4.5rem);
  height: 1px;
  margin-right: 1em;
  background: linear-gradient(90deg, transparent, var(--amber-deep));
}

.ident::after {
  margin-right: 0;
  margin-left: 1em;
  background: linear-gradient(270deg, transparent, var(--amber-deep));
}

@media (max-width: 400px) {
  .ident {
    letter-spacing: 0.36em;
    text-indent: 0.36em;
  }
}

/* Stage */
html.intro .stage__base {
  animation: fade-in 1.4s ease-out var(--t-wash) both;
}

html.intro .stage__wash {
  animation:
    wash-in 1.2s var(--ease-out) var(--t-wash) both,
    wash-hit 0.7s ease-in-out var(--t-land) forwards,
    wash-spill 0.9s linear var(--t-snag) forwards;
}

html.intro .stage__floor {
  animation: fade-in 1.6s var(--ease-out) calc(var(--t-wash) + 0.25s) both;
}

/* The whole rig takes the jolt when the safety cable catches. */
html.intro .stage__rig {
  animation: shudder 0.42s linear var(--t-snag);
}

/* Rig: rails draw from the centre, lattice fades, fixtures resolve, lamps pop. */
html.intro .rig-rail {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: draw 0.5s var(--ease-out) var(--t-click) forwards;
}

html.intro .rig-lattice {
  animation: fade-in 0.6s ease-out calc(var(--t-click) + 0.15s) both;
}

html.intro .rig-relay {
  animation: relay 0.4s ease-out var(--t-click) both;
}

html.intro .fix {
  animation: fade-in 0.45s ease-out calc(var(--t-click) + 0.12s + var(--i) * 0.06s) both;
}

html.intro .lamp-flash {
  animation: lamp-flash 0.55s ease-out calc(var(--t-lights) + var(--i) * 0.11s) both;
}

/* Every lamp dips when the safety cable snags: the rig's power sags. */
html.intro .lamp-on {
  animation:
    fade-in 0.4s ease-out calc(var(--t-lights) + var(--i) * 0.11s + 0.05s) both,
    power-dip 0.45s linear var(--t-snag);
}

html.intro .lamp-glow {
  animation:
    glow-pop 0.8s var(--ease-out) calc(var(--t-lights) + var(--i) * 0.11s + 0.05s) both,
    power-dip 0.45s linear var(--t-snag);
}

html.intro .beam {
  animation:
    fade-in 1s var(--ease-out) calc(var(--t-lights) + var(--i) * 0.11s + 0.16s) both,
    power-dip 0.45s linear var(--t-snag);
}

/* The neighbour on the right blows out on the jolt and re-strikes 0.6s later
   (script.js places its sound by the same beats); the one on the left
   stutters. */
html.intro .fixture--blink .lamp-on {
  animation:
    fade-in 0.4s ease-out calc(var(--t-lights) + var(--i) * 0.11s + 0.05s) both,
    blow 0.95s linear var(--t-snag);
}

html.intro .fixture--blink .lamp-glow {
  animation:
    glow-pop 0.8s var(--ease-out) calc(var(--t-lights) + var(--i) * 0.11s + 0.05s) both,
    blow 0.95s linear var(--t-snag),
    glow-burst 0.95s linear var(--t-snag);
}

html.intro .fixture--blink .beam {
  animation:
    fade-in 1s var(--ease-out) calc(var(--t-lights) + var(--i) * 0.11s + 0.16s) both,
    blow 0.95s linear var(--t-snag);
}

html.intro .fixture--blink .lamp-flash {
  animation:
    lamp-flash 0.55s ease-out calc(var(--t-lights) + var(--i) * 0.11s) both,
    blow-flash 0.95s linear var(--t-snag);
}

html.intro .fixture--near .lamp-on {
  animation:
    fade-in 0.4s ease-out calc(var(--t-lights) + var(--i) * 0.11s + 0.05s) both,
    near-flicker 0.5s linear var(--t-snag);
}

html.intro .fixture--near .lamp-glow {
  animation:
    glow-pop 0.8s var(--ease-out) calc(var(--t-lights) + var(--i) * 0.11s + 0.05s) both,
    near-flicker 0.5s linear var(--t-snag);
}

html.intro .fixture--near .beam {
  animation:
    fade-in 1s var(--ease-out) calc(var(--t-lights) + var(--i) * 0.11s + 0.16s) both,
    near-flicker 0.5s linear var(--t-snag);
}

/* The loose fixture's light. Its movement (shake, slip, fall, swing) and its
   sparks are run by script.js from the physics; here, only its light: a
   hesitation as GO WELL lands, sputtering from --t-warn with the beam cutting
   out, a brown-out as the clamp slips, and a jolt and flare at the snag.
   Everything that flickers stays under three dips in any second. */
html.intro .loose__lamp {
  animation:
    stutter 0.36s linear var(--t-flicker),
    sputter 0.8s linear var(--t-warn),
    shock 0.7s linear var(--t-snag);
}

html.intro .loose__beam {
  animation:
    stutter 0.36s linear var(--t-flicker),
    beam-cut 0.8s linear var(--t-warn),
    shock 0.7s linear var(--t-snag);
}

/* Its glow blooms on the jolt. */
html.intro .fixture--loose .lamp-glow {
  animation:
    glow-pop 0.8s var(--ease-out) calc(var(--t-lights) + var(--i) * 0.11s + 0.05s) both,
    power-dip 0.45s linear var(--t-snag),
    glow-flare 0.6s ease-out var(--t-snag);
}

/* The cable flashes taut as it catches. */
html.intro .safety {
  animation: snatch 0.5s ease-out var(--t-snag);
}

/* Standby LED: wakes first, blinks on the relay click, goes dark at the end. */
html.intro .tally {
  animation:
    tally-wake 0.6s var(--ease-out) var(--t-tally) both,
    tally-click 0.2s linear var(--t-click),
    tally-off 0.7s ease-out var(--t-tally-off) forwards;
}

/* Studio card */
html.intro .ident {
  animation:
    ident-in 0.55s var(--ease-out) var(--t-ident) both,
    ident-out 0.3s var(--ease-in) var(--t-ident-out) forwards;
}

/* Title card */
html.intro .mark {
  animation: fade-in 0.8s var(--ease-out) var(--t-mark) both;
}

html.intro .title .l:nth-child(1) {
  animation: title-land 0.5s var(--ease-land) var(--t-title) both;
}

html.intro .title .l:nth-child(2) {
  animation: title-land 0.5s var(--ease-land) var(--t-land) both;
}

html.intro .tagline {
  animation: rise-in 0.75s var(--ease-out) var(--t-tagline) both;
}

html.intro .rule {
  animation: rule-in 0.9s var(--ease-out) calc(var(--t-tagline) + 0.12s) both;
}

html.intro .desc {
  animation: rise-in 0.75s var(--ease-out) var(--t-desc) both;
}

/* IN DEVELOPMENT switches on, cleanly, after the silence; the punchline
   follows half a beat later. */
html.intro .status::before {
  animation: plate-glow 0.9s ease-out var(--t-status) both;
}

html.intro .status__label {
  animation: plate-on 0.5s ease-out var(--t-status) both;
}

html.intro .punch {
  animation: rise-in 0.55s var(--ease-out) var(--t-punch) both;
}

html.intro .foot {
  animation: fade-in 0.9s var(--ease-out) var(--t-foot) both;
}

/* Keyframes */
@keyframes fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes rise-in {
  from { opacity: 0; transform: translateY(0.6rem); }
  to   { opacity: 1; transform: none; }
}

@keyframes title-land {
  0%   { opacity: 0; transform: translateY(0.14em) scale(1.08); filter: blur(10px); }
  45%  { filter: blur(0); }
  100% { opacity: 1; transform: none; filter: blur(0); }
}

@keyframes rule-in {
  from { opacity: 0; transform: scaleX(0); }
  to   { opacity: 1; transform: none; }
}

@keyframes draw {
  to { stroke-dashoffset: 0; }
}

@keyframes relay {
  0%   { opacity: 0; transform: scale(0.3); }
  30%  { opacity: 1; transform: scale(1.6); }
  100% { opacity: 0; transform: scale(2.6); }
}

@keyframes lamp-flash {
  0%   { opacity: 0; }
  25%  { opacity: 1; }
  100% { opacity: 0; }
}

@keyframes glow-pop {
  0%   { opacity: 0; transform: scale(0.5); }
  50%  { opacity: 1; transform: scale(1.12); }
  100% { opacity: 1; transform: none; }
}

@keyframes tally-wake {
  0%   { opacity: 0; transform: scale(0.5); }
  45%  { opacity: 1; transform: scale(1.3); }
  100% { opacity: 1; transform: none; }
}

@keyframes tally-click {
  0%   { opacity: 1; }
  40%  { opacity: 0.3; }
  100% { opacity: 1; }
}

@keyframes tally-off {
  from { opacity: 1; }
  to   { opacity: 0; }
}

@keyframes wash-in {
  from { opacity: 0; }
  to   { opacity: 0.85; }
}

/* Starts as GO WELL lands: a flare on the hit, a dip as the centre lamp
   hesitates (--t-flicker), then recovery as the tagline arrives.
   Percentages map onto --t-land ... --t-land + 0.7s. */
@keyframes wash-hit {
  0%   { opacity: 0.85; }
  14%  { opacity: 1; }
  38%  { opacity: 0.94; }
  55%  { opacity: 0.7; }
  70%  { opacity: 0.9; }
  100% { opacity: 0.85; }
}

/* The snag: the key light flares, then the stage dims while its beam is
   thrown across the room and the rig's power sags. */
@keyframes wash-spill {
  0%   { opacity: 0.85; }
  5%   { opacity: 1; }
  16%  { opacity: 0.55; }
  30%  { opacity: 0.78; }
  45%  { opacity: 0.66; }
  100% { opacity: 0.85; }
}

/* The truss dips under the load and rings down (about 8 Hz). */
@keyframes shudder {
  0%   { transform: translate(0, 0); }
  6%   { transform: translate(0, 2.6px); }
  20%  { transform: translate(0, -1.4px); }
  34%  { transform: translate(0, 0.9px); }
  50%  { transform: translate(0, -0.45px); }
  66%  { transform: translate(0, 0.2px); }
  100% { transform: translate(0, 0); }
}

/* --------------------------------------------------------------------------
   The mishap's light
   Opacity only, on small areas, and no light dips more than three times in
   any second. script.js puts its crackles, pops and the recovery on the same
   beats.
   -------------------------------------------------------------------------- */

/* --t-flicker, 0.36s: the centre lamp hesitates as GO WELL lands. */
@keyframes stutter {
  0%        { opacity: 1; }
  12%       { opacity: 0.4; }
  28%       { opacity: 0.95; }
  42%       { opacity: 0.75; }
  65%, 100% { opacity: 1; }
}

/* --t-warn, 0.8s (up to --t-drop): sputtering on a failing contact, dips at
   0.02s and 0.22s; the clamp slips at 0.45s, and it browns out while it
   hangs there. */
@keyframes sputter {
  0%   { opacity: 1; }
  3%   { opacity: 0.3; }
  8%   { opacity: 0.95; }
  25%  { opacity: 0.85; }
  28%  { opacity: 0.35; }
  34%  { opacity: 1; }
  56%  { opacity: 1; }
  59%  { opacity: 0.25; }
  66%  { opacity: 0.7; }
  80%  { opacity: 0.55; }
  100% { opacity: 1; }
}

/* The beam is the first thing to go: it cuts out on each crackle. */
@keyframes beam-cut {
  0%   { opacity: 1; }
  2%   { opacity: 0; }
  9%   { opacity: 0; }
  11%  { opacity: 0.9; }
  26%  { opacity: 0.9; }
  28%  { opacity: 0; }
  36%  { opacity: 0; }
  38%  { opacity: 1; }
  58%  { opacity: 1; }
  60%  { opacity: 0.1; }
  70%  { opacity: 0.5; }
  82%  { opacity: 0.35; }
  100% { opacity: 1; }
}

/* --t-snag, 0.7s: the filament takes the jolt, then holds. */
@keyframes shock {
  0%        { opacity: 1; }
  5%        { opacity: 0.2; }
  12%       { opacity: 0.95; }
  22%       { opacity: 0.45; }
  34%, 100% { opacity: 1; }
}

@keyframes glow-flare {
  from { transform: scale(1.9); }
  to   { transform: scale(1); }
}

/* --t-snag, 0.45s: every lamp on the rig sags with the jolt. */
@keyframes power-dip {
  0%        { opacity: 1; }
  8%        { opacity: 0.45; }
  24%       { opacity: 0.9; }
  40%       { opacity: 0.7; }
  70%, 100% { opacity: 1; }
}

/* --t-snag, 0.95s: the right-hand neighbour pops out at +0.05s and
   re-strikes at +0.61s. */
@keyframes blow {
  0%, 4% { opacity: 1; }
  7%     { opacity: 0.04; }
  64%    { opacity: 0.04; }
  67%    { opacity: 0.6; }
  71%    { opacity: 0.15; }
  78%    { opacity: 0.85; }
  86%    { opacity: 0.7; }
  100%   { opacity: 1; }
}

@keyframes blow-flash {
  0%, 3% { opacity: 0; }
  5%     { opacity: 0.35; }
  14%    { opacity: 0; }
  66%    { opacity: 0; }
  68%    { opacity: 0.25; }
  76%    { opacity: 0; }
  100%   { opacity: 0; }
}

/* Its glow bursts as it blows, and again, smaller, as it re-strikes. */
@keyframes glow-burst {
  0%, 3%   { transform: scale(1); }
  5%       { transform: scale(1.7); }
  14%      { transform: scale(1.2); }
  66%      { transform: scale(1); }
  68%      { transform: scale(1.35); }
  78%, 100% { transform: scale(1); }
}

/* --t-snag, 0.5s: the left-hand neighbour stutters. */
@keyframes near-flicker {
  0%        { opacity: 1; }
  6%        { opacity: 0.35; }
  16%       { opacity: 0.95; }
  30%       { opacity: 0.55; }
  44%, 100% { opacity: 1; }
}

/* --t-snag: the safety cable flashes taut. */
@keyframes snatch {
  0%   { stroke-opacity: 0.6; stroke-width: 1.8; }
  6%   { stroke-opacity: 1; stroke-width: 2.6; }
  100% { stroke-opacity: 0.6; stroke-width: 1.8; }
}

/* --t-status: the plate switches on, with a warm edge that fades. */
@keyframes plate-glow {
  0%   { opacity: 0; box-shadow: 0 0 0 0 rgba(233, 180, 92, 0); }
  12%  { opacity: 1; box-shadow: 0 0 22px 2px rgba(233, 180, 92, 0.3); }
  100% { opacity: 1; box-shadow: 0 0 0 0 rgba(233, 180, 92, 0); }
}

@keyframes plate-on {
  0%        { opacity: 0; }
  20%, 100% { opacity: 1; }
}

@keyframes ident-in {
  from { opacity: 0; visibility: visible; letter-spacing: 0.72em; }
  to   { opacity: 1; visibility: visible; letter-spacing: 0.5em; }
}

@keyframes ident-out {
  from { opacity: 1; visibility: visible; transform: none; filter: blur(0); }
  to   { opacity: 0; visibility: hidden; transform: scale(1.06); filter: blur(8px); }
}


/* 8. Reduced motion, forced colours, print --------------------------------- */

@media (prefers-reduced-motion: reduce) {
  /* The intro becomes a single soft fade-up of the finished page, the centre
     light already hanging off its safety cable; a beat later, the punchline.
     Opacity only: nothing moves, shakes or flickers, and script.js runs no
     physics or sparks. (The fade is on the stage, card and footer rather than
     the body, so the pre-show stays visible while the intro is held.) */
  html.intro *,
  html.intro *::before,
  html.intro *::after {
    animation: none !important;
  }

  html.intro .stage,
  html.intro .card,
  html.intro .foot {
    animation: fade-in 1s ease-out both !important;
  }

  html.intro .punch {
    animation: fade-in 0.8s ease-out 1.1s both !important;
  }

  /* ...and held, like everything else, behind the pre-show. (The rules
     above reset the play state, so it is restated for them here.) */
  html.held .stage,
  html.held .card,
  html.held .foot,
  html.held .punch {
    animation-play-state: paused !important;
  }

  /* The pre-show holds still: the standby light simply on, the misbehaving
     light half-lit rather than flickering, every answer a plain change of
     state. Starting the show fades it out. */
  html.leaving .preshow {
    animation: fade-out 0.4s ease-in both !important;
  }

  .sb-glitch__lens {
    opacity: 0.55;
  }

  .sb-glitch__glow {
    opacity: 0.24;
  }

  html.intro .rig-rail {
    stroke-dasharray: none;
    stroke-dashoffset: 0;
  }

  .ident {
    display: none;
  }

  .control {
    transition: none;
  }
}

@media (forced-colors: active) {
  .status::before {
    border-color: CanvasText;
    background: none;
  }

  .rule,
  .mark::before,
  .mark::after {
    background: CanvasText;
  }
}

@media print {
  .stage,
  .ident,
  .preshow,
  html.js .control {
    display: none !important;
  }

  .status::before {
    border-color: #999;
    background: none;
  }

  /* Print the finished card, wherever the intro happens to be. */
  html.intro *,
  html.intro *::before,
  html.intro *::after {
    animation: none !important;
  }

  body {
    color: #000;
    background: #fff;
  }

  .card {
    padding-top: 3rem;
  }

  .title,
  .tagline,
  .desc,
  .status,
  .foot {
    color: #000;
    text-shadow: none;
  }

  .mark,
  .punch {
    color: #444;
  }
}
