/* ===========================================================================
   THE SET

   The apparatus the site is watched on: an address bar across the top, a
   remote down the left, a screen between them. All three are on screen at
   once, permanently, because that pairing is the pitch. The URL is the
   website half. The remote and the screen are the television half.

   The apparatus is dark and the screen is bright. That is deliberate: a
   screen only looks like a screen when there is a dim room around it, and it
   lets each channel bring its own palette without the furniture fighting it.

   Nothing here is drawn as a physical television. No bezel, no stand, no
   scanlines. The screen behaves like one instead: safe area, channel bug,
   lower third, inner glow.
   ========================================================================= */

@property --sweep { syntax: '<angle>'; inherits: false; initial-value: 0deg; }
@property --slat  { syntax: '<length>'; inherits: false; initial-value: 0px; }

/* Registered so they can be transitioned. The apparatus assembles itself out
   of nothing at the end of CH 01's opening, and these two lengths are what
   the assembling is made of. */
@property --remote-w { syntax: '<length>'; inherits: true; initial-value: 268px; }
@property --gutter   { syntax: '<length>'; inherits: true; initial-value: 14px; }

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

:root {
  /* The room. Not flat black: a blue black that the screen can glow into. */
  --room:      #07080D;
  --room-lift: #0D0F17;

  /* The furniture. One ramp, used by the top bar and the remote. */
  --f-00: rgba(255,255,255,0.035);
  --f-10: rgba(255,255,255,0.065);
  --f-20: rgba(255,255,255,0.11);
  --f-30: rgba(255,255,255,0.18);
  --f-ink:  rgba(255,255,255,0.94);
  --f-mute: rgba(255,255,255,0.46);
  --f-dim:  rgba(255,255,255,0.28);

  --rec: #E5484D;

  /* The accent the DARK furniture uses. It is normally the channel's own
     accent, but a channel on a light ground needs a dark accent, and a dark
     accent on the remote is a dark mark on a dark object. Each theme that
     needs to can set --chrome separately from --accent. */
  --chrome: var(--accent, #A98FE0);

  --omni-h:   62px;
  --remote-w: 268px;
  --gutter:   14px;

  --ui: 'Outfit', system-ui, -apple-system, 'Segoe UI', sans-serif;

  /* Motion. The product's numbers, so the site and the thing it sells move
     at the same speed. */
  --ease:   cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-io: cubic-bezier(0.4, 0, 0.2, 1);

  color-scheme: dark;
}

html, body {
  margin: 0; padding: 0; height: 100%;
  background: var(--room);
  font-family: var(--ui);
  -webkit-font-smoothing: antialiased;
}

body { overflow: hidden; }

/* The room has a faint lift behind the screen, as if the screen spills into
   it. Cheap, static, and it stops the black reading as a dead rectangle. */
body::before {
  content: ''; position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(120% 90% at 62% 44%, var(--room-lift) 0%, transparent 62%);
}

/* ---------------------------------------------------------------------------
   Layout
   ------------------------------------------------------------------------ */
.set {
  position: fixed; inset: 0; z-index: 1;
  display: grid;
  grid-template-rows: var(--omni-h) 1fr;
  grid-template-columns: var(--remote-w) 1fr;
  grid-template-areas: 'omni omni' 'remote screen';
  gap: 0;
  padding: 0 var(--gutter) var(--gutter);
  transition: --remote-w 900ms var(--ease), --gutter 900ms var(--ease);
}

/* ---------------------------------------------------------------------------
   COLD START

   On a first arrival at CH 01 there is no apparatus at all: no remote, no
   gutter, no glow, no rounded corners. Just an address bar and a white page,
   because that is what an ordinary website looks like. The melt assembles the
   television around the visitor, once, and then it stays.

   Clicking the top bar, or pressing any key, brings the apparatus back at
   once, so the visitor is never actually stuck watching a performance.
   ------------------------------------------------------------------------ */
.set.is-cold { --remote-w: 0px; --gutter: 0px; }

.remote { transition: opacity 700ms var(--ease), transform 950ms var(--ease); }
.set.is-cold .remote { opacity: 0; transform: translateX(-115%); pointer-events: none; }

.screen { transition: border-radius 700ms var(--ease), box-shadow 900ms var(--ease); }
.set.is-cold .screen { border-radius: 0; box-shadow: none; }

.screen::after { transition: opacity 800ms var(--ease); }
.set.is-cold .screen::after { opacity: 0; }

/* Scoped through .screen so these beat the rail's own "visible on a vertical
   channel" rule, which is declared further down the file at equal weight. */
.bug, .third, .rail { transition: opacity 600ms var(--ease); }
.set.is-cold .screen .bug,
.set.is-cold .screen .third,
.set.is-cold .screen .rail { opacity: 0; }

/* ---------------------------------------------------------------------------
   THE TOP BAR

   Which channel is on, and its name. The remote swaps pages with pushState,
   so the real URL under it is always correct too; this bar just says the one
   thing the browser's own bar cannot.
   ------------------------------------------------------------------------ */
.omni {
  grid-area: omni;
  display: flex; align-items: center; gap: 10px;
  padding: 0 4px;
  position: relative;
}

/* The channel display.

   This used to repeat the browser's own address bar back at the visitor,
   which is a joke that costs a strip of screen and tells them nothing they
   cannot already see. It shows the channel instead, which is the one thing
   the browser's bar does not. */
.omni-field {
  flex: 1; min-width: 0; max-width: 460px; margin: 0 auto; height: 40px;
  display: flex; align-items: center; justify-content: center; gap: 14px;
  padding: 0 22px;
  border-radius: 999px;
  background: rgba(0,0,0,0.34);
  box-shadow: inset 0 0 0 1px var(--f-10), inset 0 1px 3px rgba(0,0,0,0.5);
  overflow: hidden;
}

.omni-ch {
  font-size: 19px; font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--chrome, #A98FE0); line-height: 1;
}
.omni-name {
  font-size: 17px; font-weight: 700; letter-spacing: 0.17em;
  text-transform: uppercase; color: var(--f-ink); white-space: nowrap;
}
.omni-tag {
  font-size: 14px; font-weight: 600; letter-spacing: 0.15em;
  text-transform: uppercase; color: var(--f-mute); white-space: nowrap;
}
@media (max-width: 1100px) { .omni-tag { display: none; } }

/* PCS owns the place, so the corner says so. The name is the only link in the
   furniture, and it lights in the channel's chrome color like everything else
   that responds to a pointer. */
.omni-tag a {
  color: inherit; text-decoration: none;
  border-bottom: 1px solid rgba(255,255,255,0.18);
  transition: color 160ms ease, border-color 160ms ease;
}
.omni-tag a:hover,
.omni-tag a:focus-visible { color: var(--chrome, #A98FE0); border-bottom-color: currentColor; }

/* The caret that blinks while the name is being typed. */
.omni-caret {
  display: inline-block; width: 2px; height: 17px; margin-left: -8px;
  background: var(--chrome, #A98FE0);
  opacity: 0;
}
.set.is-typing .omni-caret { opacity: 1; animation: caret 900ms steps(1) infinite; }
@keyframes caret { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0.15; } }

/* The loading hairline, in the channel's own accent. Sweeps on every tune. */
.omni-load {
  position: absolute; left: 4px; bottom: 0; height: 2px; width: 0;
  background: var(--chrome, #A98FE0);
  border-radius: 2px;
  opacity: 0;
}
.set.is-tuning .omni-load { animation: omni-load 900ms var(--ease-io) both; }
@keyframes omni-load {
  0%   { width: 0;    opacity: 1; }
  62%  { width: 72%;  opacity: 1; }
  100% { width: 100%; opacity: 0; }
}

/* Both corners take an equal share of the bar, so the channel display sits at
   true center rather than being nudged along by whichever corner is wider. */
/* Both corners take an equal share of the bar, so the channel display sits at
   true center rather than being nudged along by whichever corner is wider. */
.omni-right {
  display: flex; align-items: center; gap: 2px;
  flex: 1 1 0; justify-content: flex-end;
}

/* The corner opposite the copyright. It carries the same weight as that one, so
   the channel display stays centered between them rather than being pushed off
   by whichever side has more in it. */
/* The station, named, on one line in the corner. */
.omni-left { display: flex; align-items: baseline; gap: 10px; min-width: 0; flex: 1 1 0; }

.omni-brand {
  /* The same size and weight as the channel name in the middle of the bar, but
     set as it is written rather than in capitals. Tracking comes down with the
     case: 0.14em is right for capitals and airy on lowercase. */
  font-size: 17px; font-weight: 700; letter-spacing: 0.05em;
  color: var(--f-ink); white-space: nowrap;
}
.omni-strap {
  font-size: 17px; font-weight: 600; letter-spacing: 0.15em;
  text-transform: uppercase; color: var(--f-mute); white-space: nowrap;
}
/* The strapline goes first when the bar gets tight, then the name. */
@media (max-width: 1100px) { .omni-strap { display: none; } }
@media (max-width: 720px)  { .omni-brand { display: none; } }

/* ---------------------------------------------------------------------------
   THE REMOTE

   A real remote, not a nav bar shaped like one. Numbers and names, a channel
   rocker, and a red record button that asks for the channel.
   ------------------------------------------------------------------------ */
.remote {
  grid-area: remote;
  margin-right: var(--gutter);
  border-radius: 26px;
  background: linear-gradient(176deg, #23262F 0%, #191C23 42%, #101218 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.10),
    inset 0 0 0 1px rgba(255,255,255,0.045),
    0 28px 60px -20px rgba(0,0,0,0.9),
    0 4px 14px rgba(0,0,0,0.5);
  padding: 16px 14px 18px;
  display: flex; flex-direction: column; gap: 12px;
  overflow-y: auto; overflow-x: hidden;
  scrollbar-width: none;
}
.remote::-webkit-scrollbar { display: none; }

/* Head: the IR eye and the wordmark. The eye flashes on every press, which
   is the detail that sells the object. */
.remote-head {
  display: flex; align-items: center; justify-content: center;
  padding: 2px 4px 4px;
}
.ir {
  width: 7px; height: 7px; border-radius: 50%;
  background: rgba(229,72,77,0.18);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.07);
}
.set.is-pressed .ir {
  animation: ir 420ms ease-out;
}
@keyframes ir {
  0%   { background: #FF5B60; box-shadow: 0 0 10px 2px rgba(229,72,77,0.8); }
  100% { background: rgba(229,72,77,0.18); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.07); }
}

/* A machined groove between banks of buttons: one dark line, one light line. */
.groove {
  height: 0; margin: 3px 2px;
  border-top: 1px solid rgba(0,0,0,0.55);
  border-bottom: 1px solid rgba(255,255,255,0.045);
}

.remote-top { display: flex; align-items: center; gap: 10px; }

.btn-power {
  width: 36px; height: 36px; border: 0; border-radius: 50%; flex-shrink: 0;
  background: linear-gradient(180deg, #2C3039 0%, #1B1E25 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.12), 0 2px 5px rgba(0,0,0,0.5);
  color: var(--f-mute); cursor: pointer; display: grid; place-items: center;
  transition: color 180ms, box-shadow 180ms;
}
.btn-power:hover { color: #FF8488; box-shadow: inset 0 1px 0 rgba(255,255,255,0.14), 0 0 14px rgba(229,72,77,0.28); }
.btn-power svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }

/* The channel rocker: one pill, split, with the label between the arrows. */
.rocker {
  flex: 1; height: 50px; border-radius: 25px;
  display: grid; grid-template-rows: 1fr auto 1fr;
  background: rgba(0,0,0,0.32);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.075);
  overflow: hidden;
}
.rocker button {
  border: 0; background: transparent; color: rgba(255,255,255,0.62);
  cursor: pointer; display: grid; place-items: center; padding: 0;
  transition: background 150ms, color 150ms;
}
.rocker button:hover { background: var(--f-10); color: var(--f-ink); }
.rocker button:active { background: var(--f-20); }
.rocker svg { width: 14px; height: 14px; fill: currentColor; }
.rocker-label {
  font-size: 11px; font-weight: 800; letter-spacing: 0.2em;
  color: var(--f-dim); text-align: center; line-height: 1;
}
.btn-power { width: 50px; height: 50px; }
.btn-power svg { width: 17px; height: 17px; }

/* --- The channel bank ------------------------------------------------- */
.remote { --bank-gap: 5px; }
.bank { display: flex; flex-direction: column; gap: var(--bank-gap); }

/* Every key on the remote is the same distance from the next, the actions
   included. The remote's own 12px gap is taken back under each of them. */
@media (min-width: 901px) {
  .bank ~ .act { margin-top: calc(var(--bank-gap) - 12px); }
}

.ch-btn {
  position: relative;
  display: flex; align-items: center; gap: 10px;
  width: 100%; height: 50px; padding: 0 14px 0 11px;
  border: 0; border-radius: 11px; cursor: pointer;
  background: rgba(255,255,255,0.045);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.07), 0 1px 2px rgba(0,0,0,0.35);
  color: var(--f-mute);
  font-family: inherit; text-align: left; text-decoration: none;
  transition: background 170ms var(--ease), color 170ms, box-shadow 170ms, transform 90ms;
}
.ch-btn:hover { background: rgba(255,255,255,0.085); color: var(--f-ink); }
.ch-btn:active { transform: translateY(1px); box-shadow: inset 0 1px 0 rgba(255,255,255,0.04), 0 0 0 rgba(0,0,0,0); }

.ch-num {
  width: 30px; height: 30px; flex-shrink: 0; border-radius: 8px;
  display: grid; place-items: center;
  background: rgba(0,0,0,0.35);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.06);
  font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--f-mute);
  transition: background 170ms, color 170ms, box-shadow 170ms;
}
.ch-name {
  font-size: 14px; font-weight: 700; letter-spacing: 0.11em;
  text-transform: uppercase; white-space: nowrap;
}

/* The channel you are on lights from within, in that channel's own accent. */
.ch-btn.is-on {
  background: color-mix(in srgb, var(--chrome, #A98FE0) 16%, rgba(255,255,255,0.05));
  color: #fff;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.12),
    inset 3px 0 0 var(--chrome, #A98FE0),
    0 0 18px -4px color-mix(in srgb, var(--chrome, #A98FE0) 45%, transparent);
}
.ch-btn.is-on .ch-num {
  background: var(--chrome, #A98FE0);
  color: #0B0D12;
  box-shadow: none;
}

/* --- The action bank --------------------------------------------------- */
.act {
  display: flex; align-items: center; gap: 9px;
  width: 100%; min-height: 52px; padding: 14px 16px;
  border: 0; border-radius: 11px; cursor: pointer;
  font-family: inherit; text-decoration: none; text-align: left;
  font-size: 14px; font-weight: 700; letter-spacing: 0.08em; line-height: 1.3;
  text-transform: uppercase;
  transition: background 170ms, color 170ms, box-shadow 170ms, transform 90ms;
}
.act:active { transform: translateY(1px); }
.act svg { width: 17px; height: 17px; flex-shrink: 0; }


/* The loudest thing on the remote, correctly: it is the ask. */
.act-rec {
  background: linear-gradient(180deg, #F0575C 0%, #D63E43 100%);
  color: #fff;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.28), 0 3px 12px -2px rgba(229,72,77,0.55);
}
.act-rec:hover { box-shadow: inset 0 1px 0 rgba(255,255,255,0.32), 0 5px 20px -2px rgba(229,72,77,0.8); }

/* Lit while its panel is open. The panel is not a channel, so the channel you
   were watching stands down for as long as it is up: leaving CH 07 lit said
   you were watching Contact, which was not where you were. */
.act-rec.is-on {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.4),
    0 0 0 2px rgba(255,255,255,0.55),
    0 6px 26px -2px rgba(229,72,77,0.95);
}
/* One line, centered, and tracked in a little so the full sentence fits the
   width of the remote without dropping to a second line. */
.act-rec {
  justify-content: center; text-align: center;
  letter-spacing: 0.03em; white-space: nowrap;
}

/* The label breathes: in, five seconds on, out, a second dark, in again. It
   is the one ask on the remote, so it is allowed to ask twice. */
.act-rec span {
  display: inline-block;
  animation: rec-breathe 7s ease-in-out infinite;
}
@keyframes rec-breathe {
  0%   { opacity: 0; }
  6%   { opacity: 1; }
  77%  { opacity: 1; }
  83%  { opacity: 0; }
  100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) { .act-rec span { animation: none; } }
[data-reduced='1'] .act-rec span { animation: none; }

.act-call {
  background: transparent; color: var(--f-mute);
  box-shadow: inset 0 0 0 1px var(--f-20);
}
.act-call:hover { color: var(--f-ink); box-shadow: inset 0 0 0 1px var(--f-30); background: var(--f-00); }
.act-call svg { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

/* Ask Eric anything. Quiet like the booking button until a call is open, then it
   lights, and while Eric talks the light breathes with him. */
.act-voice {
  background: transparent; color: var(--f-mute);
  box-shadow: inset 0 0 0 1px var(--f-20);
}
.act-voice[hidden] { display: none; }
.act-voice:hover { color: var(--f-ink); box-shadow: inset 0 0 0 1px var(--f-30); background: var(--f-00); }
.act-voice svg { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.act-voice[data-state='connecting'] { color: var(--f-ink); cursor: progress; }
.act-voice[data-state='listening'],
.act-voice[data-state='speaking'] {
  color: #fff; background: rgba(169,143,224,0.22);
  box-shadow: inset 0 0 0 1px rgba(169,143,224,0.7), 0 0 18px -4px rgba(169,143,224,0.7);
}
.act-voice[data-state='speaking'] { animation: voice-talk 1.1s ease-in-out infinite; }
.act-voice[data-state='error'] { color: #F0575C; box-shadow: inset 0 0 0 1px rgba(240,87,92,0.5); }
@keyframes voice-talk {
  0%, 100% { box-shadow: inset 0 0 0 1px rgba(169,143,224,0.7), 0 0 14px -4px rgba(169,143,224,0.6); }
  50%      { box-shadow: inset 0 0 0 1px rgba(169,143,224,1),   0 0 28px -2px rgba(169,143,224,0.95); }
}
@media (prefers-reduced-motion: reduce) { .act-voice[data-state='speaking'] { animation: none; } }
[data-reduced='1'] .act-voice[data-state='speaking'] { animation: none; }


/* ---------------------------------------------------------------------------
   THE SCREEN

   Bezel-less. The frame is implied by an inner glow, a vignette and a real
   broadcast safe area, not drawn as an object.
   ------------------------------------------------------------------------ */
.screen {
  grid-area: screen;
  position: relative;
  border-radius: 18px;
  overflow: hidden;
  isolation: isolate;
  background: #05060A;
  box-shadow:
    0 0 0 1px rgba(255,255,255,0.07),
    0 40px 90px -30px rgba(0,0,0,0.95),
    0 0 90px -18px color-mix(in srgb, var(--chrome, #A98FE0) 22%, transparent);

  /* One scaling unit, as in the player, but web sized. */
  --px: 1px;
  --safe: clamp(22px, 3.1vw, 62px);
}

/* Vignette and inner glow, above the content, inert. */
.screen::after {
  content: ''; position: absolute; inset: 0; z-index: 8; pointer-events: none;
  border-radius: inherit;
  background: radial-gradient(118% 100% at 50% 44%, transparent 52%, rgba(0,0,0,0.42) 100%);
  box-shadow:
    inset 0 0 120px -30px color-mix(in srgb, var(--chrome, #A98FE0) 26%, transparent),
    inset 0 0 0 1px rgba(255,255,255,0.05);
}

/* The page stack. Two pages coexist only during a tune. */
.page {
  position: absolute; inset: 0;
  overflow-y: auto; overflow-x: hidden;
  scrollbar-width: none;
  color: var(--ink);
  font-family: var(--text);
}
.page::-webkit-scrollbar { display: none; }
.page[data-format='fixed']      { overflow: hidden; }
.page[data-format='horizontal'] { overflow-x: auto; overflow-y: hidden; }

.page.is-leaving  { z-index: 4; pointer-events: none; }
.page.is-entering { z-index: 5; }

/* The ground: the channel's own background, behind everything on the page.
   Sticky rather than fixed, because the page transforms during a tune and a
   fixed child of a transformed element stops being fixed. The negative
   margin keeps it out of the page's height. */
.ground {
  position: sticky; top: 0; z-index: 0;
  height: 100cqh; margin-bottom: -100cqh;
  pointer-events: none;
}
/* The pull-back has to be a real length. A percentage margin resolves against
   the containing block's WIDTH, so -100% here would drag everything below the
   ground up by the difference between the screen's width and its height. */
.page > *:not(.ground) { position: relative; z-index: 1; }

/* Anything a channel pins to the screen itself rather than to a section has
   to win that rule back, or it lands in the flow at the end of the page. */
.page > .more,
.page > .rail-h { position: absolute; }

/* --- Channel bug ------------------------------------------------------- */
.bug {
  position: absolute; z-index: 7; pointer-events: none;
  top: var(--safe); right: var(--safe);
  display: flex; align-items: center; gap: 8px;
  height: 28px; padding: 0 12px 0 10px;
  border-radius: 999px;
  /* Solid enough to stay dark over a light channel ground as well as a
     dark one, so the white text on it is readable either way. */
  background: rgba(0,0,0,0.58);
  backdrop-filter: blur(10px);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.12);
  font-size: 13.5px; font-weight: 700; letter-spacing: 0.15em;
  text-transform: uppercase; color: rgba(255,255,255,0.9);
  font-variant-numeric: tabular-nums;
}
.bug .dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--chrome, #A98FE0);
  box-shadow: 0 0 8px var(--chrome, #A98FE0);
}
.set.is-tuning .bug { animation: bug-flash 1100ms var(--ease-io) both; }
@keyframes bug-flash {
  0%   { opacity: 0; transform: translateY(-6px); }
  18%  { opacity: 1; transform: none; }
  26%  { opacity: 0.25; }
  34%  { opacity: 1; }
  44%  { opacity: 0.4; }
  100% { opacity: 1; }
}

/* --- Lower third ------------------------------------------------------- */
/* Hidden for good. The element is a data carrier now, not furniture. */
.third[hidden] { display: none !important; }

.third {
  position: absolute; z-index: 7; pointer-events: none;
  left: var(--safe); bottom: var(--safe);
  max-width: min(46ch, 52%);
}
.third .rule {
  height: 3px; width: 46px; border-radius: 2px;
  background: var(--accent, #A98FE0);
  margin-bottom: 10px;
}
.third .name {
  font-family: var(--display, var(--ui));
  font-size: 19px; font-weight: 700; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink);
  line-height: 1;
}
.third .on {
  margin-top: 6px;
  font-size: 16px; letter-spacing: 0.01em; color: var(--meta);
  line-height: 1.35;
}
.set.is-tuning .third { animation: third-in 1300ms 220ms var(--ease) both; }
@keyframes third-in {
  from { opacity: 0; transform: translateX(-22px); }
  to   { opacity: 1; transform: none; }
}

/* A real lower third is not furniture, it is an event: it announces what you
   just tuned to and then gets out of the way. Parking it also stops it
   colliding with whatever the channel puts in that corner. */
.third.is-parked {
  opacity: 0; transform: translateX(-14px);
  transition: opacity 700ms var(--ease), transform 700ms var(--ease);
}

/* Without script nothing ever unparks it, so it simply stays on: it is the
   only thing naming the channel once the bug's flash animation cannot run. */
html:not([data-js]) .third.is-parked { opacity: 1; transform: none; }

/* --- Scroll rail: the scrollbar, as broadcast furniture ---------------- */
.rail {
  position: absolute; z-index: 7; pointer-events: none;
  right: calc(var(--safe) / 2.2); top: var(--safe); bottom: var(--safe);
  width: 3px; border-radius: 3px;
  /* The track has to be visible or the thumb reads as a stray line floating
     in the picture rather than as a position in a channel. */
  background: color-mix(in srgb, var(--ink) 22%, transparent);
  opacity: 0; transition: opacity 400ms;
}
.page[data-format='vertical'] ~ .rail,
.screen.has-rail .rail { opacity: 1; }
.rail .thumb {
  position: absolute; left: 0; right: 0; top: 0;
  border-radius: 3px; background: var(--accent, #A98FE0);
  box-shadow: 0 0 10px -2px var(--accent, #A98FE0);
}

/* ---------------------------------------------------------------------------
   POWER

   The screen collapses to a line, then a dot, then comes back. A CRT power
   cycle, which every television ever made did and no website does.
   ------------------------------------------------------------------------ */
.screen.is-off .page,
.screen.is-off .bug,
.screen.is-off .third,
.screen.is-off .rail { animation: crt-off 620ms var(--ease-io) both; }
.screen.is-on  .page,
.screen.is-on  .bug,
.screen.is-on  .third { animation: crt-on 760ms var(--ease) both; }

@keyframes crt-off {
  0%   { transform: scale(1, 1);        filter: brightness(1); opacity: 1; }
  55%  { transform: scale(1.01, 0.006); filter: brightness(2.6); opacity: 1; }
  78%  { transform: scale(0.28, 0.006); filter: brightness(3.4); opacity: 1; }
  100% { transform: scale(0.004, 0.004); filter: brightness(5); opacity: 0; }
}
@keyframes crt-on {
  0%   { transform: scale(0.004, 0.004); filter: brightness(5); opacity: 0; }
  24%  { transform: scale(0.3, 0.006);   filter: brightness(3.4); opacity: 1; }
  50%  { transform: scale(1.01, 0.006);  filter: brightness(2.6); opacity: 1; }
  100% { transform: scale(1, 1);         filter: brightness(1); opacity: 1; }
}
.screen.is-off::after { background: #000; box-shadow: none; }

/* ---------------------------------------------------------------------------
   Handheld

   A phone already is a remote. The bank becomes a thumb-reachable strip at
   the bottom of the glass, thumb high.
   ------------------------------------------------------------------------ */
@media (max-width: 900px) {
  :root { --omni-h: 48px; --gutter: 8px; }

  .set {
    grid-template-columns: 1fr;
    grid-template-areas: 'omni' 'screen';
    padding-bottom: 68px;
  }

  .remote {
    position: fixed; left: 8px; right: 8px; bottom: 8px; z-index: 10;
    margin: 0; padding: 8px 8px 9px;
    border-radius: 20px;
    flex-direction: row; align-items: center; gap: 8px;
    overflow: visible;
  }
  .remote-head, .groove, .remote-top, .act-call { display: none; }

  .bank {
    flex: 1; flex-direction: row; gap: 6px;
    overflow-x: auto; scrollbar-width: none;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
  }
  .bank::-webkit-scrollbar { display: none; }
  .ch-btn { width: auto; flex-shrink: 0; height: 38px; scroll-snap-align: center; }
  .ch-btn .ch-name { font-size: 10.5px; }

  /* The label goes, the record dot stays. Hiding every span would leave a
     red lozenge with nothing in it. */
  .act-rec { width: 40px; padding: 0; justify-content: center; flex-shrink: 0; height: 38px; }
  .act-rec span:not(.dot) { display: none; }
  .act-rec .dot { width: 11px; height: 11px; }

  /* On a phone the voice button keeps its mic and loses its words. */
  .act-voice { width: 40px; min-height: 0; height: 38px; padding: 0; justify-content: center; flex-shrink: 0; }
  .act-voice .voice-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }


  /* A phone has no spare corner. The channel bug already says what is on,
     so the lower third simply does not appear at this width. */
  .third { display: none; }
}

/* The note about screen size: shown on a handheld and nowhere else. It rides
   above the thumb strip, clears the way on its own after a few seconds, and
   never takes a tap to dismiss. Kept out of the flow so it cannot push a
   scene around, and declared before its media query so that the query wins. */
.small-screen {
  display: none;
  position: fixed; left: 10px; right: 10px; bottom: 72px; z-index: 9;
  margin: 0; padding: 9px 14px;
  border-radius: 12px;
  background: rgba(13,15,23,0.92);
  box-shadow: inset 0 0 0 1px var(--f-20), 0 8px 24px rgba(0,0,0,0.45);
  backdrop-filter: blur(6px);
  font: 400 13px/1.35 var(--ui);
  color: var(--f-ink);
  text-align: center;
  animation: small-screen-note 9s var(--ease) forwards;
}

/* Up, held, then away. It is a CSS animation rather than a timer in script so
   that the note behaves the same on a page where script never runs. */
@keyframes small-screen-note {
  0%        { opacity: 0; transform: translateY(10px); }
  4%, 88%   { opacity: 1; transform: none; }
  100%      { opacity: 0; transform: translateY(10px); visibility: hidden; }
}

/* Nothing moves, and the note simply stays: a reader who has asked for less
   motion should not have the one piece of advice on the page slide away. */
@media (prefers-reduced-motion: reduce) {
  .small-screen { animation: none; }
}

/* ---------------------------------------------------------------------------
   THE ANSWER, ON A TELEVISION

   A lead that has landed is confirmed on a set rather than in a paragraph.
   The picture tunes in: a second of static, the white blink of a tube
   catching, and then the message, with the wall behind the set lighting only
   once there is a picture to throw.

   Built out of one screen and four overlays so that nothing here has to run
   on a timer in script. The whole sequence is keyframes, which means it
   cannot drift out of step with itself.
   ------------------------------------------------------------------------ */
.sent { display: grid; gap: clamp(18px, 3vw, 26px); justify-items: center; }

.sent-tv { position: relative; width: 100%; }

.sent-screen {
  position: relative; z-index: 1; overflow: hidden;
  aspect-ratio: 16 / 9;
  container-type: inline-size;
  border-radius: 10px;
  background: radial-gradient(76% 64% at 50% 44%, #171B2E 0%, #0B0D16 72%);
  /* The drop shadow is kept tight on purpose. A wide one lands on exactly the
     patch of wall the glow lights, and being part of this element it paints
     over the glow, which cancels it. */
  box-shadow:
    0 0 0 6px #090A12,
    0 0 0 7px rgba(255,255,255,0.12),
    0 20px 34px -26px rgba(0,0,0,0.9);
}

/* Static. One tile of fractal noise, jumped around by whole tile widths on a
   stepped clock, which boils the way an untuned channel does and costs one
   background-position. */
.sent-static {
  position: absolute; inset: -60px;
  /* The noise is drawn small and blown up with pixelated scaling, so the grain
     is a visible speckle rather than a fine dither that averages out to flat
     gray at this size. Turbulence on its own is a soft mid-gray cloud: each
     channel is pulled apart to black or white by a steep linear transfer, and
     the alpha is forced opaque, which is what turns a cloud into static. A
     gentler curve leaves most pixels in the middle and reads as gray felt. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n' x='0' y='0'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='1' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='7' intercept='-3'/%3E%3CfeFuncG type='linear' slope='7' intercept='-3'/%3E%3CfeFuncB type='linear' slope='7' intercept='-3'/%3E%3CfeFuncA type='linear' slope='0' intercept='1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 300px 300px;
  image-rendering: pixelated;
  opacity: 0.9;
  animation:
    sent-boil 420ms steps(6) 3,
    sent-static-out 320ms 900ms var(--ease) forwards;
}
@keyframes sent-boil {
  0%   { background-position:   0px   0px; }
  17%  { background-position:  71px  33px; }
  34%  { background-position: -44px  96px; }
  50%  { background-position:  18px -57px; }
  67%  { background-position: -93px -21px; }
  84%  { background-position:  57px  62px; }
  100% { background-position: -29px  11px; }
}
@keyframes sent-static-out { to { opacity: 0; } }

/* The band that rolls down a set that has not locked on yet. */
.sent-roll {
  position: absolute; left: 0; right: 0; height: 26%;
  background: linear-gradient(180deg,
    transparent 0%, rgba(255,255,255,0.10) 50%, transparent 100%);
  animation:
    sent-rolling 700ms linear 2,
    sent-static-out 300ms 820ms var(--ease) forwards;
}
@keyframes sent-rolling {
  from { transform: translateY(-130%); }
  to   { transform: translateY(430%); }
}

/* The blink of the tube catching, dead center of the handover: it covers the
   frame where the static leaves and the message has not arrived. */
.sent-flash {
  position: absolute; inset: 0; background: #FFFFFF;
  opacity: 0; pointer-events: none;
  animation: sent-catch 300ms 860ms var(--ease) forwards;
}
@keyframes sent-catch {
  0%   { opacity: 0; }
  22%  { opacity: 0.92; }
  100% { opacity: 0; }
}

/* The message itself, arriving out of the flash rather than after it. */
.sent-msg {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 4cqi;
  padding: 6cqi;
  text-align: center;
  opacity: 0; transform: scale(1.06); filter: blur(7px);
  animation: sent-tune 760ms 960ms var(--ease) forwards;
}
@keyframes sent-tune {
  to { opacity: 1; transform: none; filter: blur(0); }
}

.sent-kicker {
  margin: 0;
  display: inline-flex; align-items: center; gap: 9px;
  font: 600 3.1cqi/1 var(--ui); letter-spacing: 0.24em; text-transform: uppercase;
  color: var(--chrome, #A98FE0);
}
.sent-kicker .dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: currentColor; box-shadow: 0 0 10px currentColor;
}

.sent-line {
  margin: 0;
  font: italic 400 11.4cqi/1.04 'Instrument Serif', Georgia, serif;
  color: #FFFFFF;
  text-shadow: 0 0 26px color-mix(in srgb, var(--chrome, #A98FE0) 55%, transparent);
}

/* Scanlines, always on, low enough to read as texture rather than stripes. */
.sent-scan {
  position: absolute; inset: 0; pointer-events: none;
  background: repeating-linear-gradient(180deg,
    rgba(255,255,255,0.05) 0 1px, transparent 1px 3px);
  mix-blend-mode: overlay;
}

/* The wall behind the set. Dark while the picture is noise, lit once it is a
   picture, which is what sells the set as switched on. */
.sent-glow {
  position: absolute; inset: -16% -12% -8%;
  z-index: 0; pointer-events: none; border-radius: 50%;
  background: radial-gradient(58% 52% at 50% 46%,
    color-mix(in srgb, var(--chrome, #A98FE0) 76%, transparent) 0%, transparent 76%);
  filter: blur(30px);
  opacity: 0;
  animation: sent-glow-up 900ms 1000ms var(--ease) forwards;
}
@keyframes sent-glow-up { to { opacity: 1; } }

.sent-note {
  margin: 0; text-align: center;
  font: 400 clamp(15px, 1.2vw, 18px)/1.5 var(--ui);
  color: var(--f-mute);
  opacity: 0;
  animation: sent-note-in 700ms 1320ms var(--ease) forwards;
}
@keyframes sent-note-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* Asked for less motion: the set is already tuned in. No static, no roll, no
   blink, and the message is simply on the screen. */
@media (prefers-reduced-motion: reduce) {
  .sent-static, .sent-roll, .sent-flash { display: none; }
  .sent-msg, .sent-glow, .sent-note { animation: none; opacity: 1; transform: none; filter: none; }
}
[data-reduced='1'] .sent-static,
[data-reduced='1'] .sent-roll,
[data-reduced='1'] .sent-flash { display: none; }
[data-reduced='1'] .sent-msg,
[data-reduced='1'] .sent-glow,
[data-reduced='1'] .sent-note {
  animation: none; opacity: 1; transform: none; filter: none;
}

@media (max-width: 900px) { .small-screen { display: block; } }

/* ---------------------------------------------------------------------------
   Reduced motion: the apparatus stops performing, and nothing is lost.
   ------------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .set.is-tuning .omni-load,
  .set.is-tuning .bug,
  .set.is-tuning .third,
  .set.is-pressed .ir,
  .screen.is-off .page, .screen.is-on .page,
  .screen.is-off .bug,  .screen.is-on .bug,
  .screen.is-off .third, .screen.is-on .third { animation: none !important; }
}

/* ---------------------------------------------------------------------------
   THE GLINT

   A slow pass of light across the record button, every five seconds. It is
   the one persistent ask on the site, and a button that catches the light
   once in a while gets looked at without ever demanding to be.

   One gradient on a transform, so it composites and costs nothing.
   ------------------------------------------------------------------------ */
.act-rec { position: relative; overflow: hidden; isolation: isolate; }

.act-rec::after {
  content: ''; position: absolute; z-index: 1;
  top: -60%; bottom: -60%; width: 38%;
  background: linear-gradient(100deg,
    transparent 0%, rgba(255,255,255,0.50) 50%, transparent 100%);
  transform: translateX(-280%) rotate(10deg);
  animation: glint 5s cubic-bezier(0.45, 0, 0.35, 1) infinite;
  pointer-events: none;
}
@keyframes glint {
  0%, 80% { transform: translateX(-280%) rotate(10deg); }
  100%    { transform: translateX(460%) rotate(10deg); }
}

@media (prefers-reduced-motion: reduce) { .act-rec::after { animation: none; opacity: 0; } }
[data-reduced='1'] .act-rec::after { animation: none; opacity: 0; }

/* ===========================================================================
   THE REQUEST PANEL

   The red button does not navigate. It slides a panel over the channel you
   are watching, asks for the address and who you are, and remembers which
   channel you pressed it from, so a lead arrives knowing what the person had
   been looking at. Contact is a whole channel for everything else; this is
   only the one thing people come here to ask for.

   It is red because it is the single action on the remote, not because it is
   imitating a record button. That idea was tried and dropped: it asked the
   visitor to get a joke before they got the offer.
   ========================================================================= */
.rec {
  position: absolute; inset: 0; z-index: 9;
  display: grid; place-items: center;
  padding: var(--safe);
  background: rgba(4, 5, 11, 0.72);
  backdrop-filter: blur(14px) saturate(0.85);
}
.rec[hidden] { display: none; }

.rec-panel {
  position: relative;
  width: min(520px, 100%);
  max-height: 100%; overflow-y: auto; scrollbar-width: none;
  padding: clamp(26px, 3vw, 44px);
  border-radius: 20px;
  background: linear-gradient(168deg, #1A1D2E 0%, #0D0F1B 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.10),
    inset 0 0 0 1px rgba(255,255,255,0.09),
    0 40px 90px -30px rgba(0,0,0,0.95);
  animation: rec-in 420ms var(--ease) both;
}
.rec-panel::-webkit-scrollbar { display: none; }

@keyframes rec-in {
  from { opacity: 0; transform: translateY(18px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}

.rec-close {
  position: absolute; top: 14px; right: 14px;
  width: 38px; height: 38px; border: 0; border-radius: 10px;
  background: rgba(255,255,255,0.06); color: var(--f-mute);
  display: grid; place-items: center; cursor: pointer;
  transition: background 160ms, color 160ms;
}
.rec-close:hover { background: rgba(255,255,255,0.12); color: var(--f-ink); }
.rec-close svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }

.rec-flag {
  margin: 0 0 14px;
  font-size: 13px; font-weight: 700; letter-spacing: 0.2em;
  text-transform: uppercase; color: #FF6468;
}
.rec-flag .dot {
  width: 10px; height: 10px; border-radius: 50%; background: #E5484D;
  box-shadow: 0 0 10px #E5484D;
  animation: live 2s ease-in-out infinite;
}

.rec-title {
  margin: 0 0 10px;
  font-family: 'Instrument Serif', Georgia, serif; font-style: italic;
  font-size: clamp(28px, 2.6vw, 40px); font-weight: 400; line-height: 1.05;
  color: #F3F1FB;
}
.rec-title .em { color: #A98FE0; font-style: italic; }

.rec-sub {
  margin: 0 0 22px;
  font-size: clamp(15px, 1.05vw, 18px); line-height: 1.5;
  color: rgba(243,241,251,0.72);
}

.rec-form { display: flex; flex-direction: column; gap: 15px; }
.rec-form label { display: flex; flex-direction: column; gap: 7px; }
.rec-form label > span {
  font-size: clamp(13px, 0.92vw, 15px); font-weight: 600;
  letter-spacing: 0.06em; color: rgba(243,241,251,0.62);
}
.rec-form input {
  width: 100%; padding: 14px 16px;
  font-family: var(--ui); font-size: 17px; color: #F3F1FB;
  background: rgba(255,255,255,0.06); border: 0; border-radius: 11px;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.13);
  transition: box-shadow 180ms var(--ease), background 180ms;
}
.rec-form input:focus {
  outline: 0; background: rgba(255,255,255,0.10);
  box-shadow: inset 0 0 0 2px #A98FE0;
}
.rec-form .cta.primary {
  align-self: flex-start; margin-top: 4px;
  background: #A98FE0; color: #0B0A1C;
}
.rec-form .form-status { margin: 0; color: rgba(243,241,251,0.66); }
.rec-form .form-status.bad { color: #FF8A8E; }

.rec-alt {
  display: inline-block; margin-top: 20px;
  font-size: clamp(14px, 1vw, 16px); color: rgba(243,241,251,0.6);
  text-decoration: none;
  border-bottom: 1px solid rgba(243,241,251,0.26);
  padding-bottom: 2px;
}
.rec-alt:hover { color: #F3F1FB; border-bottom-color: #A98FE0; }

@media (prefers-reduced-motion: reduce) {
  .rec-panel { animation: none; }
  .rec-flag .dot { animation: none; }
}

/* A laptop screen is short. Nine channels and three actions do not fit a
   768-pixel-tall window at full size, and a remote whose bottom buttons are
   cut off looks broken, so on a short screen every key is a size smaller. */
@media (min-width: 901px) and (max-height: 860px) {
  .remote { --bank-gap: 3px; }
  .ch-btn { height: 37px; }
  .ch-num { width: 27px; height: 27px; }
  .rocker { height: 42px; }
  .btn-power { width: 32px; height: 32px; }
  .act { min-height: 44px; padding-block: 10px; }
}
