/* ═══════════════════════════════════════════════════════════════════════════
   landing.css — the front door: gptpg.ai marketing surfaces.
   Loaded AFTER theme.css by landing.html, games.html, game.html.
   Consumes theme tokens only; adds no new palette. Nothing here leaks into
   the in-game surfaces (play/host/new all load theme.css alone).
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── per-game accent, resolved from a data-accent attribute ─────────────── */
[data-accent]        { --a: var(--gold);  --a-soft: rgba(245, 182, 63, 0.30); }
[data-accent="gold"] { --a: var(--gold);  --a-soft: rgba(245, 182, 63, 0.30); }
[data-accent="cyan"] { --a: var(--host);  --a-soft: rgba(98, 214, 232, 0.28); }
[data-accent="buzz"] { --a: var(--buzz);  --a-soft: rgba(232, 67, 74, 0.30); }
[data-accent="coral"]{ --a: var(--coral); --a-soft: rgba(255, 122, 110, 0.28); }
[data-accent="teal"] { --a: var(--teal);  --a-soft: rgba(62, 207, 178, 0.26); }

/* ── layout scaffolding ─────────────────────────────────────────────────── */
.shell { max-width: 1120px; margin: 0 auto; padding: 0 20px; }
.shell-narrow { max-width: 820px; margin: 0 auto; padding: 0 20px; }

section { position: relative; }
.band { padding: 72px 0; }
.band + .band { border-top: 1px solid var(--stage-line); }
@media (max-width: 720px) { .band { padding: 48px 0; } }

.eyebrow {
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.22em;
  font-size: 13px;
  color: var(--gold);
  margin: 0 0 12px;
}
.band h2 {
  font-size: clamp(28px, 5vw, 44px);
  line-height: 1.02;
  margin: 0 0 10px;
}
.band .lede {
  color: var(--ink-dim);
  font-size: clamp(15px, 2vw, 18px);
  line-height: 1.5;
  max-width: 62ch;
  margin: 0;
}

/* ── top bar ────────────────────────────────────────────────────────────── */
.topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  backdrop-filter: blur(14px);
  background: rgba(16, 14, 36, 0.82);
  border-bottom: 1px solid var(--stage-line);
}
.topbar .inner {
  max-width: 1120px;
  margin: 0 auto;
  padding: 12px 20px;
  display: flex;
  align-items: center;
  gap: 18px;
}
.topbar .wordmark {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 26px;
  letter-spacing: 0.1em;
  color: var(--gold);
  text-decoration: none;
  text-shadow: 0 0 18px rgba(245, 182, 63, 0.4);
}
.topbar nav { display: flex; gap: 18px; margin-left: auto; align-items: center; }
/* :not(.btn) matters — a bare `.topbar nav a` selector outranks .btn-gold's
   own color (0,2,1 vs 0,1,0) and paints the gold CTA lavender. */
.topbar nav a:not(.btn) {
  color: var(--ink-dim);
  text-decoration: none;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.02em;
}
.topbar nav a:not(.btn):hover { color: var(--ink); }
.topbar nav .btn { font-size: 15px; padding: 9px 18px; }
@media (max-width: 700px) {
  .topbar nav a.navlink { display: none; }
  .topbar nav a.navlink.keep { display: inline; }

  /* The wordmark goes. On a phone the bar had three things fighting for about
     360px — a logo, a truncated name, and a CTA wrapping onto two lines — and
     the logo is the one that earns its place least: every page repeats it
     immediately below, and the footer keeps a route home. What is left is who
     you are and the one action. */
  .topbar .wordmark { display: none; }
  .topbar .inner { justify-content: space-between; gap: 12px; }

  /* And the CTA stops wrapping. A two-line button reads as a mistake, and
     shrinking the type is cheaper than shortening the words. */
  .topbar nav .btn {
    white-space: nowrap;
    font-size: 13px;
    padding: 9px 14px;
    line-height: 1.1;
  }
  /* The name gets the room the logo gave up. */
  .topbar nav a.navlink.acct { font-size: 13px; max-width: 14ch; }
}

/* ── hero ───────────────────────────────────────────────────────────────── */
.hero {
  /* How far the whole opening sits down the page. Zero on phones, where that
     much dead space would push the headline under the fold on the one screen
     where the fold costs something. */
  --hero-drop: 0px;
  /* The set's width, declared once and in two forms.
     --set-w is for `width`, where its percentage means width.
     --set-wv is for VERTICAL calcs, where a percentage would silently mean
     height instead — the one that measures the wrong axis and puts things in
     the wrong place with no error to show for it. */
  --set-w:  min(1680px, 164%);
  --set-wv: min(1680px, 164vw);
  /* How far the TOP group — host, wordmark, tagline — rides up from the drop,
     while the copy below stays put. It is taken off the drop and handed back to
     the headline's margin, so the two moves cancel exactly and nothing under
     the tagline shifts by a pixel. */
  --top-lift: 0px;
  padding: 56px 0 clamp(48px, 6vw, 88px);
  text-align: center;
  overflow: hidden;
  position: relative;
}

@media (min-width: 980px) { .hero { --hero-drop: 300px; --top-lift: 160px; } }
/* The room moves with the content, not just the content. The backdrop is
   absolutely positioned, so padding on the hero would not have shifted it —
   the host would have dropped 200px and left the floor behind. */
.hero .shell { padding-top: calc(var(--hero-drop) - var(--top-lift)); }

/* ── the room ─────────────────────────────────────────────────────────────
   A lit stage behind the host, held right down so it never competes with him.
   It answers "what is this site" before a single word is read — which is worth
   more here than any amount of the copy below it.

   Vignetted with a mask rather than dimmed with a gradient overlay: an overlay
   is a rectangle you can always find the edges of, where a mask makes the room
   simply stop existing toward the edges. It fades out well before the pitch
   below, so the set never reads as wallpaper behind the whole page.

   It comes up LAST, two seconds after the lectern has finished rising. The host
   is introduced in the dark and only then do the lights find the room he is
   standing in — which is the right order for a reveal, and it also means the
   busiest thing on the page arrives after the eye has already read the
   wordmark rather than competing with it. */
.hero .herobackdrop,
.hero .roomwash {
  position: absolute; z-index: 0; pointer-events: none;
  top: calc(var(--hero-drop) - var(--top-lift)); left: 50%;
  width: var(--set-w);
  aspect-ratio: 16 / 9;
  height: auto;
  /* Lifted by a share of its OWN height, not a pixel offset, so the floor line
     stays under his feet at every width. He was standing in mid-air above it. */
  transform: translateX(-50%) translateY(-17%);
  /* A CIRCLE of light around the host and the wordmark, falling off to nothing
     at its edge. Everything the room has to say it says in the middle; the rest
     was just video the eye had to travel past.

     Written as an ellipse on purpose. radial-gradient's `circle` keyword only
     accepts absolute lengths, and a fixed radius would drift off the host the
     moment the viewport changed. The box is locked to 16/9, so a horizontal
     radius of 32% and a vertical one of 32/0.5625 = 56.9% describes an exact
     circle that scales with it. */
  --spot: radial-gradient(ellipse 32% 56.9% at 50% 46%,
    #000 34%, rgba(0, 0, 0, 0.55) 62%, transparent 92%);
  -webkit-mask-image: var(--spot);
          mask-image: var(--spot);
}
.hero .herobackdrop {
  object-fit: cover;
  opacity: 0.52;
  /* Softened so it reads as a SET rather than as footage. The blur takes the
     photographic detail out — the eye stops trying to resolve it and accepts it
     as depth — and the desaturation stops the video's own pinks arguing with
     the gold in front of them. */
  filter: blur(2.4px) saturate(0.72) brightness(0.92);
}
/* The wash on top of it: a violet-navy veil that pulls the whole room onto the
   page's own colour. Masked identically to the video, so it fades out exactly
   where the room does rather than ending on an edge. */
.hero .roomwash {
  z-index: 1;
  background:
    radial-gradient(ellipse 52% 56% at 50% 48%, rgba(24, 16, 52, 0.28), transparent 78%),
    linear-gradient(180deg, rgba(26, 17, 56, 0.62), rgba(16, 11, 38, 0.74));
}
.hero .shell { position: relative; z-index: 2; }
@keyframes roomUp { from { opacity: 0; } to { opacity: 0.52; } }
@keyframes washUp { from { opacity: 0; } to { opacity: 1; } }
/* 4.5s = the lectern finishes at 2.51s, plus two seconds of him standing there
   in the dark. Slow on the way up: house lights, not a switch. */
.hero.intro .herobackdrop { animation: roomUp 1.5s ease 4.5s both; }
.hero.intro .roomwash { animation: washUp 1.5s ease 4.5s both; }
@media (prefers-reduced-motion: reduce) {
  .hero .herobackdrop { animation: none; opacity: 0.52; }
  .hero .roomwash { animation: none; opacity: 1; }
}

/* ── the audience ─────────────────────────────────────────────────────────
   You are in the crowd. That is the whole composition: the audience is the
   nearest thing to camera, the host is beyond them, and you are looking over
   their heads at the stage — which is why they sit IN FRONT of him rather than
   behind, and why they run the full width of the screen rather than the width
   of the set. A crowd with the page showing past both ends of it is a graphic;
   one that leaves the frame on both sides is a room you are standing in.

   Their heads overlap the bottom of the lit floor on purpose. Below the light
   they are black on black, so the light is the only thing that renders them —
   they are silhouetted BY the stage, which is exactly what being in an audience
   looks like.

   Masked to nothing by their shoulders. The headline sits behind them in the
   page, and without the fade the bodies would be a black slab across it. */
.hero .audience {
  position: absolute; pointer-events: none;
  /* Full-bleed out of a max-width shell. */
  left: 50%; width: 100vw;
  /* The room's own top, plus most of the room's height — placing them at the
     front edge of the floor. Derived from --set-w so they track the set at
     every width instead of needing their own breakpoints. */
  /* Measured from .shell now, whose own top sits 56px below the hero's — the
     hero's padding. */
  top: calc(var(--hero-drop) - var(--top-lift) - 181px + var(--set-wv) * 0.4);
  display: flex; align-items: flex-start;
  transform: translateX(-50%);
  opacity: 0;
  /* Solid at the heads, gone by the shoulders. The fade is doing two jobs: it
     keeps the bodies off the copy behind them, and it turns the crowd into the
     break between the hero and everything below rather than a picture that
     stops. */
  -webkit-mask-image: linear-gradient(to bottom, #000 0 26%, rgba(0, 0, 0, 0.45) 50%, transparent 78%);
          mask-image: linear-gradient(to bottom, #000 0 26%, rgba(0, 0, 0, 0.45) 50%, transparent 78%);
}
.hero .audience img { width: 100%; height: auto; display: block; }

/* position:relative and nothing else. Source order alone was not enough: a
   POSITIONED element paints above static content whatever the tree order says,
   so the crowd sat over the headline regardless. Making the headline positioned
   too puts both in the same paint step, where coming later in the source is
   what wins — no z-index anywhere, and nothing to keep in sync. */
.hero h1, .hero .sub { position: relative; }

/* 4.8s — three hundred milliseconds after the room starts coming up, so the
   lights find the stage and then the crowd is there in it. Rising as it fades
   in, because a crowd that materialises without moving reads as a decal. */
@keyframes crowdIn {
  from { opacity: 0; transform: translateX(-50%) translateY(30px); }
  to   { opacity: 0.95; transform: translateX(-50%); }
}
.hero.intro .audience { animation: crowdIn 1.2s ease 4.8s both; }
@media (prefers-reduced-motion: reduce) {
  .hero .audience { animation: none; opacity: 0.95; }
}

/* ── the opening ──────────────────────────────────────────────────────────
   Three beats, and the third is the reason for the other two: nobody would
   notice the T is a lectern if it were simply there, but watching it become one
   teaches the joke in half a second.

     0.08s  the letters arrive, staggered
     1.15s  the host expands forward out of nothing and lands
     1.95s  the T grows into his lectern, and the lamp and the tubes come up

   Under two and a half seconds start to finish, and every intermediate state is
   a page that works: the wordmark is legible from the first frame and nothing
   below it waits on any of this. Anyone who scrolls at 400ms loses the payoff,
   not the product.

   Everything moves on transform and opacity only — the two properties the
   compositor can animate without touching layout. */

.hero .hero-mascot {
  display: block;
  height: clamp(152px, 32.5vw, 408px);
  width: auto;
  margin: 0 auto;
  position: relative;
  z-index: 1;
  transform-origin: 50% 78%;
  filter: drop-shadow(0 18px 30px rgba(6, 4, 20, 0.65));
}
.hero .wordwrap {
  position: relative; display: block;
  font-size: clamp(88px, 19.5vw, 208px);
}
.hero .marquee {
  font-size: inherit; line-height: 0.86; display: block;
  position: relative;
  z-index: 2;
  /* Deep enough that the lectern's edge lands at his WAIST. That is the whole
     trick: the T is already the shape of a lectern, and a figure cut off at the
     waist behind it stops reading as a logo with a mascot above it and starts
     reading as a host standing at a podium. A few tenths of an em either way
     and the illusion goes.

     Note the direction: MORE negative pulls the type up over him, which sinks
     him lower behind it. It is the same lever as moving the figure. */
  margin-top: -0.86em;
  /* It sits on a painted figure now, not on the page, so it needs its own
     contrast — a dark plate hugging the letters. */
  text-shadow:
    0 0 14px rgba(4, 2, 14, 0.85),
    0 0 14px rgba(4, 2, 14, 0.85),
    0 4px 10px rgba(4, 2, 14, 0.9);
}
/* The lectern lamp. A warm glow rising off the T onto whoever is standing at
   it — the light every game show puts under a host's chin. It sits behind the
   type but in front of him, which is only possible because .marquee makes its
   own stacking context: z-index -1 here is inside that context, so it clears
   the figure at z-index 1 without ever climbing over the letters. */
.hero .marquee::before {
  content: ''; position: absolute; z-index: -1; pointer-events: none;
  left: 50%; bottom: 42%;
  width: 2.6em; height: 2.2em;
  transform: translateX(-50%);
  background: radial-gradient(ellipse 46% 100% at 50% 100%,
    rgba(255, 214, 140, 0.42), rgba(255, 186, 90, 0.13) 46%, transparent 72%);
}
.hero .marquee .lectern {
  /* A little taller than its neighbours, and no more. Far enough to read as a
     podium the others stand around, near enough that the wordmark still reads
     as one word in one size. The colour stays identical to every other letter —
     a differently coloured glyph inside a wordmark reads as a typo before it
     reads as a lectern, so the light behind it does that job instead. */
  font-size: 1.16em;
  position: relative;
}

/* ── the neon behind the wordmark ────────────────────────────────────────
   The tube is drawn from the LETTERS THEMSELVES, not from the neon PNG.

   The asset could not do this job. It traces an outline AROUND each glyph
   rather than stroking the glyph, and it is set at much looser tracking than
   the live type — so matching its total width shrinks its glyphs, and matching
   its glyphs overflows the word. Overlaid it read as two wordmarks fighting.

   content: attr(data-c) puts a copy of each letter behind itself. Same font,
   same size, same spacing, same position, forever and at every viewport,
   because it IS the same text — there is nothing left to keep in sync.

   Transparent fill with a stroke is what makes it a tube rather than a shadow:
   a glow behind a solid letter just fattens the letter, where a hollow outline
   peeking out around it reads as glass with light in it. */
.hero .marquee > span { position: relative; display: inline-block; }
.hero .marquee > span::before {
  content: attr(data-c);
  position: absolute; left: 0; top: 0; z-index: -1;
  color: transparent;
  -webkit-text-stroke: 0.035em #ffcf83;
  transform: scale(1.045);
  transform-origin: 50% 58%;
  /* Radii are em of a wordmark that reaches 208px, so these are much smaller
     than they look — 1em here is a 208px flood that swallows the whole hero. */
  text-shadow:
    0 0 0.022em rgba(255, 242, 216, 0.85),
    0 0 0.07em rgba(255, 203, 120, 0.6),
    0 0 0.17em rgba(255, 170, 60, 0.3);
}

.hero .brand-tag {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(12px, 2.2vw, 19px);
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--gold);
  opacity: 0.82;
  margin-top: 10px;
  text-indent: 0.34em;
}

@keyframes letterIn {
  from { opacity: 0; transform: translateY(0.34em) scale(0.88); }
  to   { opacity: 1; transform: none; }
}
.hero.intro .marquee > span {
  animation: letterIn 0.58s cubic-bezier(.2, .82, .25, 1) both;
}
.hero.intro .marquee > span:nth-child(1) { animation-delay: 0.08s; }
.hero.intro .marquee > span:nth-child(2) { animation-delay: 0.16s; }
.hero.intro .marquee > span:nth-child(3) { animation-delay: 0.24s; }
.hero.intro .marquee > span:nth-child(4) { animation-delay: 0.32s; }
.hero.intro .marquee > span:nth-child(5) { animation-delay: 0.40s; }

/* The host expands forward out of a point and settles. The origin sits low on
   the figure so he grows UP from behind the wordmark rather than swelling out
   of his own middle, which would read as a zoom rather than an arrival. */
@keyframes hostIn {
  from { opacity: 0; transform: scale(0.34) translateY(8%); }
  55%  { opacity: 1; }
  to   { opacity: 1; transform: none; }
}
.hero.intro .hero-mascot {
  animation: hostIn 0.82s cubic-bezier(.18, .86, .24, 1) 1.15s both;
}

/* The lectern rises. It starts at 1/1.16 — measurably identical to the letters
   either side — and overshoots a little on the way up, because a podium that
   arrives without weight reads as a scaling bug rather than a thing rising. */
@keyframes lecternUp {
  from { transform: scale(0.862); }
  62%  { transform: scale(1.04); }
  to   { transform: scale(1); }
}
.hero .marquee .lectern b {
  display: inline-block; font-weight: inherit; font-style: inherit;
  transform-origin: 50% 100%;
}
.hero.intro .marquee .lectern b {
  animation: lecternUp 0.56s cubic-bezier(.3, .9, .3, 1) 1.95s both;
}
@keyframes lampOn { from { opacity: 0; } to { opacity: 1; } }
.hero.intro .marquee::before { animation: lampOn 0.6s ease 2.05s both; }
.hero.intro .marquee > span::before { animation: lampOn 0.9s ease 2.25s both; }

/* Motion off: the finished picture, immediately. */
@media (prefers-reduced-motion: reduce) {
  .hero .marquee > span,
  .hero .marquee .lectern b,
  .hero .marquee::before,
  .hero .marquee > span::before,
  .hero .hero-mascot { animation: none; opacity: 1; transform: none; }
}

.hero h1 {
  font-size: clamp(30px, 6.2vw, 60px);
  line-height: 1.02;
  margin: calc(clamp(26px, 3.2vw, 44px) + var(--top-lift)) auto 0;
  max-width: 17ch;
  text-wrap: balance;
}
/* The payoff word gets a line to itself. display:block is the break rather than
   a <br>, so the copy and the line break cannot drift apart when the wording
   changes — and it stays correct at every width without a second rule. */
/* The payoff word gets its own line AND its own size. On the line alone it was
   the same size as the setup above it and shorter than it, which reads as an
   orphaned fragment rather than the word the sentence was building to. Size is
   what turns a leftover into a landing. */
/* The third claim leads in gold and lands in white — the reverse of the other
   two. Only the colours differ; the size, the weight, the break and the glow
   are all inherited, because it has to read as the third of three rather than
   as a new kind of thing. */
/* .claim.claim-swap, not .claim-swap alone: the shared rule below is written
   later at the same specificity, so at equal weight source order would hand the
   em back to gold. The extra class is what settles it. */
.hero h2.claim.claim-swap { color: var(--gold); }
.hero h2.claim.claim-swap em { color: #fff; }
.hero h1 em,
.hero h2.claim em {
  font-style: normal; display: block; color: var(--gold);
  font-size: 1.42em;
  line-height: 0.94;
  /* Lit by the same stage as the wordmark, but NOT built the same way. The
     wordmark's neon tube stays unique to it — hand the headline the same
     construction and the wordmark stops being the wordmark. What it gets is the
     lighting: a warm halo and a dark plate under the glyphs for depth. One
     property each, no per-letter markup to keep in sync with the copy. */
  text-shadow:
    0 2px 0 rgba(4, 2, 14, 0.55),
    0 0 22px rgba(245, 182, 63, 0.42),
    0 0 54px rgba(245, 182, 63, 0.22);
}
/* The second claim, and the demo's own heading. */
.hero h2.claim {
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(30px, 6.2vw, 60px);
  line-height: 1.02;
  margin: clamp(46px, 6.4vw, 90px) auto 0;
  max-width: 17ch;
  text-wrap: balance;
}

/* ── the buzzer, working ──────────────────────────────────────────────────
   Built to match the real player surface rather than to look like the rest of
   the marketing page — that is the point of it. Someone who buzzes here has
   already used the product, and it should be the same object when they get
   there: same card, same button, same clock readout underneath. */
/* It arrives rather than simply being there. Three steps in half a second —
   the question, then the station under it, then the button lighting on top —
   so the whole thing assembles the way a set does.

   The entrance moves the STATION, never the button's own transform: the button
   owns transform for its 90ms press snap, and sharing the property would have
   made pressing it feel slow. The button only fades. */
.buzzdemo .bd-card,
.buzzdemo .bd-station,
.buzzdemo .bd-meta {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.6s ease, transform 0.7s cubic-bezier(.2, .8, .25, 1);
}
.buzzdemo .bd-buzz { opacity: 0; transition: opacity 0.5s ease; }
.buzzdemo.entered .bd-card,
.buzzdemo.entered .bd-station,
.buzzdemo.entered .bd-meta { opacity: 1; transform: none; }
.buzzdemo.entered .bd-card { transition-delay: 0.04s; }
.buzzdemo.entered .bd-station { transition-delay: 0.22s; }
.buzzdemo.entered .bd-buzz { opacity: 1; transition-delay: 0.46s; }
.buzzdemo.entered .bd-meta { transition-delay: 0.6s; }
@media (prefers-reduced-motion: reduce) {
  .buzzdemo .bd-card,
  .buzzdemo .bd-station,
  .buzzdemo .bd-meta,
  .buzzdemo .bd-buzz { opacity: 1; transform: none; transition: none; }
}

/* One bounded unit, because that is the job a container does here: it says this
   part is INTERACTIVE and the rest of the page is copy. That is information,
   which is why it earns its place where the podium did not — the podium was a
   decorative object under the button and this is a frame around a control. */
.buzzdemo {
  max-width: 540px;
  margin: clamp(22px, 3vw, 34px) auto 0;
  display: flex; flex-direction: column; align-items: center;
  gap: clamp(14px, 2vw, 20px);
  position: relative;
  padding: clamp(26px, 3.6vw, 38px) clamp(22px, 3.4vw, 34px) clamp(26px, 3.6vw, 34px);
  border-radius: clamp(16px, 2vw, 22px);
  /* Frosted gold, with the purple question pane resting on it. Two frosted
     surfaces in the house's two colours, one inside the other — which is also
     what keeps them separable: same material, different light. */
  /* Lighter than the brand gold, not just more of it. Both frosted layers sit
     on a near-black page, so raising alpha alone drives them toward brown and
     mauve — luminance is what reads as frost. */
  /* The brand gold itself, not a tint of it. At this strength the panel is no
     longer a dark surface, so everything sitting on it had to be recoloured —
     the small type, the Answer button and the closing call to action were all
     built light-on-dark and would have vanished. */
  background: linear-gradient(180deg, rgba(255, 206, 116, 0.95), rgba(233, 165, 52, 0.92));
  -webkit-backdrop-filter: blur(18px) saturate(1.2);
          backdrop-filter: blur(18px) saturate(1.2);
  box-shadow:
    /* Light falling INTO the box from the top edge — the box has depth, so
       there is a shaded lip under its own rim before the contents start. */
    inset 0 14px 24px -14px rgba(28, 12, 4, 0.55),
    inset 0 -10px 22px -14px rgba(28, 12, 4, 0.4),
    inset 0 1px 0 rgba(255, 250, 232, 0.75),
    inset 0 0 0 1px rgba(255, 224, 154, 0.6),
    0 26px 54px -30px rgba(4, 2, 16, 0.9);
}
/* The question gets its own surface: frosted purple glass laid on the page's
   own colour. A translucent fill rather than another outline is what keeps this
   from being the nested-box problem — an outline inside an outline reads as two
   boxes, where a pane of glass reads as something resting ON the container.

   backdrop-filter does the actual frosting where it is supported; the fill and
   the highlight carry it where it is not, so the panel never falls back to a
   flat rectangle. */
/* ── the pane ─────────────────────────────────────────────────────────────
   Everything that is words lives here, and it is THE SIZE OF WHAT IT HOLDS.
   The earlier attempt gave it a fixed third of the panel, which left empty
   purple under a single line of text — exactly the padding this design exists
   to remove. It grows when it has more to say and the buzzer takes the rest.

   The PANEL's height is fixed instead, so the section never jumps as the pane
   changes: the space moves between the two zones rather than the page resizing
   around them. */
/* Taller. The buzzer is a fixed-size object and the pane is content-sized, so
   the panel has to be tall enough to hold BOTH at their largest — at 545 the
   socket overflowed the bottom edge and sat half outside its own case, which is
   the one thing a machined-looking panel must never do. */
.buzzdemo { height: clamp(560px, 80vh, 720px); }
.buzzdemo .bd-card {
  /* ONE SIZE, ALWAYS. Content-sizing was right in principle and wrong in
     practice: the pane changed height between a one-line question and a
     two-line one, so the buzzer shifted under it every round. Sized for the
     longest thing it holds, nothing on this panel ever moves — and it grows
     only for the one deliberate moment, the buzz. */
  flex: 0 0 auto;
  /* Tall enough for the longest question at two lines plus the clock and the
     prompt. It was 205 and the question overflowed the TOP edge — a centred
     flex column pushes overflow out of both ends, so a pane one line too short
     does not clip, it spills upward over the panel above it. */
  height: 246px;
  overflow: hidden;
  width: 100%;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 12px;
  /* Room under the pane. The button sat almost against it, and a plunger that
     close to the thing it answers reads as one control rather than two — the
     pane is what the room says to you and the button is what you say back. */
  margin-bottom: clamp(20px, 3.5vw, 40px);
  padding: clamp(16px, 2.4vw, 22px) clamp(16px, 2.4vw, 24px);
  border-radius: 14px;
  background: linear-gradient(180deg, rgba(52, 38, 104, 0.92), rgba(34, 24, 74, 0.9));
  box-shadow:
    inset 0 1px 0 rgba(233, 226, 255, 0.24),
    inset 0 0 0 1px rgba(192, 123, 255, 0.20),
    0 3px 6px -3px rgba(58, 26, 4, 0.45),
    0 16px 30px -14px rgba(58, 26, 4, 0.6);
  transition: flex-grow 300ms var(--ease-pop);
}
/* The buzzer takes whatever the pane is not using. */
.buzzdemo .bd-station {
  flex: 1 1 auto; min-height: 0;
  display: flex; align-items: center; justify-content: center;
  transition: opacity 180ms ease, transform 240ms var(--ease-pop);
}
/* Buzzed: the pane takes the panel and the button goes. */
.buzzdemo.answering .bd-card { flex-grow: 1; }
.buzzdemo.answering .bd-station {
  flex: 0 0 0; opacity: 0; transform: scale(0.9);
  pointer-events: none; overflow: hidden;
}

/* THE ANSWER ROW'S GEOMETRY IS PINNED HERE, before the pane is ever allowed to
   grow — which is the whole reason the last attempt failed. These elements were
   sized for a short slot, so a taller parent stretched the input into an oval
   the height of the panel, and patching it afterwards never held. A row that
   states its own height cannot be stretched by anything above it. */
.buzzdemo .bd-answer {
  flex: 0 0 auto; align-items: center;
  width: 100%; max-width: 420px; height: 60px;
}
.buzzdemo .bd-answer > * { flex: 0 0 auto; align-self: stretch; }
.buzzdemo .bd-input { flex: 1 1 auto; min-width: 0; }

/* The clock: a ring draining beside the number it counts. One is read at a
   glance without looking, the other when you want to know exactly. */
@property --sweep { syntax: '<percentage>'; inherits: false; initial-value: 100%; }
.buzzdemo .bd-timer {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 9px;
  font-family: var(--font-display); font-weight: 800; color: var(--ink);
}
.buzzdemo .bd-ring {
  width: 28px; height: 28px; border-radius: 50%; flex: none;
  --sweep: 100%;
  background: conic-gradient(var(--gold) var(--sweep), rgba(255, 255, 255, 0.10) 0);
  -webkit-mask: radial-gradient(closest-side circle, transparent calc(100% - 5px), #000 calc(100% - 5px));
          mask: radial-gradient(closest-side circle, transparent calc(100% - 5px), #000 calc(100% - 5px));
}
.buzzdemo .bd-secs { font-size: 21px; color: var(--gold); font-variant-numeric: tabular-nums; }
.buzzdemo .bd-secslabel {
  font-size: 10px; letter-spacing: 0.2em; text-transform: uppercase; color: var(--ink-dim);
}

/* The prompt line moved INSIDE the pane, so it sits on purple rather than on
   gold — which is why it stops being brown text on a panel. */
.buzzdemo .bd-meta { flex: 0 0 auto; color: var(--ink-dim); margin: 0; }

/* The pane's floor plan, buzz beat and reveal alike: the question floats
   centred in the upper room — one auto margin above it, one above whatever
   holds the floor — and the bottom line stays put. In the buzz beat that
   bottom line is the clock with "Tap to buzz in" under it; in the reveal it is
   the verdict ("Nobody buzzed", "Correct · 2.40s") standing exactly where the
   prompt just was, so the player's eye never has to move between beats.
   Answering keeps its centred stack and the end screen has its own zoning. */
.buzzdemo:not(.answering):not(.over) .bd-timer { margin-top: auto; }
.buzzdemo:not(.answering):not(.over) .bd-q { margin-top: auto; }
.buzzdemo.answered:not(.over) .bd-meta { margin-top: auto; }
.buzzdemo .bd-meta.yes { color: var(--teal); }
.buzzdemo .bd-meta.nope { color: var(--coral); }

.buzzdemo .bd-q {
  margin: 0;
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(17px, 2.4vw, 22px); line-height: 1.25;
  color: var(--ink);
  text-wrap: balance;
}
.buzzdemo .bd-a {
  margin: 0; min-height: 1.3em;
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(15px, 2vw, 19px);
  letter-spacing: 0.04em;
  color: var(--teal);
  opacity: 0; transform: translateY(4px);
  transition: opacity 0.28s ease, transform 0.28s ease;
}
/* The answer is the payoff of the round, so it is the biggest thing in the
   pane. Solid gold, LIT rather than painted: this carried a gold-into-violet
   gradient for one commit, and text that changes colour halfway down reads as
   an artefact before it reads as a flourish — worse, its transparent text-fill
   bled through every later colour override, so the end screen inherited it
   too. The glow is the flourish now. */
.buzzdemo .bd-a.shown {
  opacity: 1; transform: none;
  font-size: clamp(28px, 6vw, 42px); line-height: 1.05;
  font-family: var(--font-display); font-weight: 800; letter-spacing: 0.01em;
  color: var(--gold);
  text-shadow: 0 0 24px rgba(245, 182, 63, 0.4), 0 3px 10px rgba(24, 12, 2, 0.35);
}

/* ── the button, and nothing else ─────────────────────────────────────────
   There was a podium here. It was the largest object in the section and it
   carried no information — the nameplate and the score were tiny and lost on
   it, and a big flat slab competed with the button for attention. The button IS
   the product moment; everything around it should be getting out of its way.

   What survives is the one thing the podium was genuinely doing: a pool of
   light under the button so it is standing on something rather than floating.
   That is a gradient, not an object. */
.buzzdemo .bd-station {
  position: relative;
  display: flex; justify-content: center;
  padding-bottom: clamp(16px, 2.6vw, 26px);
}
.buzzdemo .bd-station::after {
  content: ''; position: absolute; z-index: 0; pointer-events: none;
  left: 50%; bottom: 0;
  width: clamp(250px, 64vw, 360px); height: clamp(38px, 7vw, 54px);
  transform: translateX(-50%);
  background: radial-gradient(ellipse 50% 50% at 50% 50%,
    rgba(232, 67, 74, 0.34), rgba(232, 67, 74, 0.08) 46%, transparent 74%);
}

/* ── the buzzer ───────────────────────────────────────────────────────────
   A plunger seated in a housing. Every state is the same object in a different
   colour, so the four shades live in custom properties and the states swap only
   those — writing the gradient and three shadows out per state is how they
   drift apart from each other. */
.buzzdemo .bd-station .bd-socket {
  position: relative;
  display: grid; place-items: center;
  /* Only just bigger than the plunger. The gap between the two IS the housing,
     and a wide one reads as a button sitting in a bowl rather than a button
     mounted in a panel. */
  /* Sized like the buzzer in the actual game, which is as big as the screen
     will let it be. The old pair had a clamp EACH, so the housing gap — the
     thing that makes it read as mounted rather than resting — grew and shrank
     with the viewport. One clamp here and a percentage on the plunger means the
     gap is the same proportion of the assembly at every width. */
  /* Width decides, and aspect-ratio follows. `max-height: 100%` alongside
     aspect-ratio is a fight the height wins — it shrank a 300px socket to 215
     and left it looking lost in a taller case. The panel is tall enough for
     this now, so the socket simply gets to be its own size. */
  width: clamp(240px, 56vw, 340px);
  aspect-ratio: 1;
  border-radius: 50%;
  /* The housing the plunger is mounted into. Dark, and lit from below rather
     than above — the inside of a cup catches light on its FAR wall, which is
     the bottom of the ellipse from where you are standing. That one inversion
     is the difference between a hole and a bump. */
  background: radial-gradient(circle at 50% 46%, #05030c 46%, #171034 78%, #241a4a 100%);
  box-shadow:
    inset 0 8px 16px -6px rgba(0, 0, 0, 0.95),
    inset 0 -6px 12px -6px rgba(192, 123, 255, 0.4),
    /* The bezel: a drawn purple ring around the housing. It reads as the collar
       the mechanism is fixed to, and on gold it is the one edge that separates
       the whole assembly from the panel it is mounted in. */
    0 0 0 clamp(2px, 0.45vw, 3px) #8b4ce0,
    0 0 0 clamp(3px, 0.6vw, 4px) rgba(20, 8, 40, 0.55),
    0 0 22px -4px rgba(139, 76, 224, 0.55);
}
.buzzdemo .bd-buzz {
  --bz-hi:   #ff8f8b;
  --bz-mid:  #f0555a;
  --bz-low:  #d3323b;
  --bz-side: #a41f28;
  --bz-glow: 232, 67, 74;

  /* Nearly fills its housing — matched to the real player buzzer (92%): a
     slim shadow gap, not a bowl. */
  width: 92%;
  aspect-ratio: 1;
  border: 0; border-radius: 50%;
  cursor: pointer;
  padding: 0;
  display: grid; place-items: center;
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(26px, 6.4vw, 40px);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-on-buzz);
  -webkit-user-select: none; user-select: none;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  background: radial-gradient(circle at 38% 28%, var(--bz-hi), var(--bz-mid) 52%, var(--bz-low));
  box-shadow:
    0 10px 0 var(--bz-side),
    0 16px 34px -10px rgba(var(--bz-glow), 0.7),
    inset 0 -8px 18px rgba(0, 0, 0, 0.24);
  position: relative; z-index: 1;
  transition: transform 0.09s ease, box-shadow 0.09s ease;
}
.buzzdemo .bd-buzz:active {
  /* Travels exactly as far as its own side is deep, so it seats into the
     housing instead of sinking through it. */
  transform: translateY(10px);
  box-shadow:
    0 0 var(--bz-side),
    0 6px 16px -8px rgba(var(--bz-glow), 0.7),
    inset 0 -8px 18px rgba(0, 0, 0, 0.24);
}
.buzzdemo .bd-buzz:disabled { cursor: default; }

/* Armed and waiting. A slow breath rather than a flash — it has to read as live
   without becoming the thing you look at while trying to read the question. */
@keyframes buzzIdle {
  0%, 100% {
    box-shadow: 0 10px 0 var(--bz-side),
                0 16px 32px -12px rgba(var(--bz-glow), 0.5),
                inset 0 -8px 18px rgba(0, 0, 0, 0.24);
  }
  50% {
    box-shadow: 0 10px 0 var(--bz-side),
                0 20px 50px -8px rgba(var(--bz-glow), 0.9),
                inset 0 -8px 18px rgba(0, 0, 0, 0.24);
  }
}
.buzzdemo .bd-buzz.armed { animation: buzzIdle 2.3s ease-in-out infinite; }

/* The strike. Down its own side depth, then a judder — a real buzzer is a
   spring under a dome, and the rattle after the hit is most of what makes
   pressing one satisfying. */
@keyframes buzzHit {
  0%   { transform: translate(0, 10px) scale(0.97); }
  35%  { transform: translate(-4px, 2px) scale(1.02); }
  52%  { transform: translate(4px, 4px) scale(0.99); }
  68%  { transform: translate(-2px, 0) scale(1.01); }
  84%  { transform: translate(2px, 1px) scale(1); }
  100% { transform: none; }
}
.buzzdemo .bd-buzz.hit { animation: buzzHit 0.36s cubic-bezier(.3, .9, .3, 1) both; }

/* In. Same object, green, breathing harder than it did while it waited. */
.buzzdemo .bd-buzz.in,
.buzzdemo .bd-buzz.right {
  --bz-hi:   #a6f5bd;
  --bz-mid:  #3ecb74;
  --bz-low:  #21a355;
  --bz-side: #146b38;
  --bz-glow: 62, 203, 116;
}
.buzzdemo .bd-buzz.in:not(.hit) { animation: buzzIdle 1.3s ease-in-out infinite; }
.buzzdemo .bd-buzz.right,
.buzzdemo .bd-buzz.wrong { animation: none; }

@media (prefers-reduced-motion: reduce) {
  .buzzdemo .bd-buzz.armed,
  .buzzdemo .bd-buzz.in:not(.hit),
  .buzzdemo .bd-buzz.hit { animation: none; }
}
.buzzdemo .bd-buzz:focus-visible { outline: 3px solid var(--gold); outline-offset: 6px; }

/* The ONE running line. Tap to buzz in, Answer — 6s, Correct · 1.24s, Nobody
   buzzed — every state of the round says its piece here rather than half of it
   above the pill and half below. A fixed height, not a minimum, so a longer
   message cannot push the panel taller. */
/* GOLD. These three colours were picked when this line sat ON the gold panel —
   a dark brown, a deep red and a forest green, all correct there and all
   unreadable now that the line lives inside the purple pane. It was
   brown-on-brown on the one line that tells you what to do next. */
.buzzdemo .bd-meta {
  margin: 0; height: 1.6em;
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(17px, 2.5vw, 22px); letter-spacing: 0.08em;
  color: var(--gold);
  text-shadow: 0 0 18px rgba(245, 182, 63, 0.35);
}
.buzzdemo .bd-meta.hot { color: #ff8f8b; text-shadow: 0 0 18px rgba(232, 67, 74, 0.4); }
.buzzdemo .bd-meta.yes { color: #6ee7b0; text-shadow: 0 0 18px rgba(62, 207, 178, 0.4); }
.buzzdemo .bd-meta.nope { color: #ff8f8b; text-shadow: 0 0 18px rgba(232, 67, 74, 0.4); }

/* Four bolts, one per corner, holding the panel to whatever is behind it. Drawn
   as one element with four gradient layers rather than four spans — they are a
   single detail, not four objects, and one element cannot drift out of
   alignment with itself.

   Each is a dark countersink with a bright rim on its UPPER edge and a shadow
   below: light from above, which is where the light on this page comes from.
   Reverse those two and the bolt pops out of the panel instead of into it. */
.buzzdemo .bd-bolts {
  position: absolute; inset: clamp(9px, 1.4vw, 14px);
  pointer-events: none;
  border-radius: inherit;
  /* Each bolt is confined by its background-SIZE, not by its own last colour
     stop. Relying on a trailing `transparent` stop to keep the gradient out of
     the rest of the element does not survive Safari — it filled the whole panel
     with the bolt's cream highlight and turned the gold to buttermilk. A 12px
     tile with no-repeat cannot do that whatever the stops say. */
  background-image:
    radial-gradient(circle, #2c1a04 0 46%, rgba(255, 246, 220, 0.7) 52% 62%, rgba(255, 246, 220, 0) 74%),
    radial-gradient(circle, #2c1a04 0 46%, rgba(255, 246, 220, 0.7) 52% 62%, rgba(255, 246, 220, 0) 74%),
    radial-gradient(circle, #2c1a04 0 46%, rgba(255, 246, 220, 0.7) 52% 62%, rgba(255, 246, 220, 0) 74%),
    radial-gradient(circle, #2c1a04 0 46%, rgba(255, 246, 220, 0.7) 52% 62%, rgba(255, 246, 220, 0) 74%);
  background-size: 12px 12px;
  background-position: 0 0, 100% 0, 0 100%, 100% 100%;
  background-repeat: no-repeat;
}

/* Sound off. Small, in the corner, and out of the way — but always present
   rather than appearing after the first noise, because a control you go looking
   for AFTER being startled is a control that arrived too late. The choice is
   remembered, so nobody has to press it twice. */
.buzzdemo .bd-mute {
  position: absolute; top: 8px; right: 10px;
  width: 30px; height: 30px;
  display: grid; place-items: center;
  border: 0; border-radius: 8px; cursor: pointer;
  background: rgba(58, 34, 6, 0.14);
  opacity: 0.6;
  transition: opacity 0.2s ease, background 0.2s ease;
}
.buzzdemo .bd-mute:hover { opacity: 1; background: rgba(58, 34, 6, 0.24); }
.buzzdemo .bd-mute:focus-visible { outline: 3px solid #3d2a55; outline-offset: 2px; opacity: 1; }
/* One icon, two states: the waves and the cross swap rather than the whole
   glyph, so nothing shifts by a pixel between muted and not. */
.buzzdemo .bd-mute svg {
  width: 17px; height: 17px;
  fill: rgba(48, 27, 4, 0.9);
  stroke: rgba(48, 27, 4, 0.9);
  stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round;
}
.buzzdemo .bd-mute svg .on,
.buzzdemo .bd-mute svg .off { fill: none; }
.buzzdemo .bd-mute svg .off { display: none; }
.buzzdemo .bd-mute.muted svg .on { display: none; }
.buzzdemo .bd-mute.muted svg .off { display: inline; }

/* Held seats. Each of these comes and goes through a round, and each needs a
   display of its OWN for visibility to reserve anything: `hidden` carries
   display:none from the UA stylesheet, and only a page rule outranks it.
   Without them the answer pill sat on screen the whole time, including under
   "Nobody buzzed". */
.buzzdemo .bd-best { flex: 0 0 auto; }
.buzzdemo .bd-best.__old { display: block; }
/* The answer pill leaves the layout entirely when it is hidden. It used to
   hold its seat with visibility, but a 60px phantom under the question ruins
   the buzz beat's centring, and the seat no longer needs holding — entering
   the answering beat regrows the whole pane anyway, so there is no static
   layout to protect. */
.buzzdemo .bd-answer[hidden] { display: none; }
.buzzdemo .bd-round[hidden],
.buzzdemo .bd-best[hidden] { visibility: hidden; }
.buzzdemo .bd-round { height: 1.5em; }
.buzzdemo .bd-best  { height: 1.7em; }

/* The round line. It only exists once you have answered something — a counter
   sitting at zero before you have played is a scoreboard for nothing. */
.buzzdemo .bd-round {
  margin: 0; width: 100%;
  display: flex; align-items: center; justify-content: center;
  gap: clamp(10px, 2vw, 18px);
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(11px, 1.5vw, 13px); letter-spacing: 0.2em;
  color: rgba(58, 34, 4, 0.62);
}
.buzzdemo .bd-round b { letter-spacing: 0.06em; font-size: clamp(13px, 1.8vw, 15px); }
.buzzdemo .bd-round b.ok { color: #0d5f3a; }
.buzzdemo .bd-round b.no { color: #8c1520; }

/* The answer field. Sits between the prompt and the clock, which is the order
   the real game runs in — you buzz first and answer second.

   The [hidden] rules are load-bearing, not defensive. `hidden` hides an element
   through the UA stylesheet's `display: none`, and any display declared in a
   page stylesheet outranks it — so a flex row with a hidden attribute stays
   visible. It did: the answer field sat there through the reveal and the end
   of the round until this was added. */
/* The slot both occupy. A fixed height so swapping one for the other cannot
   move anything, and both absolutely positioned inside it so they genuinely
   share the space rather than stacking. */
.buzzdemo .bd-slot {
  position: relative;
  width: 100%;
  height: 52px;
}
/* ONE object, not three. An input, a mic and a button sitting side by side on
   gold read as three small dark shapes; the same three inside a single pill
   read as a thing you type into. The buttons are round because the pill is
   round — a square control inside a capsule is the detail that gives it away. */
/* NOT absolute. `position: absolute; inset: 0` was how this filled the old
   fixed-height slot, and it is the real reason the last attempt produced an
   oval: inset:0 stretches to the whole positioned ancestor, so the taller the
   pane got, the taller the pill grew. It sits in the flow now and states its
   own height above. That one declaration cost two attempts. */
.buzzdemo .bd-answer {
  position: relative;
  display: flex; align-items: center; gap: 6px;
  padding: 5px 5px 5px 6px;
  border-radius: 999px;
  background: #1b1134;
  box-shadow:
    inset 0 0 0 2px rgba(58, 34, 6, 0.55),
    inset 0 2px 6px rgba(0, 0, 0, 0.5);
}
.buzzdemo .bd-input {
  flex: 1; min-width: 0;
  padding: 0 14px;
  border: 0; background: none;
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 18px;              /* under 16px and iOS zooms the page on focus */
  text-align: center;
}
.buzzdemo .bd-input::placeholder { color: rgba(233, 226, 255, 0.4); }
.buzzdemo .bd-input:focus { outline: none; }
/* The focus ring goes on the PILL, so the whole object lights rather than a
   field inside it — otherwise the outline draws a rectangle in a capsule. */
.buzzdemo .bd-answer:focus-within {
  box-shadow:
    inset 0 0 0 2px var(--gold-hot),
    inset 0 2px 6px rgba(0, 0, 0, 0.5),
    0 0 0 4px rgba(245, 182, 63, 0.28);
}
/* The send. Its own object under the clock rather than a 38px circle hiding
   inside the pill — beating the clock with a mouse meant hunting a thumbnail
   while the seconds ran. Same grammar as the buzzer: gold, seated on a side
   shadow, and it travels exactly as deep as that shadow when pressed. Only in
   the answering beat does it exist at all, which is why visibility is CSS on
   the root class instead of another [hidden] toggle for the JS to forget. */
.buzzdemo .bd-send {
  display: none;
  flex: 0 0 auto;
  width: 100%; max-width: 420px; height: 58px;
  border: 0; border-radius: 999px; cursor: pointer;
  font-family: var(--font-display); font-weight: 800;
  font-size: 19px; letter-spacing: 0.14em; text-transform: uppercase;
  color: #241a05;
  background: linear-gradient(180deg, var(--gold-hot), var(--gold));
  box-shadow: 0 5px 0 #a9741c, 0 16px 28px -14px rgba(20, 8, 40, 0.7);
  transition: transform 0.09s ease, background 0.2s ease, box-shadow 0.09s ease;
}
.buzzdemo.answering .bd-send { display: block; }
.buzzdemo .bd-send:hover { background: linear-gradient(180deg, #ffe09a, var(--gold)); }
/* Travels its own depth, like the buzzer does. */
.buzzdemo .bd-send:active {
  transform: translateY(5px);
  box-shadow: 0 0 0 #a9741c, 0 8px 16px -10px rgba(20, 8, 40, 0.6);
}
.buzzdemo .bd-send:focus-visible { outline: 3px solid var(--gold); outline-offset: 3px; }

/* The answer clock. A bar as well as a number, because a shrinking bar is read
   without looking at it — which is the point when the thing you are supposed to
   be doing is thinking of the answer. */
.buzzdemo .bd-clock {
  width: 82%; height: 6px; border-radius: 6px;
  background: rgba(255, 255, 255, 0.06);
  overflow: hidden;
  opacity: 0;
  transition: opacity 0.2s ease;
}
.buzzdemo .bd-clock.running { opacity: 1; }
.buzzdemo .bd-clock i {
  display: block; height: 100%; width: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--gold), #f0555a);
}

/* Round over: the button has nothing left to do, so it stops asking to be
   pressed rather than sitting there greyed out and confusing. */
.buzzdemo.over .bd-station { display: none; }
/* Where the round lands. The primary action is gold — the same button as the
   page's main call to action — because this is the same ask, arriving at the
   one moment the visitor has actually played the thing. */
.buzzdemo .bd-end {
  width: 100%;
  display: flex; flex-direction: column; align-items: center;
  gap: 10px;
}
.buzzdemo .bd-end[hidden] { display: none; }
.buzzdemo .bd-end .btn {
  width: 100%; text-align: center;
  background: linear-gradient(180deg, #3d2a55, #1e1033);
  border-color: #1e1033;
  color: #ffe9bb;
  box-shadow: 0 10px 24px -12px rgba(20, 8, 40, 0.8);
}
.buzzdemo .bd-end .btn:hover { background: linear-gradient(180deg, #4d3768, #2a1544); }
.buzzdemo .bd-again {
  border: 0; background: none; cursor: pointer;
  padding: 4px 8px;
  font-family: var(--font-display); font-weight: 700;
  font-size: 13px; letter-spacing: 0.1em;
  color: rgba(58, 34, 4, 0.7);
  text-decoration: underline; text-underline-offset: 3px;
}
.buzzdemo .bd-again:hover { color: #2a1803; }
.buzzdemo .bd-again:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
/* The pane takes the whole panel: with the buzzer gone, a fixed-height card
   left the bottom half of the machine as empty gold. The result deserves the
   stage the round was played on. */
.buzzdemo.over .bd-card { flex-grow: 1; }
.buzzdemo.over .bd-q { font-size: clamp(26px, 4.2vw, 38px); }
/* Golden, flat, and readable — the verdict line is a sentence, so it gets the
   body font and none of the reveal's glow. */
.buzzdemo.over .bd-a {
  color: #ffdf9e;
  font-family: var(--font-body); font-weight: 400;
  font-size: clamp(16px, 2.2vw, 20px); line-height: 1.5;
  max-width: 36ch; text-wrap: pretty;
  text-shadow: none;
}
/* The one number this demo can honestly report: reaction time is exactly what
   the engine measures to the millisecond, so it is the true stat rather than an
   invented score. It lives in the pane with the verdict now, coloured for
   purple rather than the gold it used to sit on, and only exists on the end
   screen — mid-round the display:none keeps the early un-hide in buzz() from
   leaking it into the pane. */
.buzzdemo .bd-best {
  flex: 0 0 auto;
  display: none;
  margin: 0;
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(11px, 1.5vw, 13px); letter-spacing: 0.22em;
  color: var(--ink-dim);
}
/* Pushed to the pane's floor: the score and its verdict hold the middle, and
   the stat reads as the footnote it is. */
.buzzdemo.over .bd-best { display: block; margin-top: auto; }
.buzzdemo.over .bd-q { margin-top: auto; }
.buzzdemo .bd-best b {
  color: var(--gold); letter-spacing: 0.02em;
  font-size: clamp(14px, 1.9vw, 17px); font-variant-numeric: tabular-nums;
  margin-left: 6px;
}
/* Settles to gold — `both` keeps the end frame, and the stat sits on the
   purple pane now, where gold is the readable colour. */
@keyframes bestFlash {
  0%   { color: #fff6e2; text-shadow: 0 0 18px rgba(245, 182, 63, 0.9); }
  100% { color: var(--gold); text-shadow: none; }
}
.buzzdemo .bd-best b.flash { animation: bestFlash 0.7s ease both; }

/* Motion off: the answer simply appears, and the button does not travel. */
@media (prefers-reduced-motion: reduce) {
  .buzzdemo .bd-a { transition: none; }
  .buzzdemo .bd-buzz { transition: none; }
  .buzzdemo .bd-buzz:active { transform: none; }
  .buzzdemo .bd-send:active { transform: none; }
}

.hero .sub {
  color: var(--ink-dim);
  font-size: clamp(16px, 2.4vw, 20px);
  line-height: 1.5;
  max-width: 56ch;
  margin: 16px auto 0;
  text-wrap: pretty;
}
.hero .hero-orb {
  width: 108px;
  height: 108px;
  margin: 0 auto 4px;
  filter: drop-shadow(0 12px 28px rgba(6, 4, 20, 0.6));
}

/* A rule between the three claims. Fading out at both ends rather than stopping
   dead, because a hard-ended line on a page with no other straight edges reads
   as a border someone forgot to remove. */
.sectionbreak {
  height: 1px; border: 0;
  width: min(420px, 70%);
  margin: clamp(34px, 5vw, 68px) auto;
  background: linear-gradient(90deg,
    transparent, rgba(245, 182, 63, 0.5) 22%, rgba(245, 182, 63, 0.5) 78%, transparent);
}

/* ── the way in ───────────────────────────────────────────────────────────
   The counterpart to the buzzer panel: same bounded shape, opposite value. A
   second bright gold box under a bright gold box would have been two things
   shouting, so this one is dark with a gold edge — same family, not the same
   claim on the eye. */
.startbox {
  max-width: 520px;
  margin: clamp(20px, 2.6vw, 32px) auto 0;
  padding: clamp(20px, 2.8vw, 28px) clamp(18px, 3vw, 30px) clamp(18px, 2.6vw, 24px);
  border-radius: clamp(16px, 2vw, 22px);
  background: linear-gradient(180deg, rgba(245, 182, 63, 0.09), rgba(255, 255, 255, 0.015));
  box-shadow:
    inset 0 1px 0 rgba(255, 238, 196, 0.28),
    inset 0 0 0 2px rgba(245, 182, 63, 0.55),
    0 0 40px -18px rgba(245, 182, 63, 0.5),
    0 26px 54px -30px rgba(4, 2, 16, 0.9);
}
/* Each control on its own line. Side by side they were three small things
   sharing a row and none of them looked like the main way in; stacked and full
   width, each one is a decision on its own. */
.startbox .joinrow {
  margin: 0;
  flex-direction: column;
  align-items: stretch;
  gap: clamp(10px, 1.6vw, 14px);
}
.startbox .joinrow > * { width: 100%; }
/* Both actions carry gold now. Start a game is the filled one because it is
   what most people should do; Join with a code is outlined in the same gold so
   the pair reads as two ways in rather than one button and one afterthought. */
.startbox .joinrow .btn {
  padding: 19px 24px;
  font-size: clamp(18px, 2.4vw, 21px);
  letter-spacing: 0.06em;
}
.startbox .joinrow .btn-ghost {
  background: linear-gradient(180deg, var(--gold-hot), var(--gold));
  border-color: var(--gold);
  color: var(--ink-on-gold);
  box-shadow: 0 4px 0 #a9741c, 0 14px 30px -14px rgba(245, 182, 63, 0.7);
}
.startbox .joinrow .btn-ghost:hover { background: linear-gradient(180deg, #ffe09a, var(--gold)); }
.startbox .joinrow .btn-ghost:active { transform: translateY(4px); box-shadow: 0 0 0 #a9741c; }
.startbox .joinrow #joinbtn {
  background: rgba(245, 182, 63, 0.1);
  border: 2px solid var(--gold);
  color: var(--gold-hot);
}
.startbox .joinrow #joinbtn:hover { background: rgba(245, 182, 63, 0.2); }
/* The line that says who the code is for. Between the two actions on purpose:
   Start a game is for the person with the room, everything under this is for
   the person who was invited to it. Two different people, and the split says so
   without a heading. */
.startbox .joinhint {
  margin: clamp(6px, 1.2vw, 12px) 0 2px;
  font-size: clamp(13px, 1.7vw, 15px);
  line-height: 1.4;
  color: var(--ink-dim);
  text-wrap: pretty;
}
/* The code slot, four times the height it was. A four-letter code deserves to
   look like the four-letter code on a game show board rather than a text field
   that happens to accept four characters — and at this size it is unmissable as
   the thing to do if somebody sent you one. */
/* Gold, like every other piece of type that matters on this page. The field
   inherited a muted placeholder built for a dark form; at this size it is not a
   form field any more, it is a sign with four letters on it. */
.startbox .joinrow .field { color: var(--gold); }
.startbox .joinrow .field::placeholder {
  color: rgba(245, 182, 63, 0.55);
}
.startbox .joinrow .field {
  width: 100%;
  min-height: clamp(132px, 26vw, 196px);
  padding: 10px 16px;
  font-size: clamp(40px, 9vw, 68px);
  letter-spacing: 0.28em;
  text-indent: 0.28em;
  text-align: center;
  border-radius: 14px;
  /* Recessed, so it reads as a slot to fill rather than a button to press —
     everything else in this box is raised. */
  box-shadow:
    inset 0 3px 10px rgba(4, 2, 14, 0.7),
    inset 0 0 0 1px rgba(192, 123, 255, 0.22);
}
.startbox .cta-note { margin: clamp(14px, 1.8vw, 18px) 0 0; }

.cta-row {
  display: flex;
  gap: 12px;
  justify-content: center;
  flex-wrap: wrap;
  margin: 30px 0 0;
}
.cta-row .btn { font-size: 20px; padding: 15px 30px; }
.cta-note { color: var(--ink-faint); font-size: 13px; margin: 12px 0 0; }

/* join strip */
.joinrow {
  display: flex;
  gap: 10px;
  justify-content: center;
  align-items: stretch;
  flex-wrap: wrap;
  margin: 26px 0 0;
}
.joinrow .field {
  width: 168px;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 24px;
  letter-spacing: 0.28em;
  text-indent: 0.28em;
  text-transform: uppercase;
  text-align: center;
}
.joinrow .btn { font-size: 16px; }
/* It carries Start a game, the code and Join with a code on one line now, so
   the two buttons have to match each other rather than the field's face. */
.hero .joinrow .btn-ghost { font-size: 16px; }

/* the glowing floor line under the hero */
.hero-rail {
  height: 3px;
  max-width: 520px;
  margin: 44px auto 0;
  border-radius: var(--r-pill);
  background: repeating-linear-gradient(90deg, var(--gold) 0 10px, transparent 10px 26px);
  opacity: 0.5;
}

/* ── how a game night works ───────────────────────────────────────────────
   Three beats, one at a time, each a single claim with a single picture. The
   section's whole argument is that this is easy, and a section that needs three
   paragraphs to say so is arguing against itself.

   They alternate sides on wide screens and stack on narrow ones. Alternating
   matters more than it looks: three identical rows read as a table of features,
   where three that swap sides read as a sequence you are moving through. */
/* Centred, and at claim size rather than label size. It is the heading of a
   section, not a tag on top of one. Gold with the two words that name the thing
   in white — the same device the hero uses, colours swapped, so it reads as
   part of the same family. */
/* .band h2.night-head, not .night-head alone. .band h2 is (0,1,1) and this was
   (0,1,0), so the section's generic heading rule was winning and quietly
   putting back its own margin and size — the heading was neither centred nor
   the size it is declared at. */
.band h2.night-head {
  text-align: center;
  margin: 0 auto;
  max-width: 18ch;
  font-size: clamp(32px, 6vw, 58px);
  line-height: 1.0;
  color: var(--gold);
  text-wrap: balance;
  text-shadow: 0 2px 0 rgba(4, 2, 14, 0.5), 0 0 26px rgba(245, 182, 63, 0.35);
}
/* Three lines, and the break is the em rather than a <br> — the copy and the
   line break cannot drift apart when the wording changes. */
.band h2.night-head em { font-style: normal; display: block; color: #fff; }
/* A name sitting MID-sentence stays on its line. The block break is right when
   the em is the payoff at the end — "One more / go" — and wrong when it is a
   noun in the middle, which turned "If Trivia lands, try these next" into three
   ragged lines with the word Trivia stranded on its own. */
.band h2.night-head.inline em { display: inline; }

.night { display: grid; gap: clamp(56px, 9vw, 128px); margin-top: clamp(34px, 5vw, 64px); }
.night-step {
  display: grid;
  gap: clamp(20px, 3.4vw, 48px);
  align-items: center;
}
/* Stacked at EVERY width, picture then words. The alternating two-column
   version read as a features table on a wide screen; one column you scroll
   straight down is the same rhythm as the hero, and the whole point of this
   section is that the thing is simple.

   Held to a reading column and centred, so the picture and the words stay in
   the same measure instead of drifting to opposite ends of a wide page. */
.night-step { max-width: 620px; margin-inline: auto; width: 100%; }
.night-n {
  display: block;
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(11px, 1.5vw, 13px);
  letter-spacing: 0.32em; text-transform: uppercase;
  color: rgba(245, 182, 63, 0.75);
  margin-bottom: 10px;
}
.night-say h3 {
  margin: 0 0 12px;
  font-size: clamp(28px, 4.6vw, 46px);
  line-height: 1.0;
  text-wrap: balance;
}
/* The payoff word in gold on its own line, the same device the hero uses. Three
   sections into the page it is no longer a flourish, it is how this site says
   "this is the important word". */
.night-say h3 em { font-style: normal; display: block; color: var(--gold); }
.night-say h3 span { display: block; }
.night-say { text-align: center; }
.night-say p {
  margin: 0 auto;
  color: var(--ink-dim);
  font-size: clamp(15px, 2vw, 18px);
  line-height: 1.55;
  max-width: 42ch;
  text-wrap: pretty;
}

/* The pictures. All CSS — they restyle with the theme, weigh nothing, and stay
   crisp at any size. */
.night-art { display: grid; place-items: center; }

.art-tv {
  width: min(340px, 100%);
  aspect-ratio: 16 / 10;
  border-radius: 14px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px;
  background: linear-gradient(180deg, #1d1840, #12102a);
  box-shadow:
    inset 0 0 0 1px rgba(233, 226, 255, 0.14),
    inset 0 0 34px -8px rgba(192, 123, 255, 0.4),
    0 0 0 clamp(6px, 1vw, 10px) #0d0a1c,
    0 26px 44px -22px rgba(4, 2, 16, 0.9);
}
.art-tv-code {
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(38px, 6vw, 56px);
  letter-spacing: 0.24em; text-indent: 0.24em;
  color: var(--gold);
  text-shadow: 0 0 26px rgba(245, 182, 63, 0.45);
}
.art-tv-say {
  font-size: 12px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink-faint);
}

.art-phones { display: flex; gap: clamp(10px, 1.6vw, 16px); align-items: flex-end; }
.art-phone {
  width: clamp(56px, 8vw, 74px);
  aspect-ratio: 9 / 17;
  border-radius: 12px;
  padding: 6px;
  display: grid; place-items: center;
  background: linear-gradient(158deg, #3b3752, #16131f);
  box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, 0.18), 0 14px 24px -14px rgba(4, 2, 16, 0.9);
}
/* The middle one is a beat taller — three identical phones read as an icon,
   where one standing proud reads as three people holding them. */
.art-phone:nth-child(2) { transform: translateY(-12px); }
.art-phone i {
  width: 74%; aspect-ratio: 1; border-radius: 50%;
  background: radial-gradient(circle at 38% 28%, #ff8f8b, #f0555a 52%, #d3323b);
  box-shadow: 0 3px 0 #a41f28, 0 0 18px -2px rgba(232, 67, 74, 0.7);
}

.art-clock {
  position: relative;
  width: clamp(150px, 22vw, 210px);
  aspect-ratio: 1;
  border-radius: 50%;
  display: grid; place-items: center; align-content: center; gap: 2px;
  background: radial-gradient(circle at 50% 40%, #1d1840, #100e26);
  box-shadow:
    inset 0 0 0 2px rgba(245, 182, 63, 0.5),
    inset 0 0 40px -12px rgba(245, 182, 63, 0.5),
    0 0 46px -18px rgba(245, 182, 63, 0.6);
  /* The dial warms as the minute runs out — go, then warning, then out of time.
     SAME period and same start as the hand, so the colour and the hand can
     never disagree about how much is left; only the easing differs, because a
     clock TICKS but pressure BUILDS. Two of the four stops are the brand's own
     gold and buzzer red, so the ring passes through the palette on its way from
     green to stop rather than importing a traffic light. */
  animation: dial 60s linear infinite;
}
@keyframes dial {
  0% {
    box-shadow:
      inset 0 0 0 2px rgba(53, 208, 127, 0.62),
      inset 0 0 40px -12px rgba(53, 208, 127, 0.5),
      0 0 46px -18px rgba(53, 208, 127, 0.62);
  }
  40% {
    box-shadow:
      inset 0 0 0 2px rgba(245, 182, 63, 0.6),
      inset 0 0 40px -12px rgba(245, 182, 63, 0.5),
      0 0 46px -18px rgba(245, 182, 63, 0.6);
  }
  72% {
    box-shadow:
      inset 0 0 0 2px rgba(255, 157, 77, 0.68),
      inset 0 0 40px -12px rgba(255, 157, 77, 0.52),
      0 0 46px -18px rgba(255, 157, 77, 0.66);
  }
  100% {
    box-shadow:
      inset 0 0 0 2px rgba(232, 67, 74, 0.78),
      inset 0 0 44px -12px rgba(232, 67, 74, 0.55),
      0 0 52px -16px rgba(232, 67, 74, 0.72);
  }
}
/* Reduced motion keeps the dial still — and gold, the colour it reads as when
   nothing is running, rather than frozen mid-warning. */
@media (prefers-reduced-motion: reduce) { .art-clock { animation: none; } }
.art-clock span {
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(52px, 8vw, 78px); line-height: 1;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}
.art-clock i {
  font-style: normal;
  font-size: 11px; letter-spacing: 0.28em; text-transform: uppercase;
  color: var(--ink-faint);
}
/* A hand that TICKS rather than sweeps. steps(60) is the whole thing: a smooth
   360-degree rotation reads as a loading spinner, where sixty discrete jumps of
   six degrees each reads as a clock — and a clock is the claim. One lap is one
   minute, which is also the claim. */
.art-clock::after {
  content: ''; position: absolute; z-index: 1;
  left: 50%; bottom: 50%;
  width: 2px; height: 34%;
  border-radius: 2px;
  /* Black at the hub, gold at the tip. Reading outward from the centre it goes
     from the dark of the dial to the brightest thing on it, so the eye follows
     the hand out to where it is pointing rather than back in to where it is
     pinned. */
  background: linear-gradient(180deg,
    var(--gold-hot) 0%, var(--gold) 38%, rgba(90, 60, 12, 0.85) 74%, #0b0718 100%);
  box-shadow: 0 0 10px rgba(245, 182, 63, 0.55);
  transform-origin: 50% 100%;
  transform: translateX(-50%);
  animation: tick 60s steps(60, end) infinite;
}
@keyframes tick {
  from { transform: translateX(-50%) rotate(0deg); }
  to   { transform: translateX(-50%) rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) { .art-clock::after { animation: none; } }

/* ── the catalog rail ─────────────────────────────────────────────────────
   Categories, then a horizontal rail of whatever is in the one you pressed.

   Native overflow with scroll-snap rather than a JS carousel: momentum,
   keyboard, trackpad and screen readers all keep working for free, and there is
   no transform to fight when the window resizes. A carousel that reimplements
   scrolling is a carousel that reimplements it worse. */
/* .band .lede.rail-lede — the generic .band .lede is (0,2,0) and sets margin:0,
   so a single-class rule here loses and the centring silently does nothing.
   Third time this file has caught me that way. */
.band .lede.rail-lede { margin-inline: auto; text-align: center; }

/* A grid, not a wrapping flex row. Flex sizes each pill to its own label, so
   eight categories of different name lengths give eight different widths and
   the row reads as ragged. Equal columns make them one set of controls. */
.cats {
  display: grid;
  /* Two per row, four on a wide screen. Fixed counts rather than auto-fit:
     auto-fit packs by available width, so eight pills could land as 2/2/2/2 or
     3/3/2 depending on the viewport, and a ragged last row is what makes a set
     of controls look accidental. */
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
  margin: clamp(22px, 3vw, 32px) auto 0;
  max-width: 760px;
}
@media (min-width: 760px) { .cats { grid-template-columns: repeat(4, 1fr); } }
/* STACKED, matching /new's picker. Side by side the count was 11px at 65%
   opacity trailing the label — and it is the thing that tells you whether a
   category is worth opening. Label above, number below, number biggest. */
.cat {
  --cat-neon: var(--gold);
  display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  padding: 10px 16px;
  border-radius: 18px;
  cursor: pointer;
  border: 1px solid rgba(233, 226, 255, 0.16);
  background: rgba(255, 255, 255, 0.03);
  color: var(--ink-dim);
  font-family: var(--font-display); font-weight: 800;
  font-size: 13px; letter-spacing: 0.1em; text-transform: uppercase;
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}
.cat b {
  font-weight: 800; font-size: 20px; line-height: 1; opacity: 1;
  color: var(--gold); font-variant-numeric: tabular-nums;
}
.cat:hover { color: var(--ink); border-color: var(--cat-neon); }
/* The pressed one is the only lit object in the row. */
.cat.on {
  color: #fff;
  border-color: var(--cat-neon);
  background: color-mix(in srgb, var(--cat-neon) 16%, transparent);
  box-shadow: 0 0 0 1px var(--cat-neon), 0 0 20px -6px var(--cat-neon);
}
/* The lit chip's count stays BRIGHT — it is the number you most want to read,
   and dimming the selected one is exactly backwards. */
.cat.on b { opacity: 1; color: #fff6dc; text-shadow: 0 0 10px var(--cat-neon); }
.cat:focus-visible { outline: 3px solid var(--gold); outline-offset: 3px; }

.cats-blurb {
  margin: 14px auto 0;
  text-align: center;
  color: var(--ink-faint);
  font-size: 14px;
  min-height: 1.4em;
}

/* The rail. Edges fade rather than stop, so it reads as continuing past the
   frame — which is the one thing that tells you it scrolls without a caption
   saying so. */
/* The box everything is measured against: pills, grid and the SVG overlay all
   live in here, and catneon.js reads its geometry. No border of its own — the
   traced tube IS the container, and a static box behind it would be a second
   outline arguing with the one that moves. */
.catwrap {
  position: relative;
  max-width: 920px;
  margin: clamp(20px, 3vw, 30px) auto 0;
}
.neonsvg {
  position: absolute; inset: 0; z-index: 2;
  pointer-events: none; overflow: visible;
}
.np { fill: none; stroke-linejoin: round; stroke-linecap: round; }
/* The soft halo thrown onto the wall behind the sign. */
.np-bloom { stroke-width: 16; opacity: 0.3; filter: blur(10px); }
/* The lit gas itself. */
.np-tube  { stroke-width: 5; opacity: 0.95;
  filter: drop-shadow(0 0 5px currentColor) drop-shadow(0 0 12px currentColor); }
/* The overexposed centre — the trick that stops neon looking like a line. */
.np-core  { stroke-width: 1.5; opacity: 0.9; }

/* Idle: the halo breathes. Real signs are never perfectly still, and a static
   glow reads as a border. */
#neontube .np-bloom { animation: neon-breathe 4.5s ease-in-out infinite; }
@keyframes neon-breathe {
  0%, 100% { opacity: 0.26; }
  50%      { opacity: 0.42; }
}
/* Switching category strikes the tube like a sign powering on. */
#neontube.strike { animation: neon-strike 620ms steps(1, end) 1; }
@keyframes neon-strike {
  0%   { opacity: 0; }
  9%   { opacity: 1; }
  14%  { opacity: 0.15; }
  21%  { opacity: 1; }
  28%  { opacity: 0.4; }
  36%  { opacity: 1; }
  48%  { opacity: 0.7; }
  100% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  #neontube .np-bloom, #neontube.strike { animation: none; }
}

/* All of them on screen at once: three across, four down. A grid you can scan
   beats a carousel you have to operate — every game is visible without anyone
   having to discover that the thing slides. */
.rail {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  /* dense, so the tiles NOT in the lit category backfill the holes the
     silhouette leaves rather than shoving the shape around. */
  grid-auto-flow: row dense;
  gap: clamp(10px, 1.6vw, 16px);
}
/* Four across, because the category silhouettes are authored against a
   four-column grid. Below that catneon falls back to prefix packing on its
   own — placing a tile at column 4 of a 2-column grid would CREATE columns 3
   and 4 and burst the container. */
@media (min-width: 760px) { .rail { grid-template-columns: repeat(4, 1fr); } }

.rtile {
  /* A fixed shape, not one that grows to fit its own words. A grid row sizes to
     its tallest cell, so one two-line tagline was making a whole row taller and
     the tiles stopped matching each other. Equal tiles are also what lets the
     traced tube be one clean shape rather than a stepped one. */
  aspect-ratio: 3 / 4;
  position: relative;
  display: flex; flex-direction: column; justify-content: flex-end;
  align-items: center; text-align: center;
  gap: 4px;
  padding: 16px 14px 18px;
  overflow: hidden;
  border-radius: 16px;
  text-decoration: none;
  background-size: cover; background-position: center;
  /* The house neon build, the same one /new's lit tiles use: a saturated edge,
     an ambient bloom around it, and a hot near-white core inside. The core is
     the part that does the work — a coloured line reads as a border, and a line
     with an overexposed centre reads as lit gas in glass.

     Deliberately no power-on flicker: twelve tiles stuttering at once is a
     strobe, not a sign. */
  --neon: var(--gold);
  border: 1px solid var(--stage-line);
  box-shadow: 0 18px 34px -20px rgba(4, 2, 16, 0.9);
  transition: opacity 0.24s ease, transform 0.18s ease,
              box-shadow 0.24s ease, border-color 0.24s ease;
}
/* A tile in the lit category is a small neon sign in its own right, in the same
   colour as the tube around it. No power-on flicker: twelve tiles stuttering at
   once is a strobe, not a sign. */
.rtile.lit {
  border-color: var(--neon);
  box-shadow:
    0 0 7px -1px var(--neon),
    0 0 22px -7px var(--neon),
    0 18px 34px -20px rgba(4, 2, 16, 0.9);
}
.rtile.lit::after {
  content: ''; position: absolute; inset: 2px; z-index: 2;
  border-radius: inherit; pointer-events: none;
  border: 1px solid color-mix(in srgb, var(--neon) 30%, white);
  opacity: 0.5;
}
/* Outside the lit category they recede to unlit glass but stay legible and
   clickable — they are still games you can read about. */
.rtile.dim { opacity: 0.34; }
.rtile.dim:hover { opacity: 0.85; }
/* The scrim. Without it the art wins and the words are unreadable on half the
   tiles — and which half depends on which artwork, which is not a thing you can
   fix per tile. */
.rtile::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: linear-gradient(180deg,
    rgba(6, 3, 18, 0.15) 0 32%, rgba(6, 3, 18, 0.78) 74%, rgba(6, 3, 18, 0.94) 100%);
}
.rtile > * { position: relative; }
.rtile:hover { transform: translateY(-4px); }
.rtile.lit:hover { box-shadow:
  0 0 10px 0 var(--neon),
  0 0 32px -4px var(--neon),
  0 22px 40px -20px rgba(4, 2, 16, 0.95); }
.rtile.lit:hover::after { opacity: 0.85; }
.rtile:focus-visible { outline: 3px solid var(--gold); outline-offset: 4px; }

.rtile h3 {
  margin: 0;
  font-size: clamp(19px, 2.4vw, 23px);
  line-height: 1.05;
  color: #fff;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.85);
}
.rtile .rline {
  color: var(--ink-dim);
  font-size: 13.5px; line-height: 1.35;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.9);
}
.rtile .rmeta {
  display: flex; justify-content: center; gap: 10px; margin-top: 6px;
  font-family: var(--font-display); font-weight: 800;
  font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--neon);
}
.rtile .rmeta span { color: var(--ink-faint); }

@media (prefers-reduced-motion: reduce) {
  .rtile, .rtile:hover { transition: none; transform: none; }
}

/* ── filter chips (/games) ──────────────────────────────────────────────── */
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 24px; }
.chip {
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 14px;
  padding: 8px 15px;
  border-radius: var(--r-pill);
  border: 1px solid var(--stage-line);
  background: var(--panel-inset);
  color: var(--ink-dim);
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.chip:hover { color: var(--ink); }
.chip[aria-pressed="true"] {
  background: var(--gold);
  border-color: var(--gold);
  color: var(--ink-on-gold);
}
.filter-count { color: var(--ink-faint); font-size: 13px; margin-top: 14px; }

/* ── "why it's different" feature grid ──────────────────────────────────── */
.features {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(256px, 1fr));
  gap: 16px;
  margin-top: 32px;
}
.feature {
  padding: 22px;
  border-radius: var(--r-lg);
  border: 1px solid var(--stage-line);
  background: var(--panel);
}
/* The cards carry their own titles. The icons here were emoji — somebody
   else's artwork, in somebody else's style, redrawn per platform — and the
   cards read better without a row of them competing with the headings. That
   removal left a `.feature` fragment welded to the selector below it, which
   made it `.feature .ghero .backlink:hover` and therefore matched nothing. */
.ghero h1 {
  font-size: clamp(38px, 9vw, 78px);
  line-height: 0.94;
  margin: 12px 0 0;
  color: var(--a);
  text-shadow: 0 0 34px var(--a-soft);
}
.ghero .gh-tag {
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-size: clamp(13px, 2.4vw, 17px);
  color: var(--ink-dim);
  margin: 10px 0 0;
}
.ghero .gh-meta { display: flex; flex-wrap: wrap; gap: 8px; margin: 20px 0 0; }
.ghero .gh-hook {
  color: var(--ink);
  font-size: clamp(16px, 2.3vw, 19px);
  line-height: 1.55;
  max-width: 66ch;
  margin: 24px 0 0;
  text-wrap: pretty;
}
.ghero .cta-row { justify-content: flex-start; margin-top: 28px; }

/* numbered play-through, on the game pages. Scoped to its container: it was
   a bare .beat, and a bare generic word is a class name waiting to be taken by
   accident — which is exactly what happened. */
.playthrough { counter-reset: beat; margin-top: 30px; display: grid; gap: 14px; }
.playthrough .beat {
  position: relative;
  padding: 18px 20px 18px 66px;
  border-radius: var(--r-md);
  border: 1px solid var(--stage-line);
  background: var(--panel);
  color: var(--ink-dim);
  font-size: 15.5px;
  line-height: 1.5;
}
.playthrough .beat::before {
  counter-increment: beat;
  content: counter(beat);
  position: absolute;
  left: 18px;
  top: 16px;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 17px;
  color: var(--ink-on-gold);
  background: var(--a);
}

/* TV vs phone split — the platform's signature, given real estate */
.split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 18px;
  margin-top: 30px;
}
/* The two screens are the page's most important idea — what the room sees
   versus what only you see — and they were two grey boxes. Same violet neon as
   every other container on the site, so they read as part of it. */
.side {
  padding: 24px;
  border-radius: var(--r-lg);
  border: 1px solid rgba(139, 76, 224, 0.55);
  background:
    radial-gradient(120% 100% at 50% 0%, rgba(139, 76, 224, 0.12), transparent 62%),
    linear-gradient(180deg, var(--panel-raised), var(--panel));
  box-shadow:
    0 0 0 1px rgba(12, 6, 28, 0.5),
    0 0 20px -6px rgba(139, 76, 224, 0.45),
    inset 0 1px 0 rgba(233, 226, 255, 0.08),
    var(--shadow-card);
}
.side .sidehead {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-display);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 19px;
  margin-bottom: 4px;
}
.side.tv .sidehead { color: var(--gold); }
.side.phone .sidehead { color: var(--host); }
.side .sidesub { color: var(--ink-faint); font-size: 13px; margin: 0 0 14px; }
.side ul { margin: 0; padding: 0; list-style: none; display: grid; gap: 10px; }
.side li {
  position: relative;
  padding-left: 22px;
  color: var(--ink-dim);
  font-size: 15px;
  line-height: 1.45;
}
.side li::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 8px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.6;
}
.side.tv li::before { background: var(--gold); }
.side.phone li::before { background: var(--host); }

/* settings table */
/* ── what you can change ──────────────────────────────────────────────────
   Hairline rules between rows made this read as a spec sheet — the one part of
   a game's page that is a LIST OF CHOICES somebody is about to make, laid out
   like a datasheet nobody reads. One container per setting, the same rule /new
   and /me follow, on the house violet with the gold saved for the labels. */
.settings-list { margin-top: 26px; display: grid; gap: 10px; }
.setting {
  display: grid;
  grid-template-columns: minmax(150px, 230px) 1fr;
  gap: 18px;
  padding: 16px 18px;
  border-radius: 14px;
  background:
    radial-gradient(120% 100% at 50% 0%, rgba(139, 76, 224, 0.10), transparent 62%),
    var(--panel);
  border: 1px solid rgba(139, 76, 224, 0.55);
  box-shadow:
    0 0 0 1px rgba(12, 6, 28, 0.5),
    0 0 16px -6px rgba(139, 76, 224, 0.4),
    inset 0 1px 0 rgba(233, 226, 255, 0.08);
}
@media (max-width: 560px) {
  .setting { grid-template-columns: 1fr; gap: 6px; }
}
.setting dt {
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 16px;
  /* Gold: this is the word you scan for down the column, and the description
     beside it is what you read once you have found it. */
  color: var(--gold);
}
.setting dd { margin: 0; color: var(--ink-dim); font-size: 15px; line-height: 1.45; }
@media (max-width: 620px) {
  .setting { grid-template-columns: 1fr; gap: 4px; }
}

/* example prompt card — styled like it's on the TV */
.example {
  margin-top: 26px;
  padding: 30px 26px;
  border-radius: var(--r-lg);
  border: 1px solid var(--a);
  background:
    radial-gradient(ellipse 90% 80% at 50% 0%, var(--a-soft), transparent 70%),
    var(--panel-inset);
  box-shadow: var(--shadow-card);
  text-align: center;
}
.example .exlabel {
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-size: 12px;
  color: var(--a);
}
.example .extext {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(21px, 3.6vw, 32px);
  line-height: 1.14;
  margin: 14px auto 0;
  max-width: 30ch;
  text-wrap: balance;
}
.example .exnote {
  color: var(--ink-dim);
  font-size: 14.5px;
  line-height: 1.5;
  max-width: 60ch;
  margin: 18px auto 0;
}

/* related games */
/* These are RECOMMENDATIONS — the whole point of the section is that somebody
   who liked this game should take one — and they were small grey pills with the
   length whispered after the name. Cards now: stacked, so the game's name and
   how long it takes are two facts rather than one run-on line, and both are
   legible from where you are actually sitting. */
.related { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 22px; justify-content: center; }
.related a {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 16px 26px;
  min-width: 150px;
  border-radius: 16px;
  border: 2px solid rgba(139, 76, 224, 0.75);
  background:
    radial-gradient(120% 100% at 50% 0%, rgba(139, 76, 224, 0.14), transparent 62%),
    var(--panel);
  box-shadow:
    0 0 0 1px rgba(12, 6, 28, 0.5),
    0 0 18px -6px rgba(139, 76, 224, 0.5);
  color: var(--ink);
  text-decoration: none;
  font-size: 17px;
  font-family: var(--font-display); font-weight: 800;
  letter-spacing: 0.02em;
  transition: border-color var(--t-fast), transform var(--t-fast) var(--ease-pop);
}
.related a:hover {
  border-color: #b47cff; transform: translateY(-2px);
  box-shadow: 0 0 0 1px rgba(12, 6, 28, 0.5), 0 0 26px -4px rgba(161, 92, 255, 0.7);
}
/* The length in gold, because "twenty minutes" is what decides whether you
   start another one tonight. It was --ink-faint trailing after the name. */
.related a small, .related a span:last-child, .related a .dur {
  font-family: var(--font-display); font-weight: 700;
  font-size: 13px; letter-spacing: 0.06em;
  color: var(--gold); opacity: 1;
}

/* closing CTA band */
.closer { text-align: center; }
.closer h2 { font-size: clamp(30px, 6vw, 52px); }

/* ── footer ─────────────────────────────────────────────────────────────── */
.sitefoot {
  border-top: 1px solid var(--stage-line);
  padding: 40px 0 56px;
  color: var(--ink-faint);
  font-size: 13.5px;
}
.sitefoot .inner {
  max-width: 1120px;
  margin: 0 auto;
  padding: 0 20px;
  display: flex;
  flex-wrap: wrap;
  /* Centred, and spaced rather than left-packed. Four columns totalling about
     half the page's width sat flush against the left edge with the other half
     empty, so the footer looked like it had lost something — the columns were
     fine, the row simply had nothing telling it where to sit. */
  justify-content: center;
  gap: 26px clamp(28px, 5vw, 64px);
  align-items: flex-start;
  text-align: center;
}
.sitefoot .fcol { justify-items: center; }
/* The brand block is a sentence, not a column of links, so it keeps its own
   line rather than being squeezed into the same rhythm as the lists. */
.sitefoot .fcol:first-child { justify-items: center; }
.sitefoot .fbrand { font-family: var(--font-display); font-weight: 800; font-size: 22px; color: var(--gold); letter-spacing: 0.08em; }
.sitefoot .fcol { display: grid; gap: 8px; align-content: start; justify-items: center; }
.sitefoot .fcol strong {
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 12px;
  color: var(--ink-dim);
  font-weight: 700;
}
.sitefoot a { color: var(--ink-faint); text-decoration: none; }
.sitefoot a:hover { color: var(--ink-dim); }
.sitefoot .fend { width: 100%; padding-top: 8px; border-top: 1px solid var(--stage-line); margin-top: 8px;   text-align: center;
}

/* ── reveal-on-scroll: opt-in, and off entirely for reduced motion ────────
   Uses the INDEPENDENT `translate` property, not `transform`, so it composes
   with the hover lift on .gcard instead of overriding it. */
@media (prefers-reduced-motion: no-preference) {
  .reveal { opacity: 0; translate: 0 14px; transition: opacity 500ms ease, translate 500ms var(--ease-pop); }
  .reveal.shown { opacity: 1; translate: none; }
}


/* ── Simon ────────────────────────────────────────────────────────────────
   Four buzzers in a housing, which is what Simon has always been — and what
   this page has been building all along. The pads reuse the demo buzzer's
   construction: seated in a dark surround, travelling their own depth on press,
   dark until lit.

   Quarter-circle pads made from border-radius rather than an SVG or four
   images: it stays crisp at any size, recolours with the theme, and there is
   nothing to load. */
.simon {
  display: flex; flex-direction: column; align-items: center;
  gap: clamp(14px, 2vw, 20px);
  margin-top: clamp(26px, 4vw, 44px);
}
.simon-board {
  position: relative;
  width: clamp(258px, 76vw, 372px);
  aspect-ratio: 1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(7px, 1.2vw, 11px);
  padding: clamp(7px, 1.2vw, 11px);
  border-radius: 50%;
  /* The housing, lit from below — the inside of a cup catches light on its far
     wall, and that inversion is what makes it read as a case rather than a
     disc. Same trick as the buzzer's socket. */
  background: radial-gradient(circle at 50% 42%, #0a0618 46%, #17102f 78%, #241a4a 100%);
  box-shadow:
    inset 0 8px 18px -6px rgba(0, 0, 0, 0.95),
    inset 0 -6px 14px -6px rgba(192, 123, 255, 0.35),
    0 0 0 2px rgba(192, 123, 255, 0.35),
    0 30px 60px -28px rgba(4, 2, 16, 0.95);
}
.simon-board .pad {
  --pad: var(--gold);
  border: 0; cursor: pointer; padding: 0;
  background: color-mix(in srgb, var(--pad) 26%, #0b0718);
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--pad) 45%, transparent),
    inset 0 -6px 14px rgba(0, 0, 0, 0.5);
  transition: background 90ms ease, box-shadow 90ms ease, transform 90ms ease;
}
/* The four quadrants of one circle. */
.simon-board .p0 { border-radius: 100% 8px 8px 8px; }
.simon-board .p1 { border-radius: 8px 100% 8px 8px; }
.simon-board .p2 { border-radius: 8px 8px 8px 100%; }
.simon-board .p3 { border-radius: 8px 8px 100% 8px; }
/* Four colours the site already owns, so it belongs to this page rather than
   borrowing the toy's own primary red-yellow-green-blue. */
.simon-board .p0 { --pad: var(--buzz); }
.simon-board .p1 { --pad: var(--host); }
.simon-board .p2 { --pad: var(--teal); }
.simon-board .p3 { --pad: var(--gold); }

/* Lit is the whole game: it has to be unmistakable at a glance and from across
   a room, so it is a full-strength fill plus a bloom, not a tint. */
.simon-board .pad.lit {
  background: color-mix(in srgb, var(--pad) 92%, white 18%);
  box-shadow:
    inset 0 0 0 1px #fff,
    0 0 24px 2px var(--pad),
    0 0 60px -6px var(--pad);
}
.simon-board .pad:active { transform: scale(0.985); }
.simon-board .pad:focus-visible { outline: 3px solid var(--gold); outline-offset: 4px; }
/* Game over: the whole board goes cold. */
.simon-board.over .pad { background: #14102a; box-shadow: inset 0 0 0 1px rgba(233, 226, 255, 0.1); }

/* The round counter, in the hub where the toy's logo goes. */
.simon-hub {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: 34%; aspect-ratio: 1;
  border-radius: 50%;
  display: grid; place-items: center; align-content: center;
  pointer-events: none;
  background: radial-gradient(circle at 50% 38%, #1b1540, #0b0718);
  box-shadow: inset 0 0 0 2px rgba(192, 123, 255, 0.4), 0 8px 20px -8px rgba(0, 0, 0, 0.9);
}
.simon-hub b {
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(28px, 7vw, 42px); line-height: 1;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}
.simon-hub i {
  font-style: normal;
  font-size: 9px; letter-spacing: 0.26em; text-transform: uppercase;
  color: var(--ink-faint);
}

.simon-say {
  margin: 0; min-height: 1.5em;
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(15px, 2.2vw, 19px); letter-spacing: 0.08em;
  color: var(--ink-dim);
  text-align: center;
}
.simon-best {
  margin: 0;
  font-family: var(--font-display); font-weight: 800;
  font-size: 12px; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ink-faint);
}
.simon-best b { color: var(--gold); font-size: 15px; letter-spacing: 0.02em; }
.simon-best[hidden], .simon-end[hidden] { display: none; }
.simon-end { margin-top: 4px; }

@media (prefers-reduced-motion: reduce) {
  .simon-board .pad { transition: background 90ms ease; }
  .simon-board .pad:active { transform: none; }
}


/* ── what it is for ───────────────────────────────────────────────────────
   The mission and the way in, in one section. It replaced six feature cards
   arguing a case nobody was making — and a headline that invented a problem
   ("nobody should have to sit out") that does not happen on a real game night,
   where nobody sits out anyway, host included.

   The name already says what this is for. The section says it plainly, twice,
   and then hands over the door. */
.mission { text-align: center; }
.mission-eyebrow {
  margin: 0 0 14px;
  font-family: var(--font-display); font-weight: 800;
  font-size: 12px; letter-spacing: 0.3em; text-transform: uppercase;
  color: var(--ink-faint);
}
.mission-say {
  margin: clamp(16px, 2.2vw, 22px) auto 0;
  max-width: 46ch;
  color: var(--ink-dim);
  font-size: clamp(16px, 2.2vw, 19px);
  line-height: 1.55;
  text-wrap: pretty;
}
/* The same door as the hero's, so it is the same three controls stacked the
   same way — a second call to action that looked different would read as a
   different offer. */
.mission-join {
  flex-direction: column;
  align-items: stretch;
  gap: clamp(10px, 1.6vw, 14px);
  max-width: 460px;
  margin: clamp(28px, 4vw, 44px) auto 0;
}
.mission-join > * { width: 100%; }
.mission-join .btn { padding: 17px 24px; font-size: clamp(17px, 2.2vw, 20px); }
.mission-join .btn-ghost {
  background: linear-gradient(180deg, var(--gold-hot), var(--gold));
  border-color: var(--gold);
  color: var(--ink-on-gold);
  box-shadow: 0 4px 0 #a9741c, 0 14px 30px -14px rgba(245, 182, 63, 0.7);
}
.mission-join .btn-ghost:active { transform: translateY(4px); box-shadow: 0 0 0 #a9741c; }
.mission-join #joinbtn2 {
  background: rgba(245, 182, 63, 0.1);
  border: 2px solid var(--gold);
  color: var(--gold-hot);
}
.mission-join .field {
  width: 100%;
  min-height: clamp(96px, 18vw, 132px);
  text-align: center;
  color: var(--gold);
  font-size: clamp(34px, 7vw, 52px);
  border-radius: 14px;
  box-shadow:
    inset 0 3px 10px rgba(4, 2, 14, 0.7),
    inset 0 0 0 1px rgba(192, 123, 255, 0.22);
}
.mission-join .field::placeholder { color: rgba(245, 182, 63, 0.5); }


/* ── ambient background for a game page ───────────────────────────────────
   A negative z-index child of <body> paints above the body's own gradient and
   below every bit of content, which is exactly the layer we want and the reason
   this needs no changes anywhere else.

   The blur is already in the file (scripts/build-game-bg.mjs) — a fixed,
   full-viewport filter: blur() is one of the most expensive things a phone GPU
   can be asked to redraw on every scroll frame, and it would be doing it for
   the whole page. Opacity stays here because that is the dial worth keeping
   adjustable, and the mask fades the loop out at the edges so it reads as depth
   rather than as a video someone left playing. */
.gbg { position: fixed; inset: 0; z-index: -1; overflow: hidden; pointer-events: none; }
.gbg video,
.gbg img {
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0.24;
  -webkit-mask-image: radial-gradient(ellipse 92% 74% at 50% 34%, #000 24%, transparent 76%);
          mask-image: radial-gradient(ellipse 92% 74% at 50% 34%, #000 24%, transparent 76%);
}

/* ── a game's own page ────────────────────────────────────────────────────
   Centred and stacked, the shape the landing page settled on. Everything here
   used to be left-aligned two-column blocks; the page reads as an introduction
   now rather than a spec sheet. */
.ghero { text-align: center; padding-top: 26px; }
/* The way back out. Left-aligned and built like the meta pills below it, so it
   belongs to the page's own vocabulary instead of being the one piece of plain
   faint text on it. Purple hairline at rest, gold when you reach for it —
   the same "cold until touched" rule the neon everywhere else follows. */
.backrow { text-align: left; margin-bottom: clamp(18px, 3vw, 30px); }
.ghero .backlink {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 9px 17px 9px 14px;
  border-radius: 999px;
  border: 1px solid rgba(192, 123, 255, 0.3);
  background: rgba(192, 123, 255, 0.06);
  color: #c5aae8;
  font-family: var(--font-display); font-weight: 700;
  font-size: 12px; letter-spacing: 0.13em; text-transform: uppercase;
  transition: color .18s, border-color .18s, background .18s, box-shadow .18s;
}
/* Drawn, not typed: a rotated corner scales with the text and keeps its weight
   in step with the border, which an arrow glyph from the font would not. */
.ghero .backlink i {
  width: 7px; height: 7px;
  border-left: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
  transition: transform .18s;
}
.ghero .backlink:hover {
  color: var(--gold-hot);
  border-color: rgba(245, 182, 63, 0.55);
  background: rgba(245, 182, 63, 0.09);
  box-shadow: 0 0 18px -4px rgba(245, 182, 63, 0.4);
}
.ghero .backlink:hover i { transform: rotate(45deg) translate(2px, -2px); }

/* The host, on a lit floor. Same pool-of-light trick as the landing hero: a
   cast shadow says an object has weight, a pool of light says there is a floor
   for it to have weight on. */
.gstage { position: relative; display: grid; place-items: center; }
.gstage::after {
  content: ''; position: absolute; z-index: 0; pointer-events: none;
  left: 50%; bottom: 6%;
  width: min(620px, 92%); height: 46%;
  transform: translateX(-50%);
  background: radial-gradient(ellipse 46% 52% at 50% 62%,
    rgba(245, 182, 63, 0.22), rgba(245, 182, 63, 0.06) 46%, transparent 80%);
}
.ghost-img {
  position: relative; z-index: 1;
  height: clamp(150px, 26vw, 260px); width: auto;
  filter: drop-shadow(0 18px 28px rgba(6, 4, 20, 0.6));
}
@keyframes hostArrive {
  from { opacity: 0; transform: scale(0.42) translateY(8%); }
  to   { opacity: 1; transform: none; }
}
.ghero.intro .ghost-img { animation: hostArrive 0.8s cubic-bezier(.18, .86, .24, 1) 0.1s both; }
@media (prefers-reduced-motion: reduce) { .ghero.intro .ghost-img { animation: none; } }

.ghero h1 {
  margin: clamp(10px, 1.6vw, 16px) auto 0;
  font-size: clamp(46px, 10vw, 92px);
  line-height: 0.94;
  color: var(--gold);
  text-shadow: 0 2px 0 rgba(4, 2, 14, 0.55), 0 0 30px rgba(245, 182, 63, 0.4);
}
.ghero .gh-tag {
  margin: 8px auto 0;
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(15px, 2.2vw, 19px); letter-spacing: 0.16em; text-transform: uppercase;
  color: #fff;
}
/* Named, and in one line. A host with a name is a person running your evening;
   an unnamed one is a feature. */
.ghero .gh-host {
  margin: clamp(14px, 2vw, 20px) auto 0;
  max-width: 44ch;
  color: var(--ink-dim);
  font-size: clamp(14px, 1.9vw, 16px);
  line-height: 1.5;
}
.ghero .gh-host b { display: block; color: var(--host); letter-spacing: 0.02em; }
.ghero .gh-meta {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 8px;
  margin: clamp(18px, 2.6vw, 26px) auto 0;
}
.ghero .gh-hook {
  margin: clamp(18px, 2.6vw, 26px) auto 0;
  max-width: 54ch;
  color: var(--ink-dim);
  font-size: clamp(16px, 2.1vw, 19px);
  line-height: 1.55;
  text-wrap: pretty;
}
.ghero .cta-row { justify-content: center; }

/* The beats, centred and unboxed — the card each one sat in was doing nothing
   the number was not already doing. */
#secPlay .playthrough { max-width: 660px; margin-inline: auto; }
#secPlay .playthrough .beat {
  background: none; border: 0;
  padding: 16px 0 16px 62px;
  text-align: left;
  border-top: 1px solid var(--stage-line);
}
#secPlay .playthrough .beat:first-child { border-top: 0; }
#secPlay .playthrough .beat::before { top: 14px; left: 8px; }

/* The two halves stack rather than sitting side by side. */
#secSplit .split {
  display: grid; gap: clamp(14px, 2vw, 20px);
  max-width: 620px; margin: clamp(24px, 3.4vw, 36px) auto 0;
}
#secSplit .side { text-align: left; }
#secSplit .sidehead { font-size: 16px; letter-spacing: 0.08em; }

/* Settings as a centred list rather than a wide two-column table. */
#secSettings .settings-list { max-width: 660px; margin: clamp(24px, 3.4vw, 36px) auto 0; }

#secRelated .related { justify-content: center; }
#secRelated { text-align: center; }

/* The sample question is the strongest thing on the page, so it gets the house
   neon and sits above the explanation — one real question shows what the game
   IS faster than four paragraphs about it. */
#secExample .example {
  border: 1px solid var(--gold);
  box-shadow:
    0 0 8px -1px var(--gold),
    0 0 30px -8px var(--gold),
    0 22px 44px -26px rgba(4, 2, 16, 0.9);
}

/* The account link. Signed out it is a plain nav item; signed in it carries the
   gold the rest of the bar reserves for "this is yours". */
.topbar nav a.navlink.acct { white-space: nowrap; }
/* On a phone the bar is only the wordmark and the CTA, so the account link has
   to earn its space: smaller, and truncated rather than wrapping the header. */
@media (max-width: 700px) {
  .topbar nav a.navlink.acct { font-size: 13px; max-width: 9ch; overflow: hidden; text-overflow: ellipsis; }
}
.topbar nav a.navlink.acct.signedin { color: var(--gold); }

/* The word on the plunger. Nudged up because the highlight puts the dome's
   apparent face above its geometric centre, so type sitting on the true centre
   reads as having slipped. Painted on the button, so it travels with it. */
.buzzdemo .bd-word {
  transform: translateY(-2%);
  text-shadow: 0 2px 0 rgba(88, 12, 18, 0.45);
  pointer-events: none;
}
.buzzdemo .bd-buzz:disabled .bd-word { opacity: 0.55; }

/* The first band on a page is that page's TITLE, not a section heading.
   `.band h2` is sized for the eight bands that follow it down a landing page,
   which left /daily opening on something the same weight as its own subheads —
   the page announced itself in the voice it uses for footnotes. Scoped to the
   opening band rather than raising the shared rule, because every other band on
   the site is correctly sized for what it is. */
.band.pagehead .eyebrow { font-size: clamp(13px, 1.4vw, 16px); letter-spacing: 0.26em; margin-bottom: 16px; }
.band.pagehead h2 { font-size: clamp(40px, 8vw, 76px); line-height: 0.98; margin-bottom: 16px; }
.band.pagehead .lede { font-size: clamp(16px, 2.2vw, 20px); }

/* Your name, beside the wordmark.
   Moved out of the nav because a name is not an action. On the right, between
   Play and a gold button, it read as one more link you might click by mistake;
   next to the wordmark it reads as "GPTPG, and you", which is what it means.
   Kept quiet on purpose — this is a label with a link in it, not a call. */
.topbar .whoami {
  margin-left: -6px;
  padding: 4px 10px;
  border-radius: var(--r-pill);
  border: 1px solid var(--stage-line);
  background: rgba(255, 255, 255, 0.03);
  color: var(--ink-dim);
  max-width: 22ch;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.topbar .whoami:hover { color: var(--ink); border-color: color-mix(in srgb, var(--gold) 45%, var(--stage-line)); }
@media (max-width: 700px) {
  /* The wordmark hides on a phone, so the name becomes the leftmost thing —
     which is right: on a small bar, who you are matters more than the logo. */
  .topbar .whoami { margin-left: 0; max-width: 14ch; }
}

/* Your face in the bar, beside your name.
   text-decoration matters here: the rule that strips underlines is scoped to
   `.topbar nav a`, and this link is deliberately no longer inside the nav — so
   moving it out of the nav put the underline back. */
.topbar .whoami { display: inline-flex; align-items: center; gap: 7px; text-decoration: none; }
.topbar .whoface {
  width: 22px; height: 22px; border-radius: 50%; flex: none;
  border: 1px solid var(--stage-line); object-fit: cover;
}

/* ── the toy picker and the two games behind it ─────────────────────────────
   One at a time. Three games side by side is three things nobody plays; a
   picker keeps the Simon as the thing you actually try, with two more waiting
   for whoever is enjoying themselves. */
.toypick {
  display: flex; gap: 8px; justify-content: center; flex-wrap: wrap;
  /* Room above. The pills sat hard against the blurb, so the row read as part
     of the sentence rather than as the control that changes it. */
  margin: clamp(20px, 3vw, 30px) auto clamp(16px, 2.4vw, 24px);
}
.toypick button {
  padding: 8px 18px; border-radius: var(--r-pill); cursor: pointer;
  font-family: var(--font-display); font-weight: 700; font-size: 14px;
  letter-spacing: 0.06em; text-transform: uppercase;
  background: var(--panel-inset); border: 1px solid var(--stage-line); color: var(--ink-dim);
}
.toypick button[aria-pressed="true"] {
  background: var(--gold); border-color: var(--gold); color: var(--ink-on-gold);
  box-shadow: var(--glow-gold);
}
.toy {
  max-width: 460px; margin: 0 auto; display: flex; flex-direction: column; gap: 14px;
  /* Room at the top for the chips that sit ON the lock's shoulders — they hang
     outside their own box by design, and without this the container clips them. */
  padding-top: 14px;
}

/* ── PopBop: a marquee ──────────────────────────────────────────────────
   It was a round plunger in a housing, which was wrong twice: a circle wastes
   the one dimension a phone has plenty of, and this page ALREADY has a red
   plunger four screens up — so a second one read as repetition rather than as
   identity. Three games should be three objects.

   A marquee card uses the full width, which lets the instruction be as large as
   it can possibly be. That matters more here than anywhere else on the site:
   the whole game is reading one word before a bar runs out. */
.bop-card {
  position: relative; width: 100%; aspect-ratio: 3 / 2;
  display: grid; place-items: center;
  border: 0; padding: 0; cursor: pointer; overflow: hidden;
  border-radius: 22px;
  background:
    radial-gradient(120% 100% at 50% 0%, rgba(139, 76, 224, 0.20), transparent 62%),
    linear-gradient(180deg, #241d52, #171136);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.07),
    0 0 0 2px var(--act, #8b4ce0),
    0 0 34px -10px var(--act, #8b4ce0),
    var(--shadow-card);
  touch-action: none; -webkit-user-select: none; user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition: box-shadow 140ms ease, transform 90ms ease;
}
.bop-card:active { transform: scale(0.985); }
/* Three instructions that look identical are a reading test. Three that flash
   in different colours are a game you can play from the corner of your eye. */
.bop-card[data-act="tap"]   { --act: var(--gold); }
.bop-card[data-act="swipe"] { --act: var(--host); }
.bop-card[data-act="shake"] { --act: var(--coral); }
.bop-card.wrong { --act: #5c5c68; }

.bop-word {
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(38px, 13vw, 76px); line-height: 1;
  letter-spacing: 0.02em; color: var(--ink);
  text-shadow: 0 0 26px color-mix(in srgb, var(--act, #8b4ce0) 60%, transparent);
  padding: 0 16px; text-align: center;
}
.bop-card.wrong .bop-word { color: var(--ink-dim); }

/* The window, along the card's top edge. A rail reads without being looked at,
   which is the point when you are reacting rather than reading — and along the
   top it never competes with the word for the middle of the card. */
.bop-rail {
  position: absolute; top: 0; left: 0; height: 6px; width: 100%;
  transform-origin: left; transform: scaleX(1);
  background: var(--act, var(--gold));
  box-shadow: 0 0 14px -2px var(--act, var(--gold));
  opacity: 0; transition: opacity 140ms ease;
}
.bop-card.running .bop-rail { opacity: 1; }

.bop-chip {
  position: absolute; top: 12px; left: 14px;
  display: inline-flex; align-items: baseline; gap: 5px;
  padding: 3px 10px; border-radius: var(--r-pill);
  background: rgba(8, 5, 22, 0.7); border: 1px solid rgba(245, 182, 63, 0.45);
  font-family: var(--font-display); font-weight: 700;
  font-size: 9px; letter-spacing: 0.2em; text-transform: uppercase; color: var(--ink-faint);
}
.bop-chip b { font-size: 15px; letter-spacing: 0; color: var(--gold); }
/* The name on the machine. Bottom-right and quiet — it says whose game this is
   without asking to be read. */
.bop-mark {
  position: absolute; right: 14px; bottom: 12px;
  font-family: var(--font-display); font-weight: 800;
  font-size: 10px; letter-spacing: 0.3em; color: rgba(245, 182, 63, 0.45);
}

/* ── Mindbender: a lock and a set of keys ────────────────────────────────
   It shipped as floating rows of rounded squares, which reads as a FORM — and
   nobody plays a form. Housing the two halves separately says what they are:
   the thing you are trying to open, and the things you open it with. */
.mbx-lock {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  /* align-self, not margin. The parent is a column flexbox, so the default
     stretch made this full-width however narrow its contents were — which is
     why the slots huddled left in an acre of empty panel. */
  align-self: center; padding: 20px 30px; border-radius: 20px;
  background: radial-gradient(120% 100% at 50% 0%, rgba(139, 76, 224, 0.14), transparent 60%), var(--panel);
  box-shadow:
    inset 0 8px 18px -10px rgba(0, 0, 0, 0.9),
    0 0 0 2px #8b4ce0, 0 0 26px -8px rgba(139, 76, 224, 0.6);
}
/* The counter as a chip on the lock's shoulder, and the game's name on the
   other one. The counter was floating in an acre of empty panel with the slots
   huddled off-centre — the panel was sized for a sentence that was never in it. */
.mbx-left {
  position: absolute; right: -10px; top: -12px;
  padding: 2px 10px; border-radius: var(--r-pill);
  background: var(--stage-deep); border: 1px solid rgba(245, 182, 63, 0.55);
  font-family: var(--font-display); font-weight: 800; font-size: 13px;
  color: var(--gold);
}
.mbx-lock::before {
  content: 'MINDBENDER'; position: absolute; left: 14px; top: -9px;
  padding: 0 8px; background: var(--stage);
  font-family: var(--font-display); font-weight: 800; font-size: 10px;
  letter-spacing: 0.24em; color: rgba(245, 182, 63, 0.6);
}
.mbx-slots i { width: 44px; height: 44px; }
.mbx-keys {
  padding: 12px; border-radius: 18px;
  background: var(--panel-inset); border: 1px solid var(--stage-line);
  box-shadow: inset 0 6px 14px -10px rgba(0, 0, 0, 0.9);
}
.mbx-slots, .mbx-guess { display: flex; gap: 8px; justify-content: center; }
.mbx-slots i, .mbx-guess i {
  width: 34px; height: 34px; border-radius: 10px;
  border: 2px solid var(--stage-line); background: rgba(255, 255, 255, 0.04);
  display: grid; place-items: center;
}
.mbx-syms { display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px; }
.mbx-sym {
  aspect-ratio: 1; border-radius: 14px; cursor: pointer;
  border: 2px solid var(--stage-line); background: var(--panel-inset);
  display: grid; place-items: center;
}
.mbx-sym:hover { border-color: var(--gold); }
.mbx-row { display: flex; gap: 10px; justify-content: center; }
.mbx-log { display: flex; flex-direction: column; gap: 6px; max-height: 230px; overflow-y: auto; }
.mbx-line { display: flex; align-items: center; gap: 12px; justify-content: center; }
.mbx-score { font-family: var(--font-display); font-weight: 700; font-size: 13px; color: var(--gold); }

/* The shapes. Drawn with clip-path and border-radius rather than an image, so
   there is nothing to fetch and nothing to go missing — and NAMED, so a
   colour-blind player is not playing a different game. */
.mbx-sym::after, .mbx-slots i.on::after, .mbx-guess i.on::after {
  content: ''; width: 56%; height: 56%; background: currentColor;
}
.sym-star { color: var(--gold); }
.sym-moon { color: var(--host); }
.sym-bolt { color: #c08cff; }
.sym-drop { color: #6fb7ff; }
.sym-leaf { color: var(--teal); }
.sym-eye  { color: var(--coral); }
.sym-star::after { clip-path: polygon(50% 0,61% 35%,98% 35%,68% 57%,79% 91%,50% 70%,21% 91%,32% 57%,2% 35%,39% 35%); }
.sym-bolt::after { clip-path: polygon(58% 0,20% 55%,45% 55%,38% 100%,80% 42%,52% 42%); }
.sym-leaf::after { border-radius: 0 62% 0 62%; transform: rotate(-12deg); }
.sym-drop::after { border-radius: 50% 50% 50% 50% / 62% 62% 38% 38%; transform: rotate(180deg); }
.sym-moon::after { border-radius: 50%; background: none; box-shadow: inset -7px 2px 0 0 currentColor; }
.sym-eye::after  { border-radius: 50% / 100% 100% 0 0; height: 36%; box-shadow: 0 0.9em 0 -0.42em currentColor; }

/* The moon is a crescent made by taking a bite out of a disc, so it is the one
   shape that needs NO fill — and `.mbx-guess i.on::after` outranks
   `.sym-moon::after` on specificity, which filled it back in wherever a guess
   was echoed. Same shape, said loudly enough to win. */
.mbx-slots i.sym-moon::after,
.mbx-guess i.sym-moon::after { background: none; }
/* And the eye was reading as a mushroom: the lid needs to be wider than it is
   tall, with the pupil sitting inside rather than under it. */
.sym-eye::after {
  border-radius: 50% / 100% 100% 0 0; height: 30%; width: 68%;
  box-shadow: 0 0.55em 0 -0.3em currentColor;
}

/* Rooms you are already in. Quiet until there are any — an empty row of nothing
   under the join box would be a permanent hole for the 99% of visitors who have
   never started a game. */
.rejoin { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 12px; }
.rejoin:empty { display: none; }
.rejoin-chip {
  padding: 6px 14px; border-radius: var(--r-pill);
  background: var(--panel-inset); border: 1px solid var(--stage-line);
  color: var(--ink-dim); text-decoration: none;
  font-family: var(--font-display); font-weight: 700; font-size: 13px; letter-spacing: 0.06em;
}
.rejoin-chip:hover { color: var(--ink); border-color: var(--gold); }

/* ── the header menu ──────────────────────────────────────────────────────
   The bar carried four link texts, a name and a gold call to action — six
   things across one line, and on a phone the links were hidden anyway. It is
   three things now: a way in, who you are, and the one action. */
.hmenu-wrap { position: relative; flex: none; }
.hmenu-btn {
  width: 38px; height: 38px; border-radius: 11px; cursor: pointer;
  display: grid; align-content: center; justify-items: center; gap: 4px;
  background: var(--panel-inset); border: 1px solid var(--stage-line);
}
.hmenu-btn:hover { border-color: var(--gold); }
/* Drawn, not typed — a hamburger character is a different glyph on every phone. */
.hmenu-btn i { display: block; width: 16px; height: 2px; border-radius: 2px; background: var(--ink-dim); }
.hmenu-btn:hover i { background: var(--gold); }
.hmenu {
  position: absolute; top: calc(100% + 8px); left: 0; z-index: 60;
  min-width: 190px; padding: 6px; border-radius: 14px;
  background: var(--panel); border: 1px solid var(--stage-line);
  box-shadow: var(--shadow-pop);
  display: grid;
}
.hmenu-item {
  padding: 10px 14px; border-radius: 9px;
  color: var(--ink-dim); text-decoration: none; font-size: 14.5px; font-weight: 600;
}
.hmenu-item:hover { background: var(--panel-inset); color: var(--ink); }

/* Your name takes the MIDDLE OF THE BAR, not the middle of what is left over.
   `margin: 0 auto` centres within the free space, which drifts left the moment
   one side is heavier than the other — and the left side carries a menu button
   and a wordmark against a single button on the right. Absolute centring is
   measured against the bar itself and cannot drift. */
@media (min-width: 560px) {
  .topbar .inner { position: relative; }
  .topbar .whoami {
    position: absolute; left: 50%; translate: -50% 0;
    margin: 0; max-width: 32vw;
  }
}

/* ── the single-player toys, each in its own little space ──────────────────
   Simon sat on the page with nothing around it, so the section read as one
   long band with a disc floating in the middle of it. A container says "this
   is a game" the same way the cards on /new say "this is a decision".

   Violet ground with a gold rim: the two brand colours doing the two jobs they
   do everywhere else — violet is the room, gold is the thing worth looking at.
   Square around the frame, round around the disc, because the shape of the
   container should echo the shape of the game inside it. */
.toy, .simon {
  position: relative;
  padding: clamp(18px, 3vw, 28px);
  border-radius: 24px;
  background:
    radial-gradient(120% 100% at 50% 0%, rgba(139, 76, 224, 0.14), transparent 62%),
    var(--panel);
  /* Brighter. At 55% alpha with a soft bloom the case read as a faint outline
     rather than as a lit edge — and this is the one container on the page that
     is meant to look switched ON. Full-strength tube, a hot inner hairline, and
     a bloom that actually carries. */
  border: 2px solid #a15cff;
  box-shadow:
    0 0 0 1px rgba(12, 6, 28, 0.6),
    inset 0 0 0 1px rgba(216, 180, 255, 0.35),
    0 0 18px -2px rgba(161, 92, 255, 0.75),
    0 0 46px -8px rgba(161, 92, 255, 0.45),
    var(--shadow-card);
  max-width: 460px; margin-left: auto; margin-right: auto;
}
/* The disc gets its own ring inside the square one — a round game in a round
   housing, sitting in a square case. VIOLET, matching the case: gold here made
   the housing the loudest thing in the section, competing with the four lit
   pads that are the actual game. The case and the ring are the room; the pads
   are what you look at. */
.simon .simon-board {
  box-shadow:
    inset 0 8px 22px -10px rgba(0, 0, 0, 0.95),
    inset 0 -8px 16px -8px rgba(192, 123, 255, 0.45),
    0 0 0 2px #a15cff,
    0 0 0 4px rgba(20, 8, 40, 0.6),
    0 0 26px -4px rgba(161, 92, 255, 0.6);
}

/* ── the game page's backdrop ──────────────────────────────────────────────
   A retro game-show room behind the introduction, matched to what the game
   feels like. Decorative and never load-bearing: the poster is a frame of the
   same clip, so a blocked autoplay or a slow connection leaves a photograph
   rather than a hole, and reduced-motion leaves the photograph on purpose. */
.ghero { position: relative; overflow: hidden; isolation: isolate; }
.gh-backdrop {
  position: absolute; inset: 0; z-index: -2;
  width: 100%; height: 100%; object-fit: cover;
  /* Pushed back. This is the ROOM, not the subject — at full strength it
     competes with the host portrait and makes the copy work to be read. */
  opacity: 0.30; filter: saturate(0.85) contrast(0.95);
  /* Except the television, which is a SMALL OBJECT in its frame rather than a
     whole room filling it. At the same 30% it read as an unidentifiable smudge,
     and the point of that clip is recognising what it is. */
  &[data-clip="crt"] { opacity: 0.52; filter: saturate(0.95) contrast(1.05); }
  pointer-events: none;
}
/* The wash is what keeps the words legible over moving footage: the page's own
   ground at the edges, fading to nothing in the middle where the video shows. */
.gh-wash {
  position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(ellipse 120% 80% at 50% 40%, transparent 20%, var(--stage) 78%),
    linear-gradient(180deg, rgba(23, 20, 51, 0.55), rgba(23, 20, 51, 0.2) 40%, var(--stage));
}
