/* The room the television sits in. */

:root {
  --room: #050506;
  --cabinet: #17181b;
  --cabinet-edge: #27292d;
  --cabinet-dark: #09090b;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  overscroll-behavior: none;
  background: var(--room);
  overflow: hidden;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  color: #cfd4e4;
  cursor: default;
}

/* A soft pool of light behind the set, as if it were the only thing on. Kept
   close to neutral: a black set in a dark room, lit only by its own picture,
   rather than the blue studio wash that fought the charcoal plastic. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  background: radial-gradient(ellipse 72% 62% at 50% 44%,
              rgba(125, 140, 165, 0.10) 0%,
              rgba(18, 19, 23, 0.55) 52%,
              #040405 100%);
  pointer-events: none;
}

#set {
  /* Height the handset occupies at the bottom of a phone screen: three rows
     of keys, the gaps between them and the bar's own padding. #set leaves
     this much clear so the two never overlap. */
  --handset: calc(3 * 3.2rem + 2 * 0.5rem + 1.2rem
                  + env(safe-area-inset-bottom, 0px));
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* No padding here: the breathing room is part of the fit calculation on
     #cabinet, so there is exactly one place that decides how big the set is. */
  padding: 0;
  perspective: 160vmin;
}

/* The cabinet is sized from the viewport so the 4:3 tube always fits whole,
   whether the window is tall, wide, or a phone held upright. */
#cabinet {
  position: relative;

  /* The set is sized ONCE, here, and every dimension inside it is then a
     percentage of its own width (cqw) rather than of the viewport.
     ---------------------------------------------------------------------
     This used to be vmin everywhere: the bezel, the fascia, the radii, the
     lettering. vmin is the smaller axis of the WINDOW, so in a wide short
     window the frame computed from the height and in a tall narrow one from
     the width -- the proportions of the television changed as the browser
     changed shape, and at some sizes the frame was pushed off screen. The
     set has to be one rigid object that is simply scaled to fit.

     Proportions, as fractions of the cabinet's width:
        bezel   2.5%   around the glass
        screen  95%    wide, and 4:3, so 71.25% tall
        fascia  7%     the band with the badge and the keys
        total  80.75%  tall, hence the 1.2384 (= 1 / 0.8075) below.

     So: as wide as the window allows, or as wide as the window's HEIGHT
     allows once that ratio is applied, whichever is smaller. The result
     always fits, at any window size or shape. */
  --gap: 1.6vmin;
  container-type: inline-size;
  width: min(calc(100vw - 2 * var(--gap)), calc((100vh - 2 * var(--gap)) * 1.2384));
  /* dvh second so browsers without it keep the correct vh answer above. */
  width: min(calc(100vw - 2 * var(--gap)), calc((100dvh - 2 * var(--gap)) * 1.2384));
  padding: 0;
  border-radius: 2cqw 2cqw 1.2cqw 1.2cqw;
  display: flex;
  flex-direction: column;

  /* Charcoal-black moulded plastic, the finish on a late-90s Trinitron.
     Matte rather than gloss: a soft key light rolling off the top edge,
     falling to near-black at the base, and a very fine grain so a panel
     this large reads as plastic instead of a flat fill. */
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.045'/%3E%3C/svg%3E"),
    linear-gradient(180deg,
      rgba(255, 255, 255, 0.060) 0%,
      rgba(255, 255, 255, 0.014) 12%,
      rgba(0, 0, 0, 0.000) 38%,
      rgba(0, 0, 0, 0.260) 100%),
    linear-gradient(180deg, #222327 0%, #18191c 46%, #101113 100%);

  /* Hairlines are whole device pixels on purpose. A 0.1vmin edge lands
     between pixels at most window sizes and smears into a grey blur; a 1px
     line stays crisp, and crispness is most of what reads as quality. */
  box-shadow:
    /* the moulding turning away along the top edge */
    inset 0 1px 0 rgba(255, 255, 255, 0.11),
    inset 0 0.4vmin 0.7vmin -0.25vmin rgba(255, 255, 255, 0.05),
    /* the base falling into shadow */
    inset 0 -0.5vmin 0.9vmin -0.2vmin rgba(0, 0, 0, 0.72),
    /* parting line of the mould, right at the edge */
    inset 0 0 0 1px rgba(0, 0, 0, 0.9),
    /* standing on something, and the picture's glow spilling onto it */
    0 2.6vmin 5.2vmin rgba(0, 0, 0, 0.82),
    0 0.6vmin 1.4vmin rgba(0, 0, 0, 0.6),
    0 0 14vmin rgba(130, 155, 195, 0.06);
}

/* The moulding, painted ON TOP of the picture.
 *
 * Clipping the canvas is not reliable: a WebGL canvas is its own composited
 * layer, and on a real GPU it paints its square corners straight through the
 * parent's overflow, its border-radius, and contain: paint alike -- which is
 * the static seen bleeding out around the corners of the bezel. Rather than
 * keep trying to make the clip hold, this covers whatever escapes.
 *
 * A ring of opaque cabinet colour sitting a layer above the screen, with a
 * rounded hole exactly the size of the glass. The spread reaches far enough
 * to cover the worst case -- a square corner overhanging a rounded one by
 * about 0.3 of the radius -- while staying clear of the speaker below, which
 * starts further down the fascia than the ring reaches.
 *
 * No screws, incidentally: a Sony front was one seamless moulding. */
#cabinet::after {
  /* This drew an opaque ring of flat colour around the glass, to cover the
     canvas bleeding past the rounded corners. It covered the bleed and it
     also read as a grey bar around the picture, because a flat fill cannot
     match the moulding's gradient. Not worth the cure. */
  content: none;
}

/* The recess the tube sits in: a soft shadow all the way round where the
   glass meets the plastic, which is the detail that reads as depth. */
#cabinet::before {
  content: "";
  position: absolute;
  left: 2.5cqw; right: 2.5cqw;
  top: 2.5cqw;
  /* The glass exactly. Derived the SAME way the screen derives its own
     height -- from 4:3 on the width set by left/right -- rather than from a
     hand-computed 71.25cqw. Two elements describing one rectangle by two
     different rules agreed in Blink and disagreed in WebKit, which put this
     chamfer above the real bottom of the glass and drew a false screen edge
     across live picture. One rule, so they cannot diverge. */
  aspect-ratio: 4 / 3;
  border-radius: 2.6cqw / 3.1cqw;
  pointer-events: none;
  box-shadow:
    /* the narrow chamfer where the bezel steps down to the glass, catching
       a thin line of light along its upper edge */
    0 0 0 1px rgba(0, 0, 0, 0.95),
    0 -1px 0 1px rgba(255, 255, 255, 0.055),
    0 0 1.8vmin 0.55vmin rgba(0, 0, 0, 0.62),
    inset 0 0.25vmin 0.6vmin rgba(0, 0, 0, 0.78),
    /* the same recess along the bottom lip, a touch lighter because the
       light falls from above -- without it the glass looked stuck on at the
       bottom rather than set into the moulding */
    inset 0 -0.25vmin 0.6vmin rgba(0, 0, 0, 0.62);
  z-index: 2;
}

#screen {
  position: relative;
  /* Sized by the cabinet, not by what is left over: 95% of its width, and
     4:3, which fixes its height at 71.25% of the cabinet's width. The fascia
     then takes the remainder, so the two can never fight over the space. */
  flex: 0 0 auto;
  margin: 2.5cqw 2.5cqw 0;
  /* The height is stated outright, not left to aspect-ratio.
     This is a flex item, and WebKit does not apply aspect-ratio to a flex
     item whose cross size comes from stretch: measured on an iPhone, the
     glass came out 291.84px tall against the 277.27px that 4:3 requires --
     an aspect of 1.2668, a visibly stretched picture, and a chamfer (which
     IS absolutely positioned, and where WebKit does honour aspect-ratio)
     drawing its line 14.57px above the real bottom of the picture. That
     stray line across live picture is the symptom this fixes.
     95cqw wide and 4:3 means 71.25cqw tall, and cqw is definite, so every
     engine computes the same box. aspect-ratio stays as a belt and braces
     for anything that ignores the explicit height. */
  width: 95cqw;
  height: 71.25cqw;
  aspect-ratio: 4 / 3;
  border-radius: 2.6cqw / 3.1cqw;
  overflow: hidden;
  background: #05050a;
  z-index: 1;
  /* Give the picture its own stacking context so nothing inside it can climb
     above the cabinet that frames it. */
  isolation: isolate;
  /* And clip it for real. The canvases already carry border-radius: inherit,
     but a WebGL canvas is its own composited layer and paints its square
     corners straight through that clip -- measured as static bleeding out
     across the rounded corners of the moulding while the set was tuning.
     contain: paint clips the layer itself, which the radius alone does not. */
  contain: paint;
}

/* Curved glass: a broad sky reflection across the top left, a tight specular
   highlight on the corner, and the faintest green cast the phosphor coating
   gives a tube that is switched off. */
#screen::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  background:
    radial-gradient(120% 70% at 22% -12%,
      rgba(226, 240, 255, 0.13) 0%,
      rgba(226, 240, 255, 0.05) 26%,
      rgba(226, 240, 255, 0) 52%),
    radial-gradient(28% 20% at 84% 90%,
      rgba(180, 210, 235, 0.05) 0%,
      rgba(180, 210, 235, 0) 100%),
    linear-gradient(197deg,
      rgba(255, 255, 255, 0.05) 0%,
      rgba(255, 255, 255, 0) 30%);
  mix-blend-mode: screen;
}

/* C (or a long press) turns the tube simulation off, and that has to include
   the glare painted on the glass above -- it is the one piece of the illusion
   that lives in CSS rather than in the shader. */
body.no-tube #screen::after { display: none; }

#tube, #flat {
  display: block;
  /* 100%/100%, NOT position:absolute + inset:0. A canvas is a replaced
     element, and for replaced elements inset:0 with width:auto does not
     stretch to the parent -- it falls back to the intrinsic buffer size.
     #tube happens to survive that because crt.js pins an inline pixel size,
     but #flat has no such override, so the plain fallback canvas rendered at
     its buffer dimensions and the picture vanished the moment TUBE turned
     the shader off. The overhang this was meant to cure was never the
     problem: #screen's own height was (see its rule above). */
  width: 100%;
  height: 100%;
  border-radius: inherit;
}

#tube[hidden], #flat[hidden] { display: none; }

/* The band under the tube. A grid of two equal sides around the badge, not a
   flex row: in a row the speakers shared whatever the keys left over, which
   put the badge well left of centre. Here the centre column is centred on the
   set whatever the sides contain. */
#fascia {
  flex: 1 1 auto;
  height: 7cqw;
  min-height: 0;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 1.4cqw;
  padding: 0 2.5cqw;
  position: relative;
  z-index: 3;
}

.fascia-side {
  display: flex;
  align-items: center;
  gap: 1.3vmin;
  min-width: 0;
}

.badge {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  padding: 0 1.2cqw;
}

/* A brushed-silver wordmark set into the black: the one piece of metal
   finish on the front, which is how Sony badged these sets. Dark stamped
   lettering on dark plastic was all but invisible; silver reads at a glance
   and still belongs to the object rather than floating on top of it. */
.brand {
  font: 600 1.9cqw/1 "Helvetica Neue", Helvetica, Arial, sans-serif;
  letter-spacing: 0.32em;
  /* Letter-spacing trails after the last glyph and pushes the word off
     centre; pull that trailing space back in. */
  margin-right: -0.32em;
  background: linear-gradient(180deg,
    #f4f5f7 0%, #c6c9cf 46%, #8c9097 54%, #d8dbe0 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 1px 0 rgba(0, 0, 0, 0.9));
}

/* Speaker perforation either side of the badge: an even field of round holes
   punched through the moulding, which is what the grille on these sets was.
   Two of them, balanced about the badge, rather than one long slot. */
.speaker {
  flex: 1 1 0;
  min-width: 0;
  height: 2.3cqw;
  border-radius: 0.4cqw;
  background:
    radial-gradient(circle, rgba(0, 0, 0, 0.95) 0 34%, rgba(0, 0, 0, 0) 46%)
      center / 0.58cqw 0.58cqw,
    linear-gradient(180deg, #1c1d20 0%, #141518 100%);
  box-shadow:
    inset 0 1px 0.3cqw rgba(0, 0, 0, 0.85),
    0 1px 0 rgba(255, 255, 255, 0.045);
}

/* Standby lamp: a small red LED behind a clear lens, the one spot of colour on
   an otherwise black set. Smaller and quieter than before -- a lamp that glows
   hard draws the eye away from the picture, which is the opposite of its job. */
.led {
  flex: 0 0 auto;
  /* Always the far corner. Where the speaker and keys are hidden (a phone) the
     lamp would otherwise sit right against the badge. */
  margin-left: auto;
  width: 0.9cqw;
  height: 0.9cqw;
  border-radius: 50%;
  background:
    radial-gradient(circle at 35% 30%,
      #ffd9c6 0%, #ff5b38 36%, #b81d08 76%, #4f0a02 100%);
  box-shadow:
    0 0 0.75cqw rgba(255, 78, 40, 0.72),
    0 0 2cqw rgba(255, 60, 20, 0.28),
    0 0 0 1px rgba(0, 0, 0, 0.9);
}

/* Deliberately compact. The keys share the right-hand track with the speaker,
   so every bit of width they take comes straight off it -- sized larger, they
   ate the speaker entirely and left the fascia lopsided. */
.controls {
  display: flex;
  align-items: center;
  gap: 0.94cqw;
  flex: 0 0 auto;
  z-index: 20;
}

/* Each cluster sits in a shallow recess, like the key strip along the lower
   lip of the set, so the keys read as part of the moulding. */
.pad {
  display: flex;
  align-items: center;
  gap: 0.3cqw;
  padding: 0.24cqw;
  border-radius: 0.52cqw;
  background: linear-gradient(180deg, #0a0b0d 0%, #121316 100%);
  box-shadow:
    inset 0 1px 0.3cqw rgba(0, 0, 0, 0.9),
    0 1px 0 rgba(255, 255, 255, 0.05);
}

/* Silver-grey printed legend, the same family as the badge. */
.pad-label {
  font: 600 0.79cqw/1 "Helvetica Neue", Helvetica, Arial, sans-serif;
  letter-spacing: 0.16em;
  color: rgba(198, 202, 208, 0.55);
  padding: 0 0.22cqw;
}

/* The keys: small flush caps in the same black plastic as the cabinet, a
   hairline of light along the top edge and silver-grey legends. Quiet on
   purpose -- on a real set the controls were there to be found, not seen. */
.pad button {
  position: relative;
  width: 2.74cqw;
  height: 2.16cqw;
  padding: 0;
  border: none;
  border-radius: 0.36cqw;
  cursor: pointer;
  font-size: 0.9cqw;
  line-height: 1;
  color: #b8bcc3;
  background: linear-gradient(180deg, #2b2d31 0%, #1e2023 55%, #17181b 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.12),
    inset 0 -1px 0 rgba(0, 0, 0, 0.6),
    0 1px 0.2vmin rgba(0, 0, 0, 0.8);
  transition: transform 0.08s ease, box-shadow 0.08s ease,
              color 0.15s ease, background 0.15s ease;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Unicode triangles sit above the text midline, so nudge them down to sit
   optically centred in the key rather than riding against its top edge. */
.pad button span { display: block; transform: translateY(0.07cqw); }

.pad button.wide {
  width: auto;
  min-width: 4.03cqw;
  padding: 0 0.61cqw;
  font: 600 0.78cqw/1 "Helvetica Neue", Helvetica, Arial, sans-serif;
  letter-spacing: 0.14em;
}

.pad button:hover {
  color: #eef0f3;
  background: linear-gradient(180deg, #33353a 0%, #232529 55%, #1b1c1f 100%);
}

/* Pressed keys travel a single pixel into the recess and lose their lit top
   edge -- enough to feel, not so much that the key looks loose. */
.pad button:active {
  transform: translateY(1px);
  color: #ffffff;
  box-shadow:
    inset 0 1px 0.25vmin rgba(0, 0, 0, 0.9),
    0 0 0 rgba(0, 0, 0, 0);
}

.pad button:focus-visible {
  outline: 1px solid rgba(160, 196, 255, 0.85);
  outline-offset: 2px;
}

/* Bezel off. For watching this on an actual CRT, where the last thing you
   want is a drawing of a television inside a television: the tube fills the
   display edge to edge and every piece of furniture goes away, including the
   glare highlight, which is wrong when the real glass in front of you has its
   own. Toggle with B, or SPACE for full screen as well. */
body.cinema #set { padding: 0; }
body.cinema::before { display: none; }

body.cinema #cabinet {
  width: min(100vw, calc(100vh * 4 / 3));
  width: min(100vw, calc(100dvh * 4 / 3));
  padding: 0;
  border-radius: 0;
  background: #000;
  box-shadow: none;
}
/* The glass is the whole set now, so the frame around it goes too. */
body.cinema #screen { margin: 0; }

body.cinema #cabinet::before { display: none; }
/* The corner screws are a separate layer from the moulding. Hiding only the
   moulding left four little dots floating in the corners of the bare picture,
   on desktop and phone alike. */
body.cinema #cabinet::after { display: none; }
/* The phone layout rounds the screen to match its moulding, and that rule is
   more specific than the one above -- so with the bezel off a phone kept the
   rounded corners, and the bare picture had black wedges cut out of each one.
   Bezel off means a square picture, everywhere. */
/* ...and it goes edge to edge. The phone cabinet rule is more specific than
   the plain bezel-off one, so it was still reserving the frame's width. */
/* Full screen on a phone is a mode of its own: the cabinet goes, and so does
   the remote, because swipes take over from it (see setupGestures in tv.js).
   Nothing is reserved at the bottom any more, so the picture is centred in the
   whole screen and grows until 4:3 runs into an edge -- the full width of a
   phone held upright, the full height of one held sideways. */
body.cinema.has-handset #touch { display: none !important; }

body.cinema.has-handset #set {
  bottom: 0 !important;
  padding: 0 !important;
  align-items: center;
}

body.cinema.has-handset #cabinet {
  padding: 0 !important;
  border-radius: 0 !important;
  width: min(100vw, calc(100vh * 4 / 3)) !important;
  width: min(100vw, calc(100dvh * 4 / 3)) !important;
}
body.cinema.has-handset #screen { margin: 0 !important; width: 100cqw !important; height: 75cqw !important; }
body.cinema.has-handset #screen,
body.cinema #tube, body.cinema #flat { border-radius: 0 !important; }
body.cinema #screen { border-radius: 0; }
body.cinema #screen::after { display: none; }
body.cinema #fascia { display: none; }

/* BEZEL off: only the moulding goes. The cabinet keeps its size, so the tube
   stays exactly where it was; the plastic, its shadows, the recess round the
   glass, the badge and grilles disappear, and the keys stay in reach. */
body.no-bezel:not(.cinema) #cabinet {
  background: transparent;
  box-shadow: none;
}
body.no-bezel:not(.cinema) #cabinet::before,
body.no-bezel:not(.cinema) #cabinet::after { display: none; }
body.no-bezel:not(.cinema) #fascia .speaker,
body.no-bezel:not(.cinema) #fascia .badge { visibility: hidden; }
body.no-bezel:not(.cinema) .pad,
body.no-bezel:not(.cinema) .power-group { background: transparent; box-shadow: none; }
/* Desktop, bezel off (B): the moulding AND the keys go, and the bare tube
   grows -- still 4:3 -- into all the space the set occupied, up to the
   edges of the window (with the same breathing room the set had). Press B
   again for the set, its power switch and its keys. */
body.no-bezel:not(.cinema):not(.has-handset) #fascia { display: none; }
body.no-bezel:not(.cinema):not(.has-handset) #cabinet {
  width: min(calc(100vw - 2 * var(--gap)), calc((100vh - 2 * var(--gap)) * 4 / 3));
  width: min(calc(100vw - 2 * var(--gap)), calc((100dvh - 2 * var(--gap)) * 4 / 3));
  padding: 0;
}
body.no-bezel:not(.cinema):not(.has-handset) #screen {
  margin: 0;
  width: 100cqw;
  height: 75cqw;
}
/* On a phone the bare tube takes over the whole space the cabinet occupied:
   the moulding and fascia go, and the picture grows -- still 4:3 -- until it
   meets the edge of the phone or the top of the handset. The handset stays. */
body.no-bezel.has-handset:not(.cinema) #fascia { display: none; }
body.no-bezel.has-handset:not(.cinema) #cabinet {
  width: min(calc(100vw - 2 * var(--gap)),
             calc((100vh - var(--handset) - 2 * var(--gap)) * 4 / 3)) !important;
  width: min(calc(100vw - 2 * var(--gap)),
             calc((100dvh - var(--handset) - 2 * var(--gap)) * 4 / 3)) !important;
  padding: 0 !important;
}
body.no-bezel.has-handset:not(.cinema) #screen {
  margin: 0 !important;
  width: 100cqw !important;
  height: 75cqw !important;
}

/* The touch bar sits above everything, including the bezel-off picture, and
   is the only control that is always reachable. Without it, hiding the bezel
   on a phone leaves no way to bring it back, because the cabinet buttons go
   with it and there is no keyboard. */
/* The remote. On a phone it is the only control there is, so it is built as
   an object rather than a toolbar: the black plastic body of a late-90s Sony
   remote, soft grey rubber keys, and the volume and channel rockers those
   remotes all had. Legends are printed in the silver-grey of the set's badge;
   the glowing green monospace it replaces read as a terminal, not a TV. */
#touch {
  position: fixed;
  left: 50%;
  bottom: max(1.2rem, env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  max-width: min(96vw, 52rem);
  padding: 0.5rem;
  border-radius: 1.1rem;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E"),
    linear-gradient(180deg, #212226 0%, #17181b 50%, #0e0f11 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.10),
    inset 0 0 0 1px rgba(0, 0, 0, 0.85),
    0 0.8rem 2.4rem rgba(0, 0, 0, 0.75),
    0 0.15rem 0.4rem rgba(0, 0, 0, 0.6);
  z-index: 40;
  opacity: 0;
  transition: opacity 0.18s ease;
  pointer-events: none;
}

#touch.shown { opacity: 1; pointer-events: auto; }
#touch[hidden] { display: none !important; }

/* The IR window at the tip. Only drawn where the remote is shown upright as a
   whole object; the strip layouts have no room for it. */
.remote-head { display: none; }

/* One row by default: a tablet, or a phone on its side, where the remote sits
   over the picture and has to be as slim as it can be. The rockers go to the
   two ends, volume on the left and channel on the right. */
.remote-keys {
  display: flex;
  align-items: stretch;
  gap: 0.4rem;
}
.k-vol { order: -1; }
.k-ch  { order: 1; }

/* Rubber keys: soft grey with a gentle dome from a top highlight and a darker
   base. Each key is as wide as its legend -- sharing the width equally is what
   pushed FULL SCREEN out past the edge of its key. */
#touch button {
  position: relative;
  flex: 0 0 auto;
  min-height: 2.9rem;
  padding: 0 0.85rem;
  border: none;
  border-radius: 0.75rem;
  cursor: pointer;
  font: 600 0.66rem/1 "Helvetica Neue", Helvetica, Arial, sans-serif;
  letter-spacing: 0.12em;
  white-space: nowrap;
  color: #d3d6db;
  background: linear-gradient(180deg, #35373c 0%, #2a2c30 45%, #222327 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.14),
    inset 0 -1px 0 rgba(0, 0, 0, 0.55),
    0 1px 0 rgba(0, 0, 0, 0.7),
    0 2px 4px rgba(0, 0, 0, 0.45);
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  transition: transform 0.08s ease, background 0.12s ease,
              box-shadow 0.08s ease, color 0.12s ease;
}

/* A press sinks a pixel and loses the lit top edge: felt, not bounced. */
#touch button:active {
  transform: translateY(1px);
  color: #ffffff;
  background: linear-gradient(180deg, #2a2c30 0%, #222327 55%, #1c1d20 100%);
  box-shadow:
    inset 0 1px 3px rgba(0, 0, 0, 0.7),
    0 0 0 rgba(0, 0, 0, 0);
}

/* Volume and channel rockers: one recessed pill with a key at each end and the
   name printed between them. Flat in the strip, stood upright on the portrait
   remote. row-reverse so the flat version reads minus, name, plus. */
.rocker {
  display: flex;
  flex-direction: row-reverse;
  align-items: stretch;
  gap: 2px;
  padding: 2px;
  border-radius: 0.8rem;
  /* Darker than the body it is sunk into, or the pair of keys reads as two
     separate buttons with a word between them rather than one rocker. */
  background: linear-gradient(180deg, #08090a 0%, #0d0e10 100%);
  box-shadow:
    inset 0 2px 3px rgba(0, 0, 0, 0.95),
    inset 0 0 0 1px rgba(0, 0, 0, 0.8),
    0 1px 0 rgba(255, 255, 255, 0.06);
}

#touch .rocker button {
  min-width: 2.7rem;
  padding: 0;
  font-size: 1.05rem;
  font-weight: 500;
  letter-spacing: 0;
  border-radius: 0.66rem;
  display: flex;
  align-items: center;
  justify-content: center;
}

.rock-label {
  align-self: center;
  padding: 0 0.35rem;
  font: 600 0.56rem/1 "Helvetica Neue", Helvetica, Arial, sans-serif;
  letter-spacing: 0.16em;
  color: rgba(198, 202, 208, 0.6);
}

/* The activity LED beside the IR window. Dark until a key is pressed, then a
   short red blink -- the one moving part on a remote, and the thing that tells
   you the press registered when the set itself is across the room. */
.remote-led {
  position: absolute;
  right: 1.5rem;
  top: 0.12rem;
  width: 0.46rem;
  height: 0.46rem;
  border-radius: 50%;
  background: #360d0d;
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.9), 0 0 0 1px rgba(0, 0, 0, 0.7);
  transition: background 0.18s ease, box-shadow 0.18s ease;
}

.remote-led.lit {
  background: radial-gradient(circle at 36% 32%, #ffd2c6 0%, #ff5a3c 45%, #c41f08 100%);
  box-shadow:
    0 0 0.45rem rgba(255, 80, 50, 0.95),
    0 0 1.1rem rgba(255, 60, 30, 0.5),
    0 0 0 1px rgba(0, 0, 0, 0.7);
  transition: background 0.04s ease, box-shadow 0.04s ease;
}

/* The hint shown when full screen swallows the remote. */
#immersive-hint {
  position: fixed;
  left: 50%;
  bottom: max(2.2rem, env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 60;
  padding: 0.55rem 1rem;
  border-radius: 2rem;
  font: 600 0.66rem/1 "Helvetica Neue", Helvetica, Arial, sans-serif;
  letter-spacing: 0.16em;
  color: #e7e9ec;
  background: rgba(10, 11, 13, 0.66);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.10);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.35s ease;
}
#immersive-hint.shown { opacity: 1; }
#immersive-hint[hidden] { display: none; }

/* Portrait phone. A 4:3 picture in a tall window leaves a lot of black above
   and below it, so put the picture at the top and let the controls live in
   the space underneath rather than covering the programme. */
@media (orientation: portrait) and (max-width: 820px) {
  /* The set and the handset read as one object. The handset stays pinned to
     the bottom of the window where a thumb can reach it, and the picture is
     centred in whatever is left above it -- rather than jammed against the
     top with half a phone screen of dead black in between. */
  /* The upright remote's measurements, shared by the remote and by #set so
     the space reserved for it is computed from the same numbers it is built
     from. Change a key size here and the set moves with it; they cannot drift
     apart and overlap. */
  body.has-handset {
    --r-key: 3.35rem;
    --r-gap: 0.62rem;
    --r-pad: 1rem;
    --r-head: 1.35rem;
  }

  body.has-handset #set {
    --handset: calc(0.6rem + var(--r-head) + 4 * var(--r-key) + 3 * var(--r-gap)
                    + max(var(--r-pad), env(safe-area-inset-bottom, 0px)));
    bottom: var(--handset);
    /* A 4:3 picture is as wide as the phone allows and no taller, so on a
       tall screen there is always height left over. Tried putting all of it
       above the set, which reads as the picture having fallen to the bottom;
       split evenly above and below it, the set sits in the middle of its own
       space and the handset keeps the bottom of the screen for the thumb. */
    align-items: center;
    padding: 0.5rem 0 0.55rem;
  }

  /* On a phone the handset is the control surface, so it stays put instead
     of fading out and leaving a gap where it was. */
  body.has-handset #touch { opacity: 1; pointer-events: auto; }

  /* The cabinet sizes itself from the viewport height, but with the handset
     taking the bottom of the screen #set is shorter than the viewport -- so
     the cabinet was being built taller than the space it had to live in and
     the picture spilled straight out over the bezel. Size it from what is
     actually left instead. */
  /* Exactly the same fit calculation as the desktop, with the remote's height
     taken out of what is available first. Nothing about the set's proportions
     changes on a phone -- it is the same object, just smaller -- which is why
     there are no rem overrides here any more. Those were what made the phone
     layout drift out of step with the desktop one. */
  body.has-handset #cabinet {
    width: min(calc(100vw - 2 * var(--gap)),
               calc((100vh - var(--handset) - 2 * var(--gap)) * 1.2384));
    width: min(calc(100vw - 2 * var(--gap)),
               calc((100dvh - var(--handset) - 2 * var(--gap)) * 1.2384));
    max-height: 100%;
  }

  body.has-handset #cabinet::before { z-index: 4; }
  body.has-handset #screen::after { border-radius: inherit; }

  /* The cabinet's own keys are unusable at this size and crowd the badge; the
     remote below does that job properly.
     ONLY where there is a remote. This used to apply to any narrow portrait
     window, so a tall desktop window lost the cabinet's buttons and had
     nothing to replace them with -- the remote only exists on a touch
     device. */
  body.has-handset #fascia .controls { display: none; }

  /* Upright: the remote as an object, full width along the bottom of the
     phone, its heights taken from the variables #set reserves space with. */
  #touch {
    left: 0;
    right: 0;
    bottom: 0;
    transform: none;
    max-width: none;
    border-radius: 1.4rem 1.4rem 0 0;
    padding: 0.6rem var(--r-pad) max(var(--r-pad), env(safe-area-inset-bottom, 0px));
  }

  /* The tip, with the dark IR window the remote was pointed at the set with.
     Decoration only, but it is what makes this read as a remote. */
  .remote-head {
    position: relative;
    display: flex;
    justify-content: center;
    align-items: flex-start;
    height: var(--r-head);
  }
  .remote-head .ir {
    width: 3.4rem;
    height: 0.42rem;
    border-radius: 0.21rem;
    background: linear-gradient(180deg, #2b0e0e 0%, #130606 60%, #1e0a0a 100%);
    box-shadow:
      inset 0 1px 0 rgba(255, 120, 120, 0.10),
      0 0 0 1px rgba(0, 0, 0, 0.9);
  }

  /* Three keys across the top, the rockers standing either side of TUBE and
   BEZEL, and FULL SCREEN along the bottom where the thumb rests. */
  .remote-keys {
    display: grid;
    grid-template-columns: 1fr 1.2fr 1fr;
    grid-auto-rows: var(--r-key);
    grid-template-areas:
      "mute  guide info"
      "vol   tube  ch"
      "vol   bezel ch"
      "full  full  full";
    gap: var(--r-gap);
  }
  .k-mute  { grid-area: mute; }
  .k-guide { grid-area: guide; }
  .k-info  { grid-area: info; }
  .k-vol   { grid-area: vol; }
  .k-tube  { grid-area: tube; }
  .k-ch    { grid-area: ch; }
  .k-bezel { grid-area: bezel; }
  .k-full  { grid-area: full; }

  #touch button {
    min-height: 0;
    padding: 0 0.2rem;
    font-size: 0.7rem;
  }

  .rocker {
    flex-direction: column;
    border-radius: 1.25rem;
  }
  #touch .rocker button {
    flex: 1 1 0;
    min-width: 0;
    font-size: 1.4rem;
    border-radius: 1.08rem;
  }
  .rock-label { padding: 0.2rem 0; }
}

/* Phone held sideways. The picture already fills the height, so there is no
   room to put the handset below it -- it has to sit over the programme, and
   the full-size version covered two thirds of the picture with an opaque
   slab. In landscape it becomes what a video player's controls are: one slim
   translucent strip along the bottom edge, which fades itself away again a
   few seconds after the last touch. */
@media (orientation: landscape) and (max-height: 560px) {
  /* Bezel off in landscape: the strip floats over the picture rather than
     taking a slice of the screen, so the bare tube sizes from the full height
     -- not from what the upright handset would have left. */
  body.no-bezel.has-handset:not(.cinema) #cabinet {
    width: min(calc(100vw - 2 * var(--gap)), calc((100vh - 2 * var(--gap)) * 4 / 3)) !important;
    width: min(calc(100vw - 2 * var(--gap)), calc((100dvh - 2 * var(--gap)) * 4 / 3)) !important;
  }
  body.has-handset #touch {
    left: 50%;
    right: auto;
    bottom: max(0.5rem, env(safe-area-inset-bottom, 0px));
    transform: translateX(-50%);
    width: auto;
    max-width: calc(100vw - 1.2rem);
    padding: 0.3rem;
    border-radius: 0.9rem;
    /* It sits over the programme here, so the body is smoked glass rather
       than solid plastic: the picture stays readable through it. */
    background: rgba(14, 15, 17, 0.72);
    backdrop-filter: blur(10px) saturate(1.2);
    -webkit-backdrop-filter: blur(10px) saturate(1.2);
  }

  body.has-handset .remote-keys { gap: 0.3rem; }

  body.has-handset #touch button {
    min-height: 2.35rem;
    padding: 0 0.6rem;
    font-size: 0.58rem;
    letter-spacing: 0.08em;
    border-radius: 0.6rem;
  }

  body.has-handset #touch .rocker button {
    min-width: 2.2rem;
    font-size: 0.95rem;
  }
}

#power {
  position: fixed;
  inset: 0;
  cursor: pointer;
  z-index: 10;
}
#power.hidden { display: none; }

/* Kept out of the layout entirely -- these are sources, not pictures. */
#source, #stage {
  position: fixed;
  left: -10000px;
  top: -10000px;
  width: 960px;
  height: 720px;
  pointer-events: none;
}

/* Nothing here any more.
 *
 * This block used to re-specify the cabinet's width, padding, radius, fascia
 * height and lettering for narrow windows, in units that disagreed with the
 * rules above -- which is how the frame ended up a few pixels wide at some
 * sizes and off screen at others. Every dimension is now a percentage of the
 * cabinet's own width, so the set scales as one object and needs no special
 * case for a small window. */

/* =============================================================================
   SUBSCRIBER ACCESS
   The power switch, the lamp beside it, the tube switching on and off, and the
   sign-in card and invite setup drawn on the glass (web/auth.js).

   Chrome here is neutral -- platinum and white phosphor on the dark tube.
   Colour is spent only where it reports something: the lamp (standby red,
   warming amber, on green), ACCESS DENIED in red, ACCESS GRANTED in green.
   ========================================================================== */

/* --- the power switch, far left of the fascia ------------------------------ */

.power-group {
  display: flex;
  align-items: center;
  gap: 0.62cqw;
  flex: 0 0 auto;
  padding: 0.3cqw 0.7cqw 0.3cqw 0.3cqw;
  border-radius: 2cqw;
  /* The same shallow recess the key clusters sit in. */
  background: linear-gradient(180deg, #0a0b0d 0%, #121316 100%);
  box-shadow:
    inset 0 1px 0.3cqw rgba(0, 0, 0, 0.9),
    0 1px 0 rgba(255, 255, 255, 0.05);
}

#btn-power {
  position: relative;
  width: 2.7cqw;
  height: 2.7cqw;
  padding: 0;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  display: grid;
  place-items: center;
  color: #b8bcc3;
  background:
    radial-gradient(circle at 50% 30%, rgba(255, 255, 255, 0.10) 0%, rgba(255, 255, 255, 0) 55%),
    linear-gradient(180deg, #2e3034 0%, #1f2124 55%, #16171a 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.14),
    inset 0 -1px 0 rgba(0, 0, 0, 0.6),
    0 0 0 0.12cqw rgba(0, 0, 0, 0.85),
    0 1px 0.3vmin rgba(0, 0, 0, 0.8);
  transition: transform 0.08s ease, color 0.2s ease, box-shadow 0.2s ease;
}
#btn-power svg {
  width: 1.3cqw;
  height: 1.3cqw;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.6;
  stroke-linecap: round;
  filter: drop-shadow(0 1px 0 rgba(0, 0, 0, 0.8));
}
#btn-power:hover { color: #f2f4f7; }
#btn-power:active {
  transform: translateY(1px);
  box-shadow:
    inset 0 1px 0.3vmin rgba(0, 0, 0, 0.9),
    0 0 0 0.12cqw rgba(0, 0, 0, 0.85);
}
#btn-power:focus-visible { outline: 1px solid rgba(220, 228, 240, 0.85); outline-offset: 3px; }

.power-label {
  font: 600 0.72cqw/1 "Helvetica Neue", Helvetica, Arial, sans-serif;
  letter-spacing: 0.18em;
  color: rgba(198, 202, 208, 0.55);
}

/* The lamp now lives beside the switch, not in the far corner. */
.power-group .led { margin-left: 0.1cqw; transition: background 0.5s ease, box-shadow 0.5s ease; }

/* Standby: the red lamp breathes very slowly, the way a set in standby does. */
.led[data-mode="standby"] { animation: lamp-breathe 4.8s ease-in-out infinite; }
@keyframes lamp-breathe {
  0%, 100% { filter: brightness(0.82); }
  50%      { filter: brightness(1.12); }
}
.led[data-mode="warming"] {
  background: radial-gradient(circle at 35% 30%, #fff1cc 0%, #ffb640 38%, #b86b08 78%, #4a2a02 100%);
  box-shadow: 0 0 0.75cqw rgba(255, 170, 60, 0.75), 0 0 2cqw rgba(255, 150, 40, 0.3), 0 0 0 1px rgba(0, 0, 0, 0.9);
  animation: lamp-blink 0.32s steps(1) infinite;
}
@keyframes lamp-blink { 50% { filter: brightness(0.45); } }
.led[data-mode="on"] {
  background: radial-gradient(circle at 35% 30%, #e6ffe9 0%, #74e08e 38%, #1f8a3d 78%, #0a3a18 100%);
  box-shadow: 0 0 0.75cqw rgba(110, 225, 140, 0.7), 0 0 2cqw rgba(90, 210, 120, 0.25), 0 0 0 1px rgba(0, 0, 0, 0.9);
}
body.auth-busy .led { animation: lamp-blink 0.24s steps(1) infinite; }

/* --- standby, and the tube switching on and off ----------------------------- */

#tube, #flat { transition: opacity 0.4s ease; }
body.standby #tube, body.standby #flat { opacity: 0; }

/* Power off: the picture collapses to a bright horizontal line, the line
   shrinks to a dot, and the dot lingers as phosphor afterglow. */
#screen.crt-off #tube, #screen.crt-off #flat {
  animation: crt-off 0.78s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
  transform-origin: 50% 50%;
}
@keyframes crt-off {
  0%   { transform: scale(1, 1);        filter: brightness(1) saturate(1); opacity: 1; }
  38%  { transform: scale(1.01, 0.006); filter: brightness(4.5) saturate(0); opacity: 1; }
  72%  { transform: scale(0.14, 0.005); filter: brightness(8) saturate(0);   opacity: 1; }
  100% { transform: scale(0, 0.004);    filter: brightness(10) saturate(0);  opacity: 0; }
}
#screen::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 1.6cqw;
  height: 1.6cqw;
  margin: -0.8cqw 0 0 -0.8cqw;
  border-radius: 50%;
  background: radial-gradient(circle, #ffffff 0%, rgba(225, 236, 255, 0.85) 30%, rgba(170, 200, 255, 0) 70%);
  opacity: 0;
  pointer-events: none;
  z-index: 6;
}
#screen.crt-off::before { animation: afterglow 1.7s ease-out 0.6s forwards; }
@keyframes afterglow {
  0%   { opacity: 1;   transform: scale(1.3); }
  15%  { opacity: 0.9; transform: scale(1); }
  100% { opacity: 0;   transform: scale(0.35); }
}

/* Power on: a dot becomes a line, the line opens into the picture, and the
   picture settles from over-bright to normal as the tube warms. */
#screen.crt-on #tube, #screen.crt-on #flat {
  animation: crt-on 0.62s cubic-bezier(0.16, 1, 0.3, 1) both;
  transform-origin: 50% 50%;
}
@keyframes crt-on {
  0%   { transform: scale(0, 0.004);    filter: brightness(9) saturate(0); opacity: 1; }
  28%  { transform: scale(1, 0.006);    filter: brightness(6) saturate(0); }
  62%  { transform: scale(1, 1.035);    filter: brightness(2.2) saturate(0.6); }
  100% { transform: scale(1, 1);        filter: brightness(1) saturate(1); }
}

/* --- the sign-in card on the glass ------------------------------------------- */

.auth {
  position: absolute;
  inset: 0;
  z-index: 4;
  container-type: size;
  display: grid;
  place-items: center;
  overflow: hidden;
  border-radius: inherit;
  color: #eef2f8;
  font-family: "Courier New", Courier, "Lucida Console", monospace;
  -webkit-font-smoothing: antialiased;
}
.auth[hidden] { display: none; }

/* Smoked glass over a tube full of snow: dark enough to read, thin enough that
   the static still moves behind the card. */
.auth-glass {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(120% 95% at 50% 42%, rgba(10, 13, 20, 0.72) 0%, rgba(2, 3, 6, 0.93) 70%, rgba(0, 0, 0, 0.97) 100%);
}
/* Scanlines, and a slow brighter band rolling down the picture the way an
   un-synced tube's vertical hold drifts. */
.auth-glass::before {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(0deg,
    rgba(255, 255, 255, 0.035) 0 1px, rgba(0, 0, 0, 0) 1px 3px);
  pointer-events: none;
}
.auth-glass::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 22%;
  top: -22%;
  background: linear-gradient(180deg, rgba(200, 215, 255, 0) 0%, rgba(200, 215, 255, 0.045) 50%, rgba(200, 215, 255, 0) 100%);
  animation: vhold 7s linear infinite;
  pointer-events: none;
}
@keyframes vhold { to { transform: translateY(560%); } }

.auth-card {
  /* One unit for everything on the card, tied to the glass: the height on a
     4:3 tube, the width on a tall phone sheet, so it scales as one object. */
  --u: min(1cqh, 1.3333cqw);
  position: relative;
  width: calc(var(--u) * 118);
  max-width: 92cqw;
  display: flex;
  flex-direction: column;
  gap: calc(var(--u) * 2.4);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-shadow:
    0 0 calc(var(--u) * 1.3) rgba(205, 222, 255, 0.42),
    calc(var(--u) * 0.32) calc(var(--u) * 0.32) 0 rgba(0, 0, 0, 0.92);
}
.auth.is-in .auth-card { animation: card-in 0.52s cubic-bezier(0.2, 0.9, 0.2, 1) both; }
@keyframes card-in {
  0%   { opacity: 0; transform: translateY(calc(var(--u) * 2)) scaleY(1.08); filter: blur(3px) brightness(2.4); }
  45%  { opacity: 1; filter: blur(0.6px) brightness(1.5); }
  60%  { transform: translateX(calc(var(--u) * -0.6)); }
  100% { opacity: 1; transform: none; filter: none; }
}
/* Anything that arrives on a card arrives in sequence, not all at once. */
/* backwards, not both: the clip is only for the reveal. Left in place after it
   ends, it trims focus rings and glows that sit outside a line's own box. */
.auth.is-in .auth-card > * { animation: line-in 0.4s ease backwards; }
.auth.is-in .auth-card > :nth-child(2) { animation-delay: 0.06s; }
.auth.is-in .auth-card > :nth-child(3) { animation-delay: 0.12s; }
.auth.is-in .auth-card > :nth-child(4) { animation-delay: 0.18s; }
.auth.is-in .auth-card > :nth-child(5) { animation-delay: 0.24s; }
.auth.is-in .auth-card > :nth-child(6) { animation-delay: 0.30s; }
.auth.is-in .auth-card > :nth-child(7) { animation-delay: 0.36s; }
@keyframes line-in { from { opacity: 0; clip-path: inset(-40% 100% -40% -10%); } to { opacity: 1; clip-path: inset(-40% -10% -40% -10%); } }

/* Horizontal sync tearing when something is refused. */
.auth-card.glitch { animation: glitch 0.42s steps(1) both !important; }
@keyframes glitch {
  0%   { transform: translateX(0); }
  12%  { transform: translateX(calc(var(--u) * -2.2)) skewX(-5deg); clip-path: inset(10% 0 55% 0); }
  24%  { transform: translateX(calc(var(--u) * 2.6)) skewX(4deg);  clip-path: inset(48% 0 12% 0); }
  36%  { transform: translateX(calc(var(--u) * -1.2));              clip-path: inset(0 0 0 0); }
  50%  { transform: translateX(calc(var(--u) * 0.9)) skewX(-2deg); }
  70%  { transform: translateX(calc(var(--u) * -0.4)); }
  100% { transform: none; }
}

.auth-ident {
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 2.4);
  font-size: calc(var(--u) * 2.9);
  letter-spacing: 0.55em;
  color: #cfd5de;
}
.auth-ident b { font-weight: 700; margin-right: -0.55em; }
.auth-ident .rule {
  flex: 1 1 0;
  height: calc(var(--u) * 0.26);
  background: linear-gradient(90deg, rgba(220, 228, 240, 0), rgba(220, 228, 240, 0.55) 50%, rgba(220, 228, 240, 0));
}

.auth-title {
  margin: 0;
  font-size: calc(var(--u) * 6.4);
  line-height: 1.04;
  letter-spacing: 0.07em;
  color: #ffffff;
  /* The faintest colour fringing, like convergence on a real tube. */
  text-shadow:
    calc(var(--u) * -0.18) 0 0 rgba(255, 90, 90, 0.35),
    calc(var(--u) * 0.18) 0 0 rgba(90, 160, 255, 0.35),
    0 0 calc(var(--u) * 1.6) rgba(205, 222, 255, 0.5),
    calc(var(--u) * 0.36) calc(var(--u) * 0.36) 0 rgba(0, 0, 0, 0.92);
}
.auth-title.huge { font-size: calc(var(--u) * 10.5); letter-spacing: 0.04em; }

.auth-lede {
  margin: 0;
  font-size: calc(var(--u) * 2.9);
  line-height: 1.55;
  color: #c3cad6;
}
.auth-lede.dim { color: #8b94a2; }

.auth-stamp {
  margin: 0;
  font-size: calc(var(--u) * 3);
  letter-spacing: 0.42em;
  color: #cfd5de;
}

.auth-form { display: grid; gap: calc(var(--u) * 2.3); }

.auth-field {
  display: grid;
  grid-template-columns: calc(var(--u) * 21) 1fr;
  align-items: end;
  gap: calc(var(--u) * 2);
  font-size: calc(var(--u) * 3.1);
  letter-spacing: 0.2em;
  color: #8f99a8;
}
.auth-field.wide { grid-template-columns: calc(var(--u) * 12) 1fr; }
.auth-field input {
  width: 100%;
  min-width: 0;
  font: 700 max(16px, calc(var(--u) * 4.4))/1.25 "Courier New", Courier, monospace;
  letter-spacing: 0.08em;
  color: #ffffff;
  caret-color: #ffffff;
  background: transparent;
  border: 0;
  border-radius: 0;
  border-bottom: calc(var(--u) * 0.34) solid rgba(220, 228, 240, 0.28);
  padding: calc(var(--u) * 0.5) calc(var(--u) * 0.4);
  outline: none;
  text-shadow: 0 0 calc(var(--u) * 1.2) rgba(210, 225, 255, 0.55);
  transition: border-color 0.2s ease, box-shadow 0.25s ease;
  -webkit-appearance: none;
  appearance: none;
}
.auth-field input::placeholder { color: rgba(200, 208, 220, 0.25); }
.auth-field input:focus {
  border-bottom-color: #ffffff;
  box-shadow: 0 calc(var(--u) * 0.9) calc(var(--u) * 1.4) calc(var(--u) * -0.9) rgba(210, 225, 255, 0.7);
}
.auth-field input[readonly] { color: #aeb6c3; border-bottom-style: dashed; }
.auth-field input:-webkit-autofill,
.auth-field input:-webkit-autofill:focus {
  -webkit-text-fill-color: #ffffff;
  transition: background-color 99999s ease 0s;
}

.auth-row { display: flex; align-items: center; gap: calc(var(--u) * 2); min-height: calc(var(--u) * 6); }
.auth-row.center { justify-content: center; text-align: center; }
.auth-row.split { justify-content: space-between; }

.auth-msg {
  flex: 1 1 auto;
  margin: 0;
  min-height: calc(var(--u) * 3.4);
  font-size: calc(var(--u) * 2.8);
  letter-spacing: 0.1em;
  color: #c9d0dc;
}
.auth-msg.error {
  color: #ff6b5e;
  text-shadow: 0 0 calc(var(--u) * 1.4) rgba(255, 80, 60, 0.6), calc(var(--u) * 0.3) calc(var(--u) * 0.3) 0 #000;
  animation: msg-flash 0.9s steps(1) 1;
}
@keyframes msg-flash { 0%, 20%, 40% { opacity: 1; } 10%, 30% { opacity: 0.2; } }

.auth-go {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: calc(var(--u) * 1.4);
  font: 700 calc(var(--u) * 3.2)/1 "Courier New", Courier, monospace;
  letter-spacing: 0.16em;
  color: #0b0d11;
  text-decoration: none;
  text-shadow: none;
  background: linear-gradient(180deg, #fbfcfe 0%, #dfe3ea 55%, #c4c9d2 100%);
  border: 0;
  border-radius: calc(var(--u) * 0.8);
  padding: calc(var(--u) * 1.6) calc(var(--u) * 3);
  cursor: pointer;
  box-shadow:
    0 0 calc(var(--u) * 2.4) rgba(210, 225, 255, 0.32),
    inset 0 -2px 0 rgba(0, 0, 0, 0.18);
  transition: transform 0.12s ease, box-shadow 0.2s ease, opacity 0.2s ease, filter 0.2s ease;
}
.auth-go i { font-style: normal; transition: transform 0.2s ease; }
.auth-go:hover { box-shadow: 0 0 calc(var(--u) * 3.6) rgba(220, 232, 255, 0.55), inset 0 -2px 0 rgba(0, 0, 0, 0.18); }
.auth-go:hover i { transform: translateX(calc(var(--u) * 0.7)); }
.auth-go:active { transform: translateY(1px) scale(0.99); }
.auth-go:disabled { opacity: 0.35; cursor: default; box-shadow: none; }
.auth-go:focus-visible, .auth-link:focus-visible, .auth-key:focus-visible {
  outline: calc(var(--u) * 0.3) solid rgba(255, 255, 255, 0.9);
  outline-offset: calc(var(--u) * 0.6);
}
.auth-go.small { font-size: calc(var(--u) * 2.6); padding: calc(var(--u) * 1.3) calc(var(--u) * 2.2); margin-top: calc(var(--u) * 1.6); }
.auth.busy .auth-go { animation: busy 0.8s ease-in-out infinite; }
@keyframes busy { 50% { filter: brightness(0.7); } }

.auth-link {
  background: none;
  border: 0;
  padding: calc(var(--u) * 1) 0;
  cursor: pointer;
  font: 700 calc(var(--u) * 2.6)/1 "Courier New", Courier, monospace;
  letter-spacing: 0.14em;
  color: #9fa8b6;
  text-shadow: inherit;
  transition: color 0.15s ease;
}
.auth-link:hover { color: #ffffff; }
.auth-actions { display: inline-flex; gap: calc(var(--u) * 3); }

.auth-foot {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(var(--u) * 1.3);
  margin-top: calc(var(--u) * 0.8);
  font-size: calc(var(--u) * 2.2);
  letter-spacing: 0.24em;
  color: #6f7886;
}
.auth-dot {
  width: calc(var(--u) * 1.1);
  height: calc(var(--u) * 1.1);
  border-radius: 50%;
  background: #ff5840;
  box-shadow: 0 0 calc(var(--u) * 1.2) rgba(255, 80, 50, 0.9);
  animation: lamp-breathe 2.4s ease-in-out infinite;
}

/* --- the access code: six cells in the channel numerals --------------------- */

.auth-code { position: relative; display: flex; justify-content: center; cursor: text; }
.auth-code-input {
  position: absolute;
  inset: 0;
  width: 100%;
  opacity: 0;
  font-size: 16px;               /* stops iOS zooming into the field */
  border: 0;
  background: transparent;
  color: transparent;
  caret-color: transparent;
}
.auth-code .cells { display: flex; gap: calc(var(--u) * 1.8); pointer-events: none; }
.auth-code .cell {
  position: relative;
  width: calc(var(--u) * 11.6);
  height: calc(var(--u) * 16);
  display: grid;
  place-items: center;
  font: 700 calc(var(--u) * 13)/1 "Courier New", Courier, monospace;
  color: #ffffff;
  border-bottom: calc(var(--u) * 0.6) solid rgba(220, 228, 240, 0.22);
  text-shadow:
    0 0 calc(var(--u) * 2.4) rgba(215, 228, 255, 0.75),
    calc(var(--u) * 0.5) calc(var(--u) * 0.5) 0 rgba(0, 0, 0, 0.95);
  transition: border-color 0.2s ease;
}
.auth-code .cell:nth-child(3) { margin-right: calc(var(--u) * 2.6); }
.auth-code .cell.filled { border-bottom-color: rgba(245, 248, 252, 0.85); animation: cell-pop 0.22s ease-out; }
@keyframes cell-pop { 0% { transform: scale(1.25); filter: brightness(2); } 100% { transform: none; } }
.auth-code .cell.active::after {
  content: "";
  position: absolute;
  left: 18%;
  right: 18%;
  bottom: calc(var(--u) * 1.6);
  height: calc(var(--u) * 0.7);
  background: #ffffff;
  box-shadow: 0 0 calc(var(--u) * 1.4) rgba(220, 232, 255, 0.9);
  animation: cursor-blink 1.05s steps(1) infinite;
}
@keyframes cursor-blink { 50% { opacity: 0; } }
.auth.busy .auth-code .cell { animation: cells-scan 0.9s linear infinite; }
.auth.busy .auth-code .cell:nth-child(2) { animation-delay: 0.1s; }
.auth.busy .auth-code .cell:nth-child(3) { animation-delay: 0.2s; }
.auth.busy .auth-code .cell:nth-child(4) { animation-delay: 0.3s; }
.auth.busy .auth-code .cell:nth-child(5) { animation-delay: 0.4s; }
.auth.busy .auth-code .cell:nth-child(6) { animation-delay: 0.5s; }
@keyframes cells-scan { 0%, 100% { filter: brightness(1); } 40% { filter: brightness(2.2); } }

/* --- granted, welcome, progress ------------------------------------------------- */

.auth-granted, .auth-welcome { display: grid; gap: calc(var(--u) * 2.6); text-align: center; justify-items: center; }
.auth.granted .auth-stamp {
  color: #82e6a4;
  letter-spacing: 0.5em;
  text-shadow: 0 0 calc(var(--u) * 1.8) rgba(110, 230, 150, 0.7), calc(var(--u) * 0.3) calc(var(--u) * 0.3) 0 #000;
  animation: granted-flash 1.2s steps(1) 1;
}
@keyframes granted-flash { 0%, 16%, 32% { opacity: 1; } 8%, 24% { opacity: 0.15; } }
.auth.granted .auth-lede::after { content: "…"; display: inline-block; width: 1.4em; text-align: left; animation: dots 1.2s steps(4) infinite; }
@keyframes dots { 0% { clip-path: inset(0 100% 0 0); } 100% { clip-path: inset(0 -10% 0 0); } }

.auth-steps {
  display: flex;
  justify-content: center;
  gap: calc(var(--u) * 3.4);
  font-size: calc(var(--u) * 2.2);
  letter-spacing: 0.16em;
  color: #7a8390;
}
.auth-steps span { position: relative; padding-bottom: calc(var(--u) * 0.9); white-space: nowrap; }
.auth-steps .done { color: #9aa3b0; }
.auth-steps .on { color: #ffffff; }
.auth-steps .on::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: calc(var(--u) * 0.28);
  background: #ffffff;
  box-shadow: 0 0 calc(var(--u) * 1.2) rgba(220, 232, 255, 0.8);
}

.auth-checks {
  list-style: none;
  display: flex;
  gap: calc(var(--u) * 4);
  margin: calc(var(--u) * -0.6) 0 0 calc(var(--u) * 23);
  padding: 0;
  font-size: calc(var(--u) * 2.3);
  letter-spacing: 0.12em;
  color: #66707e;
}
.auth-checks li::before { content: "○ "; }
.auth-checks li.ok { color: #e6eaf1; }
.auth-checks li.ok::before { content: "● "; }

/* --- authenticator enrolment ------------------------------------------------------ */

.auth-enrol {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: calc(var(--u) * 4);
  align-items: center;
}
/* The QR code is the one thing on the glass that is not phosphor: a crisp
   white plate, square pixels, a quiet zone -- anything else and cameras miss. */
.auth-qr {
  margin: 0;
  padding: calc(var(--u) * 1.2);
  background: #ffffff;
  border-radius: calc(var(--u) * 1);
  box-shadow: 0 0 calc(var(--u) * 3.4) rgba(215, 228, 255, 0.4), 0 0 0 calc(var(--u) * 0.4) rgba(255, 255, 255, 0.15);
  animation: qr-in 0.7s cubic-bezier(0.2, 0.9, 0.2, 1) both 0.15s;
}
@keyframes qr-in { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0 0 0 0); } }
.auth-qr img { display: block; width: calc(var(--u) * 31); height: calc(var(--u) * 31); image-rendering: pixelated; }
.auth-enrol-steps ol {
  margin: 0;
  padding-left: 0;
  list-style-position: inside;
  display: grid;
  gap: calc(var(--u) * 1.5);
  font-size: calc(var(--u) * 2.6);
  line-height: 1.4;
  color: #dfe4ec;
}
.auth-enrol-steps small { font-size: calc(var(--u) * 2); color: #87909e; letter-spacing: 0.08em; }
.auth-key {
  display: flex;
  align-items: baseline;
  gap: calc(var(--u) * 1.6);
  margin-top: calc(var(--u) * 2);
  padding: calc(var(--u) * 1.1) calc(var(--u) * 1.6);
  font: 700 calc(var(--u) * 2.4)/1.3 "Courier New", Courier, monospace;
  color: #e7ebf2;
  text-align: left;
  text-shadow: inherit;
  background: rgba(255, 255, 255, 0.035);
  border: calc(var(--u) * 0.24) dashed rgba(220, 228, 240, 0.28);
  border-radius: calc(var(--u) * 0.9);
  cursor: pointer;
}
.auth-key span { color: #7d8694; letter-spacing: 0.2em; font-size: calc(var(--u) * 2); }
.auth-key code { font: inherit; letter-spacing: 0.1em; word-break: break-all; }
.auth-key:hover { border-color: rgba(240, 244, 250, 0.6); }
.auth-enrol + .auth-code .cell { height: calc(var(--u) * 11); font-size: calc(var(--u) * 9); width: calc(var(--u) * 8.4); }

/* --- recovery codes ------------------------------------------------------------ */

.auth-codes {
  list-style: none;
  counter-reset: code;
  margin: 0;
  padding: calc(var(--u) * 2) calc(var(--u) * 3);
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: calc(var(--u) * 1.3) calc(var(--u) * 5);
  font-size: calc(var(--u) * 3.6);
  border: calc(var(--u) * 0.24) dashed rgba(220, 228, 240, 0.22);
  border-radius: calc(var(--u) * 1);
  background: rgba(255, 255, 255, 0.025);
}
.auth-codes li { counter-increment: code; white-space: nowrap; }
.auth-codes li::before { content: counter(code, decimal-leading-zero) "  "; color: #5b6370; font-size: 0.7em; }
.auth-codes code { font: inherit; letter-spacing: 0.12em; color: #ffffff; }

/* --- phones: the card becomes a sheet of the same glass above the handset ------ */

body.auth-sheet .auth {
  position: fixed;
  inset: 0;
  z-index: 35;
  border-radius: 0 0 1.2rem 1.2rem;
  padding: max(1rem, env(safe-area-inset-top, 0px)) 0 1rem;
  /* Short screens (a phone on its side) scroll the card rather than cut it. */
  overflow-y: auto;
  overscroll-behavior: contain;
  place-items: start center;
}
body.auth-sheet .auth-glass { position: fixed; }
body.auth-sheet .auth-card { margin: auto 0; }
body.auth-sheet .auth-glass { background: radial-gradient(140% 90% at 50% 35%, #0b0e15 0%, #030407 70%, #000 100%); }
body.auth-sheet .auth-card { width: min(92vw, 34rem); max-width: 92vw; }
body.auth-sheet.cinema .auth { inset: 0; }
@media (orientation: portrait) and (max-width: 820px) {
  /* Stop above the upright handset, so its power key stays in reach. The same
     sum #set reserves for it, from the same variables. */
  body.auth-sheet.has-handset .auth {
    bottom: calc(0.6rem + var(--r-head) + 4 * var(--r-key) + 3 * var(--r-gap)
                 + max(var(--r-pad), env(safe-area-inset-bottom, 0px)));
  }
}
@media (orientation: portrait) {
  body.auth-sheet .auth-field { grid-template-columns: 1fr; gap: 0.4rem; }
  body.auth-sheet .auth-checks { margin-left: 0; flex-wrap: wrap; }
  body.auth-sheet .auth-enrol { grid-template-columns: 1fr; justify-items: center; text-align: left; }
  body.auth-sheet .auth-codes { grid-template-columns: 1fr 1fr; gap: 0.55rem 0.9rem; padding: 0.8rem; font-size: clamp(13px, 3.7vw, 19px); }
  body.auth-sheet .auth-steps { gap: 3.4vw; font-size: clamp(9px, 2.5vw, 12px); letter-spacing: 0.08em; }
  body.auth-sheet .auth-code .cells { gap: 1.8vw; }
  body.auth-sheet .auth-code .cell { width: 11.2vw; height: 15vw; font-size: 11.5vw; }
  body.auth-sheet .auth-code .cell:nth-child(3) { margin-right: 3vw; }
  body.auth-sheet .auth-title { font-size: clamp(22px, 7.4vw, 40px); }
  body.auth-sheet .auth-row.split { flex-wrap: wrap; }
}

/* --- the power key on the handset ------------------------------------------------ */

.k-power-strip { order: -2; }
#touch .k-power, #touch .k-power-strip {
  display: grid;
  place-items: center;
  padding: 0;
  color: #e3e6ea;
}
#touch .k-power svg, #touch .k-power-strip svg {
  width: 1.15rem;
  height: 1.15rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.5;
  stroke-linecap: round;
}
#touch .k-power-strip { min-width: 2.9rem; border-radius: 50%; }
.remote-head .k-power { display: none; }
body.has-handset .power-label { display: none; }



/* The power hint on each card is itself a switch, for screens with no power
   key in reach (a phone on its side, where the handset is hidden). */
button.auth-foot {
  background: none;
  border: 0;
  padding: calc(var(--u) * 0.8) calc(var(--u) * 1.4);
  font-family: "Courier New", Courier, monospace;
  font-weight: 700;
  cursor: pointer;
  text-shadow: inherit;
  transition: color 0.2s ease;
}
button.auth-foot:hover { color: #c9d0dc; }

.auth-codes, .auth-key code { -webkit-user-select: text; user-select: text; }

/* =============================================================================
   THE HANDSET, UPRIGHT
   A real remote's head: the power key at top left in its own well, the IR lens
   centred with the maker's mark etched under it, the activity lamp at top right
   in a matching bezel -- then a machined groove, and the keys below.
   ========================================================================== */
@media (orientation: portrait) and (max-width: 820px) {
  body.has-handset {
    --r-head: 4.1rem;
    --r-key: 3.3rem;
    --r-gap: 0.6rem;
  }

  body.has-handset #touch {
    border-radius: 1.9rem 1.9rem 0 0;
    padding-top: 0.75rem;
    background:
      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E"),
      radial-gradient(120% 60% at 50% 0%, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0) 60%),
      linear-gradient(180deg, #25272b 0%, #1a1b1e 38%, #111214 100%);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.16),
      inset 0 0 0 1px rgba(0, 0, 0, 0.9),
      inset 1px 0 0 rgba(255, 255, 255, 0.04),
      inset -1px 0 0 rgba(255, 255, 255, 0.04),
      0 -0.6rem 2.2rem rgba(0, 0, 0, 0.7);
  }

  .remote-head {
    position: relative;
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    height: var(--r-head);
    margin: 0 0.1rem 0.7rem;
  }
  /* The groove between the head and the keys. */
  .remote-head::after {
    content: "";
    position: absolute;
    left: 0.4rem;
    right: 0.4rem;
    bottom: -0.45rem;
    height: 2px;
    border-radius: 1px;
    background: linear-gradient(90deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.85) 12%, rgba(0, 0, 0, 0.85) 88%, rgba(0, 0, 0, 0));
    box-shadow: 0 1px 0 rgba(255, 255, 255, 0.055);
  }

  /* Power key: a domed key sunk into a round well, its halo showing the set's
     lamp -- red in standby, amber while it thinks, green when the set is on. */
  #touch .remote-head .k-power {
    grid-column: 1;
    grid-row: 1;
    justify-self: start;
    position: relative;
    width: 3.15rem;
    height: 3.15rem;
    min-height: 0;
    margin-left: 0.2rem;
    border-radius: 50%;
    display: grid;
    place-items: center;
    color: #eef0f3;
    background:
      radial-gradient(circle at 50% 28%, rgba(255, 255, 255, 0.16) 0%, rgba(255, 255, 255, 0) 52%),
      linear-gradient(180deg, #3b3d42 0%, #2a2c30 50%, #202124 100%);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.2),
      inset 0 -2px 3px rgba(0, 0, 0, 0.5),
      0 0 0 0.3rem #0b0c0e,
      0 0 0 calc(0.3rem + 1px) rgba(255, 255, 255, 0.05),
      0 3px 6px rgba(0, 0, 0, 0.55);
    transition: transform 0.08s ease, box-shadow 0.3s ease;
  }
  #touch .remote-head .k-power::before {
    content: "";
    position: absolute;
    inset: -0.3rem;
    border-radius: 50%;
    box-shadow: 0 0 0 1.5px rgba(255, 88, 64, 0.55), 0 0 0.7rem rgba(255, 80, 50, 0.28);
    opacity: 0.8;
    transition: box-shadow 0.4s ease, opacity 0.4s ease;
    pointer-events: none;
  }
  body[data-lamp="on"] #touch .remote-head .k-power::before {
    box-shadow: 0 0 0 1.5px rgba(110, 225, 140, 0.6), 0 0 0.8rem rgba(90, 210, 120, 0.3);
  }
  body[data-lamp="warming"] #touch .remote-head .k-power::before,
  body.auth-busy #touch .remote-head .k-power::before {
    box-shadow: 0 0 0 1.5px rgba(255, 176, 60, 0.7), 0 0 0.8rem rgba(255, 160, 40, 0.35);
    animation: lamp-blink 0.3s steps(1) infinite;
  }
  #touch .remote-head .k-power svg { width: 1.3rem; height: 1.3rem; stroke-width: 2.6; }
  #touch .remote-head .k-power:active {
    transform: translateY(1px);
    background: linear-gradient(180deg, #2a2c30 0%, #222326 100%);
    box-shadow:
      inset 0 2px 4px rgba(0, 0, 0, 0.7),
      0 0 0 0.3rem #0b0c0e,
      0 0 0 calc(0.3rem + 1px) rgba(255, 255, 255, 0.05);
  }

  /* IR lens: smoked red glass with a highlight, the maker's mark etched below. */
  .remote-head .ir {
    grid-column: 2;
    grid-row: 1;
    align-self: start;
    justify-self: center;
    margin-top: 0.55rem;
    width: 4.6rem;
    height: 0.95rem;
    border-radius: 0.5rem;
    background:
      linear-gradient(180deg, rgba(255, 255, 255, 0.16) 0%, rgba(255, 255, 255, 0) 45%),
      radial-gradient(80% 120% at 50% 100%, #4a1010 0%, #230707 60%, #150404 100%);
    box-shadow:
      inset 0 1px 2px rgba(0, 0, 0, 0.9),
      inset 0 0 0 1px rgba(255, 110, 110, 0.08),
      0 0 0 2px #0a0b0c,
      0 1px 0 2px rgba(255, 255, 255, 0.05);
  }
  .remote-brand {
    grid-column: 2;
    grid-row: 1;
    align-self: end;
    margin-bottom: 0.55rem;
    font: 600 0.56rem/1 "Helvetica Neue", Helvetica, Arial, sans-serif;
    letter-spacing: 0.42em;
    /* Letter-spacing trails after the last glyph; indent the same amount so
       the word is optically centred under the lens. */
    text-indent: 0.42em;
    justify-self: center;
    text-align: center;
    color: transparent;
    background: linear-gradient(180deg, #c9ccd2 0%, #8e9299 100%);
    -webkit-background-clip: text;
    background-clip: text;
    text-shadow: 0 1px 0 rgba(0, 0, 0, 0.5);
    opacity: 0.75;
    pointer-events: none;
  }

  /* Activity lamp in a bezel that answers the power key's well. */
  .remote-head .remote-led {
    grid-column: 3;
    grid-row: 1;
    justify-self: end;
    position: relative;
    right: auto;
    top: auto;
    transform: none;
    margin-right: 0.85rem;
    width: 0.62rem;
    height: 0.62rem;
    box-shadow:
      inset 0 1px 1px rgba(0, 0, 0, 0.9),
      0 0 0 0.28rem #0b0c0e,
      0 0 0 calc(0.28rem + 1px) rgba(255, 255, 255, 0.05);
  }
  .remote-head .remote-led.lit {
    box-shadow:
      0 0 0.5rem rgba(255, 80, 50, 0.95),
      0 0 1.2rem rgba(255, 60, 30, 0.5),
      0 0 0 0.28rem #0b0c0e,
      0 0 0 calc(0.28rem + 1px) rgba(255, 255, 255, 0.05);
  }

  /* Keys: a touch more dome and a softer, deeper shadow. */
  body.has-handset #touch .remote-keys button {
    border-radius: 0.95rem;
    font-size: 0.72rem;
    letter-spacing: 0.16em;
    background:
      radial-gradient(120% 90% at 50% 0%, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0) 55%),
      linear-gradient(180deg, #34363b 0%, #292b2f 48%, #212226 100%);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.15),
      inset 0 -2px 2px rgba(0, 0, 0, 0.45),
      0 1px 0 rgba(0, 0, 0, 0.8),
      0 3px 7px rgba(0, 0, 0, 0.45);
  }
  body.has-handset #touch .remote-keys button:active {
    background: linear-gradient(180deg, #25272a 0%, #1f2023 100%);
    box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.75);
  }
  body.has-handset .rocker { border-radius: 1.4rem; padding: 3px; }
  body.has-handset #touch .rocker button { border-radius: 1.15rem; font-size: 1.5rem; font-weight: 400; }
  body.has-handset .k-full { letter-spacing: 0.22em !important; }

  .k-power-strip { display: none !important; }
}

#immersive-hint { white-space: nowrap; }

/* No double-tap-to-zoom delay on the picture: double tap is full screen. */
#screen { touch-action: manipulation; }

/* =============================================================================
   THE CABINET
   Built from the same materials as the handset: grained black plastic with a
   soft sheen rolling off the top, machined edges catching one line of light,
   parts that meet at real seams. Proportions are untouched -- the glass, the
   bezel and the fascia keep their sizes -- so everything sized from them
   (the picture, full screen, the ops console's frame) is unaffected.
   ========================================================================== */

#cabinet {
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.055'/%3E%3C/svg%3E"),
    /* key light rolling off the top edge */
    radial-gradient(110% 38% at 50% 0%, rgba(255, 255, 255, 0.085) 0%, rgba(255, 255, 255, 0) 70%),
    /* the faint cool spill of the picture onto the plastic around it */
    radial-gradient(70% 55% at 50% 44%, rgba(150, 175, 215, 0.035) 0%, rgba(150, 175, 215, 0) 70%),
    /* the base falling into shadow */
    linear-gradient(180deg, rgba(0, 0, 0, 0) 55%, rgba(0, 0, 0, 0.32) 100%),
    linear-gradient(180deg, #27292d 0%, #1c1d20 36%, #141517 72%, #0f1012 100%);
  box-shadow:
    /* machined outer edge: one lit line along the top, faint on the sides */
    inset 0 1px 0 rgba(255, 255, 255, 0.17),
    inset 1px 0 0 rgba(255, 255, 255, 0.045),
    inset -1px 0 0 rgba(255, 255, 255, 0.045),
    inset 0 -1px 0 rgba(0, 0, 0, 0.9),
    /* the rounded shoulder turning away */
    inset 0 0.6cqw 1.2cqw -0.6cqw rgba(255, 255, 255, 0.06),
    inset 0 -0.9cqw 1.4cqw -0.5cqw rgba(0, 0, 0, 0.6),
    /* parting line of the mould */
    inset 0 0 0 1px rgba(0, 0, 0, 0.92),
    /* sitting on something */
    0 3.2cqw 6cqw rgba(0, 0, 0, 0.78),
    0 0.9cqw 1.6cqw rgba(0, 0, 0, 0.55),
    0 0 12cqw rgba(130, 155, 200, 0.05);
}

/* The front bezel is a separate pressing set into the housing: a fine seam
   runs round it just inside the outer edge, dark with a lit lower lip. */
#cabinet::after {
  content: "";
  position: absolute;
  inset: 0.62cqw 0.62cqw auto;
  /* Just the front bezel: from the seam to the bottom of the glass. */
  height: calc(71.25cqw + 2.5cqw - 0.62cqw + 1.9cqw);
  border-radius: 1.5cqw 1.5cqw 0.6cqw 0.6cqw;
  pointer-events: none;
  box-shadow:
    /* the seam */
    0 0 0 1px rgba(0, 0, 0, 0.72),
    0 1px 0 1px rgba(255, 255, 255, 0.045),
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    /* the bezel face sloping down toward the glass: lit along the top,
       sides catching a little, the lower face turned away into shadow */
    inset 0 1.9cqw 1.6cqw -1.25cqw rgba(255, 255, 255, 0.05),
    inset 1.9cqw 0 1.6cqw -1.45cqw rgba(255, 255, 255, 0.018),
    inset -1.9cqw 0 1.6cqw -1.45cqw rgba(255, 255, 255, 0.018),
    inset 0 -1.9cqw 1.6cqw -1.2cqw rgba(0, 0, 0, 0.42);
  z-index: 3;
}

/* The glass sits in a stepped, machined lip: a dark rebate right at the glass,
   a bright catch-light where the light from above hits its lower edge, and the
   chamfer and recess shadows the picture was already set into. */
#cabinet::before {
  box-shadow:
    /* rebate: the dark step down to the glass */
    0 0 0 1px rgba(0, 0, 0, 0.96),
    0 0 0 0.42cqw #0a0b0d,
    /* the lip catching light along its lower edge, shadowed along its upper */
    0 0.1cqw 0 0.42cqw rgba(255, 255, 255, 0.07),
    0 -0.1cqw 0 0.42cqw rgba(0, 0, 0, 0.7),
    0 0 0 calc(0.42cqw + 1px) rgba(0, 0, 0, 0.85),
    /* the bezel falling back into the recess */
    0 0 2.2cqw 0.9cqw rgba(0, 0, 0, 0.5),
    /* depth where the glass meets the rebate, top and bottom */
    inset 0 0.25vmin 0.6vmin rgba(0, 0, 0, 0.78),
    inset 0 -0.25vmin 0.6vmin rgba(0, 0, 0, 0.62);
}

/* --- the fascia: a control strip below a machined groove -------------------- */

#fascia { position: relative; }
#fascia::before {
  content: "";
  position: absolute;
  left: 2.6cqw;
  right: 2.6cqw;
  top: 0.05cqw;
  height: 2px;
  border-radius: 1px;
  background: linear-gradient(90deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.8) 8%, rgba(0, 0, 0, 0.8) 92%, rgba(0, 0, 0, 0));
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05);
  pointer-events: none;
}

/* Speaker grilles: a recessed well with a lit lip, punched with a fine
   staggered field of holes. */
.speaker {
  height: 2.5cqw;
  border-radius: 0.55cqw;
  background:
    radial-gradient(circle, rgba(0, 0, 0, 0.95) 0 32%, rgba(0, 0, 0, 0) 44%) 0 0 / 0.56cqw 0.5cqw,
    radial-gradient(circle, rgba(0, 0, 0, 0.95) 0 32%, rgba(0, 0, 0, 0) 44%) 0.28cqw 0.25cqw / 0.56cqw 0.5cqw,
    linear-gradient(180deg, #16171a 0%, #1d1e21 55%, #232428 100%);
  box-shadow:
    inset 0 1px 0.35cqw rgba(0, 0, 0, 0.95),
    inset 0 -1px 0 rgba(255, 255, 255, 0.04),
    0 1px 0 rgba(255, 255, 255, 0.06),
    0 -1px 0 rgba(0, 0, 0, 0.5);
}

/* The power switch, finished like the handset's: a domed key in a round well,
   ringed by a halo that shows the set's lamp. */
.power-group {
  padding: 0.34cqw 0.8cqw 0.34cqw 0.34cqw;
  border-radius: 2.2cqw;
  background: linear-gradient(180deg, #0b0c0e 0%, #131417 100%);
  box-shadow:
    inset 0 1px 0.35cqw rgba(0, 0, 0, 0.95),
    inset 0 -1px 0 rgba(255, 255, 255, 0.04),
    0 1px 0 rgba(255, 255, 255, 0.06);
}
#btn-power {
  background:
    radial-gradient(circle at 50% 26%, rgba(255, 255, 255, 0.18) 0%, rgba(255, 255, 255, 0) 55%),
    linear-gradient(180deg, #3a3c41 0%, #292b2f 52%, #1e1f22 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.2),
    inset 0 -2px 3px rgba(0, 0, 0, 0.5),
    0 0 0 0.16cqw #08090a,
    0 0 0 calc(0.16cqw + 1px) rgba(255, 88, 64, 0.45),
    0 0 0.6cqw rgba(255, 80, 50, 0.22),
    0 0.12cqw 0.3cqw rgba(0, 0, 0, 0.6);
  transition: transform 0.08s ease, box-shadow 0.4s ease;
}
body[data-lamp="on"] #btn-power {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.2),
    inset 0 -2px 3px rgba(0, 0, 0, 0.5),
    0 0 0 0.16cqw #08090a,
    0 0 0 calc(0.16cqw + 1px) rgba(110, 225, 140, 0.5),
    0 0 0.6cqw rgba(90, 210, 120, 0.22),
    0 0.12cqw 0.3cqw rgba(0, 0, 0, 0.6);
}
body[data-lamp="warming"] #btn-power,
body.auth-busy #btn-power {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.2),
    inset 0 -2px 3px rgba(0, 0, 0, 0.5),
    0 0 0 0.16cqw #08090a,
    0 0 0 calc(0.16cqw + 1px) rgba(255, 176, 60, 0.65),
    0 0 0.6cqw rgba(255, 160, 40, 0.3),
    0 0.12cqw 0.3cqw rgba(0, 0, 0, 0.6);
}
#btn-power:active {
  transform: translateY(1px);
  background: linear-gradient(180deg, #27292c 0%, #1f2023 100%);
}
.power-group .led {
  box-shadow:
    0 0 0.75cqw rgba(255, 78, 40, 0.72),
    0 0 0 0.22cqw #08090a,
    0 0 0 calc(0.22cqw + 1px) rgba(255, 255, 255, 0.05);
}
.power-group .led[data-mode="on"] {
  box-shadow:
    0 0 0.75cqw rgba(110, 225, 140, 0.7),
    0 0 0 0.22cqw #08090a,
    0 0 0 calc(0.22cqw + 1px) rgba(255, 255, 255, 0.05);
}
.power-group .led[data-mode="warming"] {
  box-shadow:
    0 0 0.75cqw rgba(255, 170, 60, 0.75),
    0 0 0 0.22cqw #08090a,
    0 0 0 calc(0.22cqw + 1px) rgba(255, 255, 255, 0.05);
}

/* The keys, in the handset's domed rubber, in deeper wells. */
.pad {
  padding: 0.3cqw;
  border-radius: 0.7cqw;
  background: linear-gradient(180deg, #0b0c0e 0%, #131417 100%);
  box-shadow:
    inset 0 1px 0.35cqw rgba(0, 0, 0, 0.95),
    inset 0 -1px 0 rgba(255, 255, 255, 0.04),
    0 1px 0 rgba(255, 255, 255, 0.06);
}
.pad button {
  border-radius: 0.5cqw;
  color: #c9ccd2;
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0) 55%),
    linear-gradient(180deg, #35373c 0%, #292b2f 48%, #202124 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.15),
    inset 0 -2px 2px rgba(0, 0, 0, 0.45),
    0 1px 0 rgba(0, 0, 0, 0.8),
    0 0.15cqw 0.35cqw rgba(0, 0, 0, 0.45);
}
.pad button:hover {
  color: #ffffff;
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0) 55%),
    linear-gradient(180deg, #3c3e43 0%, #2e3034 48%, #232427 100%);
}
.pad button:active {
  background: linear-gradient(180deg, #25272a 0%, #1f2023 100%);
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.75);
}

/* The bare picture has no cabinet, and so none of its furniture either. */
body.no-bezel #fascia::before,
body.no-bezel #fascia::after,
body.cinema #fascia::before,
body.cinema #fascia::after { display: none; }
body.no-bezel:not(.cinema) #btn-power { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), inset 0 -2px 3px rgba(0, 0, 0, 0.5), 0 0.12cqw 0.3cqw rgba(0, 0, 0, 0.6); }

/* =============================================================================
   TRINITRON
   The set and its handset as a matched pair: the same grained black plastic,
   the same domed power key in a haloed well, the same smoked red lens, the
   same etched silver lettering.
   ========================================================================== */

/* The lower front: one long horizontal slot grille, with the name etched into
   the middle of it -- the classic Trinitron speaker bar. */
#fascia { gap: 0; padding: 0 2.5cqw; }
.fascia-side { gap: 1.1cqw; }
.speaker {
  height: 1.9cqw;
  border-radius: 0.5cqw;
  background:
    repeating-linear-gradient(180deg,
      rgba(0, 0, 0, 0.92) 0 0.2cqw,
      rgba(0, 0, 0, 0) 0.2cqw 0.38cqw),
    linear-gradient(180deg, #17181b 0%, #1e1f22 55%, #242529 100%);
  box-shadow:
    inset 0 1px 0.3cqw rgba(0, 0, 0, 0.95),
    inset 0 -1px 0 rgba(255, 255, 255, 0.045),
    0 1px 0 rgba(255, 255, 255, 0.06);
}
.badge {
  padding: 0 1.6cqw;
  background: none;
  box-shadow: none;
}
.badge .brand {
  font: 600 1.02cqw/1 "Helvetica Neue", Helvetica, Arial, sans-serif;
  letter-spacing: 0.46em;
  margin-right: 0;
  text-indent: 0.46em;
  color: transparent;
  background: linear-gradient(180deg, #d9dce1 0%, #8f939a 100%);
  -webkit-background-clip: text;
  background-clip: text;
  filter: drop-shadow(0 1px 0 rgba(0, 0, 0, 0.9));
  opacity: 0.8;
}

/* The control keys live in a single recessed panel on the right. */
.controls {
  padding: 0.3cqw;
  border-radius: 0.8cqw;
  background: linear-gradient(180deg, #0b0c0e 0%, #131417 100%);
  box-shadow:
    inset 0 1px 0.35cqw rgba(0, 0, 0, 0.95),
    inset 0 -1px 0 rgba(255, 255, 255, 0.04),
    0 1px 0 rgba(255, 255, 255, 0.06);
  gap: 0.5cqw;
}
.controls .pad { background: none; box-shadow: none; padding: 0; }

/* Phones: the fascia keeps power, lens, grille and name; the keys are on the handset. */
body.has-handset .badge .brand { font-size: 1.15cqw; }

body.no-bezel:not(.cinema) .controls { background: none; box-shadow: none; }

/* =============================================================================
   OLD-SCHOOL CONTROLS
   Hard plastic push-buttons with flat tops and chamfered edges, sunk in a strip
   along the lower front, with the legends printed in white on the panel under
   each group -- how a set from the nineties labelled its controls. The logo is
   just LNF, set into the middle of the speaker bar.
   ========================================================================== */

.badge { padding: 0 1.8cqw; }
.badge .brand {
  display: block;
  height: 1.95cqw;
  width: auto;
  aspect-ratio: 2140 / 700;
  overflow: visible;
  /* a satin metal badge set into the plastic: a dark seat below, a hairline of light above */
  opacity: 0.5;
  transform: translateY(0.45cqw);
  filter: drop-shadow(0 1px 0 rgba(0, 0, 0, 0.9));
}

.power-group, .controls .pad {
  display: grid;
  justify-items: center;
  align-content: center;
  row-gap: 0.42cqw;
  padding: 0;
  background: none;
  box-shadow: none;
  border-radius: 0;
}
.controls {
  display: flex;
  align-items: center;
  gap: 1.5cqw;
  padding: 0;
  background: none;
  box-shadow: none;
}
.power-group .keys, .controls .keys {
  display: flex;
  align-items: center;
  gap: 0.28cqw;
  padding: 0.22cqw;
  border-radius: 0.34cqw;
  /* the slot the buttons are sunk into */
  background: #09090b;
  box-shadow:
    inset 0 1px 0.25cqw rgba(0, 0, 0, 0.95),
    0 1px 0 rgba(255, 255, 255, 0.07);
}
.power-group .keys { gap: 0.7cqw; padding-right: 0.7cqw; }

/* The printed legend. */
.legend {
  font: 700 0.62cqw/1 "Helvetica Neue", Helvetica, Arial, sans-serif;
  letter-spacing: 0.22em;
  text-indent: 0.22em;
  color: rgba(232, 234, 238, 0.72);
  white-space: nowrap;
}
.legend.split { display: flex; width: 100%; justify-content: space-around; }
.legend.split span { text-indent: 0.22em; }

/* The buttons: flat-topped hard plastic with a chamfer catching the light. */
.pad button, #btn-power {
  position: relative;
  width: 3.1cqw;
  height: 1.55cqw;
  padding: 0;
  border: 0;
  border-radius: 0.18cqw;
  cursor: pointer;
  display: grid;
  place-items: center;
  font: 700 0.78cqw/1 "Helvetica Neue", Helvetica, Arial, sans-serif;
  color: #d6d8dc;
  background:
    /* flat top */
    linear-gradient(180deg, #34363a 0%, #2c2e32 100%) center / calc(100% - 0.3cqw) calc(100% - 0.34cqw) no-repeat,
    /* chamfer: lit above, shadowed below */
    linear-gradient(180deg, #55585e 0%, #3a3c40 30%, #1f2023 70%, #141517 100%);
  box-shadow:
    0 0 0 1px #050506,
    0 0.14cqw 0 #050506,
    0 0.2cqw 0.3cqw rgba(0, 0, 0, 0.6);
  transition: transform 0.06s ease, box-shadow 0.06s ease, filter 0.15s ease;
}
.pad button span { display: block; transform: none; line-height: 1; }
.pad button:hover, #btn-power:hover { filter: brightness(1.14); }
.pad button:active, #btn-power:active {
  transform: translateY(0.12cqw);
  box-shadow:
    0 0 0 1px #050506,
    0 0.02cqw 0 #050506,
    0 0.06cqw 0.12cqw rgba(0, 0, 0, 0.6);
}
.pad button:focus-visible, #btn-power:focus-visible { outline: 1px solid rgba(235, 238, 242, 0.85); outline-offset: 2px; }

/* Power is the long one, with the symbol moulded into it. */
#btn-power { width: 4.6cqw; border-radius: 0.18cqw; }
#btn-power svg { width: 0.95cqw; height: 0.95cqw; stroke-width: 2.8; filter: none; }
body[data-lamp] #btn-power { box-shadow: 0 0 0 1px #050506, 0 0.14cqw 0 #050506, 0 0.2cqw 0.3cqw rgba(0, 0, 0, 0.6); }

/* The standby lamp: one small round LED beside the switch. */
.power-group .led {
  width: 0.62cqw;
  height: 0.62cqw;
  margin: 0;
  box-shadow:
    0 0 0.6cqw rgba(255, 78, 40, 0.75),
    0 0 0 0.12cqw #050506;
}
.power-group .led[data-mode="on"] { box-shadow: 0 0 0.6cqw rgba(110, 225, 140, 0.7), 0 0 0 0.12cqw #050506; }
.power-group .led[data-mode="warming"] { box-shadow: 0 0 0.6cqw rgba(255, 170, 60, 0.75), 0 0 0 0.12cqw #050506; }

/* The speaker bar runs both sides of the logo. */
.speaker { height: 1.7cqw; }

/* Phones: power, lamp, grille and logo stay on the set; the rest is on the handset. */
body.has-handset .power-group .legend { display: none; }
body.no-bezel:not(.cinema) .power-group .keys,
body.no-bezel:not(.cinema) .controls .keys { background: none; box-shadow: none; }

/* On a phone the set is small, so the logo takes more of the bar. */
body.has-handset .badge .brand { height: 2.6cqw; }

/* =============================================================================
   MASTER CONTROL, REMEMBER THIS TV, HOLD TO SIGN OUT
   The owner's room (web/control.js) is the same smoked glass and phosphor
   lettering as the sign-in card, laid out as a console. Colour is spent only
   on status: the green lamp of someone watching right now and the red of an
   account switched off. Everything you can press is platinum on black.
   ========================================================================== */

/* --- the CONTROL key: admins only ------------------------------------------ */

/* !important on the hide: the general key rules are more specific, and a
   viewer must never be shown a key that only leads to a refusal. */
body:not(.is-admin) .pad-control, body:not(.is-admin) #touch .k-control { display: none !important; }
body.is-admin .controls .pad.pad-control { display: grid; }
body.is-admin #touch .k-control { display: grid; place-items: center; }
#btn-control::after {
  /* three little faders moulded into the key top */
  content: "";
  width: 1.3cqw;
  height: 0.62cqw;
  background:
    linear-gradient(90deg, currentColor 0 0.12cqw, transparent 0.12cqw) 0 50% / 0.5cqw 100% repeat-x;
  opacity: 0.55;
}
@media (orientation: portrait) and (max-width: 820px) {
  body.is-admin .remote-keys {
    grid-template-areas:
      "mute  guide info"
      "vol   tube  ch"
      "vol   bezel ch"
      "full  full  ctrl";
  }
  #touch .k-control { grid-area: ctrl; }
  body.is-admin.has-handset #touch .k-control { letter-spacing: 0.14em; }
}
@media (orientation: landscape) and (max-height: 560px) {
  body.is-admin #touch .k-control { order: 2; padding: 0 0.7rem; }
}

/* --- switches: remember this TV, and every on/off in the room -------------------- */

.auth-switch, .ctl-switch {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--u) * 1.6);
  padding: calc(var(--u) * 0.8) 0;
  background: none;
  border: 0;
  cursor: pointer;
  font: 700 calc(var(--u) * 2.7)/1 "Courier New", Courier, monospace;
  letter-spacing: 0.16em;
  color: #9aa3b1;
  text-shadow: inherit;
  white-space: nowrap;
}
.auth-switch .sw, .ctl-switch .sw {
  position: relative;
  flex: 0 0 auto;
  width: calc(var(--u) * 7);
  height: calc(var(--u) * 3.6);
  border-radius: calc(var(--u) * 1.8);
  background: #07080b;
  box-shadow: inset 0 1px calc(var(--u) * 0.5) rgba(0, 0, 0, 0.95), 0 0 0 calc(var(--u) * 0.24) rgba(220, 228, 240, 0.22);
  transition: box-shadow 0.2s ease, background 0.2s ease;
}
.auth-switch .sw::after, .ctl-switch .sw::after {
  content: "";
  position: absolute;
  top: calc(var(--u) * 0.55);
  left: calc(var(--u) * 0.6);
  width: calc(var(--u) * 2.5);
  height: calc(var(--u) * 2.5);
  border-radius: 50%;
  background: linear-gradient(180deg, #6d7480 0%, #454b55 100%);
  transition: transform 0.22s cubic-bezier(0.3, 1.4, 0.5, 1), background 0.2s ease, box-shadow 0.2s ease;
}
.auth-switch[aria-checked="true"], .ctl-switch[aria-checked="true"] { color: #ffffff; }
.auth-switch[aria-checked="true"] .sw, .ctl-switch[aria-checked="true"] .sw {
  box-shadow: inset 0 1px calc(var(--u) * 0.5) rgba(0, 0, 0, 0.9), 0 0 0 calc(var(--u) * 0.24) rgba(240, 244, 250, 0.7),
              0 0 calc(var(--u) * 1.8) rgba(215, 228, 255, 0.35);
}
.auth-switch[aria-checked="true"] .sw::after, .ctl-switch[aria-checked="true"] .sw::after {
  transform: translateX(calc(var(--u) * 3.3));
  background: linear-gradient(180deg, #ffffff 0%, #d4d9e1 100%);
  box-shadow: 0 0 calc(var(--u) * 1.2) rgba(225, 235, 255, 0.8);
}
.auth-switch:focus-visible, .ctl-switch:focus-visible { outline: calc(var(--u) * 0.3) solid rgba(255, 255, 255, 0.9); outline-offset: calc(var(--u) * 0.5); }

.auth-remember { display: grid; justify-items: center; gap: calc(var(--u) * 0.6); }
.auth-remember-hint { font-size: calc(var(--u) * 2); letter-spacing: 0.12em; color: #707988; }
.auth-form .auth-remember { justify-items: start; }

.auth.welcome-back .auth-stamp { letter-spacing: 0.5em; }

/* --- hold to sign out -------------------------------------------------------------- */

#hold-osd {
  --u: min(1cqh, 1.3333cqw);
  position: absolute;
  left: 50%;
  bottom: 9%;
  z-index: 7;
  transform: translateX(-50%);
  display: grid;
  gap: 0.7rem;
  justify-items: center;
  padding: 0.9rem 1.6rem 1rem;
  border-radius: 0.6rem;
  background: rgba(3, 4, 7, 0.86);
  box-shadow: 0 0 0 1px rgba(220, 228, 240, 0.18), 0 1rem 2.4rem rgba(0, 0, 0, 0.6);
  font: 700 clamp(11px, 1.6cqw, 20px)/1 "Courier New", Courier, monospace;
  letter-spacing: 0.2em;
  color: #eef2f8;
  text-shadow: 0 0 0.6rem rgba(205, 222, 255, 0.45);
  white-space: nowrap;
  pointer-events: none;
}
#hold-osd[hidden] { display: none; }
#hold-osd .bar { width: 100%; min-width: 12rem; height: 3px; background: rgba(220, 228, 240, 0.16); overflow: hidden; }
#hold-osd .bar i { display: block; height: 100%; width: 100%; background: #ffffff; box-shadow: 0 0 0.5rem rgba(225, 235, 255, 0.9); transform-origin: 0 50%; transform: scaleX(0); }
#hold-osd.run .bar i { animation: hold-fill 1.58s linear forwards; }
#hold-osd.done .bar { display: none; }
@keyframes hold-fill { to { transform: scaleX(1); } }
#btn-power, #touch [data-act=power] { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }

/* --- the room ------------------------------------------------------------------------- */

.control { z-index: 5; overflow: clip; }
.ctl {
  --u: min(1cqh, 1.3333cqw);
  position: relative;
  width: 94cqw;
  height: 92cqh;
  display: flex;
  flex-direction: column;
  gap: calc(var(--u) * 1.6);
  font-weight: 700;
  letter-spacing: 0.06em;
  color: #dfe4ec;
  text-shadow:
    0 0 calc(var(--u) * 1.1) rgba(205, 222, 255, 0.32),
    calc(var(--u) * 0.26) calc(var(--u) * 0.26) 0 rgba(0, 0, 0, 0.92);
}
.control.is-in .ctl { animation: card-in 0.52s cubic-bezier(0.2, 0.9, 0.2, 1) both; }

.ctl-head { display: grid; gap: calc(var(--u) * 1.2); }
.ctl-ident { display: flex; align-items: center; gap: calc(var(--u) * 2); font-size: calc(var(--u) * 2); letter-spacing: 0.55em; color: #aab2bf; }
.ctl-ident b { margin-right: -0.55em; }
.ctl-ident .rule { flex: 1 1 0; height: calc(var(--u) * 0.2); background: linear-gradient(90deg, rgba(220, 228, 240, 0), rgba(220, 228, 240, 0.45) 50%, rgba(220, 228, 240, 0)); }
.ctl-bar { display: flex; align-items: center; gap: calc(var(--u) * 3); flex-wrap: wrap; }
.ctl-title {
  margin: 0;
  font-size: calc(var(--u) * 5.4);
  line-height: 1;
  letter-spacing: 0.08em;
  color: #ffffff;
  text-shadow:
    calc(var(--u) * -0.16) 0 0 rgba(255, 90, 90, 0.35),
    calc(var(--u) * 0.16) 0 0 rgba(90, 160, 255, 0.35),
    0 0 calc(var(--u) * 1.6) rgba(205, 222, 255, 0.5),
    calc(var(--u) * 0.34) calc(var(--u) * 0.34) 0 rgba(0, 0, 0, 0.92);
}
.ctl-tabs { display: flex; gap: calc(var(--u) * 1); margin-left: auto; }
.ctl-tabs button, .ctl-exit {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--u) * 1);
  padding: calc(var(--u) * 1.2) calc(var(--u) * 2);
  font: 700 calc(var(--u) * 2.5)/1 "Courier New", Courier, monospace;
  letter-spacing: 0.16em;
  color: #9aa3b1;
  background: rgba(255, 255, 255, 0.02);
  border: calc(var(--u) * 0.22) solid rgba(220, 228, 240, 0.16);
  border-radius: calc(var(--u) * 0.7);
  cursor: pointer;
  text-shadow: inherit;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.ctl-tabs button:hover, .ctl-exit:hover { color: #ffffff; border-color: rgba(240, 244, 250, 0.5); }
.ctl-tabs button.on { color: #0b0d11; text-shadow: none; background: linear-gradient(180deg, #fbfcfe 0%, #dfe3ea 55%, #c4c9d2 100%); border-color: transparent; box-shadow: 0 0 calc(var(--u) * 2) rgba(210, 225, 255, 0.3); }
.ctl-tabs .n { font-size: 0.8em; opacity: 0.7; }
.ctl-exit i { font-style: normal; }
.ctl-tabs button:focus-visible, .ctl-exit:focus-visible, .ctl-person:focus-visible, .ctl-btn:focus-visible {
  outline: calc(var(--u) * 0.3) solid rgba(255, 255, 255, 0.9); outline-offset: calc(var(--u) * 0.3);
}
.ctl-summary { margin: 0; font-size: calc(var(--u) * 2.1); letter-spacing: 0.2em; color: #7f8896; }

.ctl-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: calc(var(--u) * 1.6) calc(var(--u) * 1.2) calc(var(--u) * 2) calc(var(--u) * 0.4);
  scrollbar-width: thin;
  scrollbar-color: rgba(220, 228, 240, 0.25) transparent;
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 calc(var(--u) * 1.6), #000 calc(100% - calc(var(--u) * 3)), transparent 100%);
          mask-image: linear-gradient(180deg, transparent 0, #000 calc(var(--u) * 1.6), #000 calc(100% - calc(var(--u) * 3)), transparent 100%);
}
.ctl-body > * + * { margin-top: calc(var(--u) * 2.2); }

.ctl-msg { margin: 0; min-height: calc(var(--u) * 3); font-size: calc(var(--u) * 2.3); letter-spacing: 0.14em; color: #e6eaf1; }
.ctl-msg.error { color: #ff6b5e; text-shadow: 0 0 calc(var(--u) * 1.4) rgba(255, 80, 60, 0.6), calc(var(--u) * 0.3) calc(var(--u) * 0.3) 0 #000; animation: msg-flash 0.9s steps(1) 1; }

.ctl-row { display: flex; align-items: center; gap: calc(var(--u) * 2.2); flex-wrap: wrap; min-height: calc(var(--u) * 5); }
.ctl-row.end { justify-content: flex-end; }
.ctl-fine { margin: 0; font-size: calc(var(--u) * 2); letter-spacing: 0.12em; color: #7c8593; }
.ctl-quick { display: inline-flex; gap: calc(var(--u) * 2.4); margin-left: auto; }
.ctl .auth-go { font-size: calc(var(--u) * 2.6); padding: calc(var(--u) * 1.3) calc(var(--u) * 2.4); }
.ctl .auth-link { font-size: calc(var(--u) * 2.3); }
.ctl .auth-link.armed { color: #ffffff; animation: armed 0.7s steps(1) infinite; }
@keyframes armed { 50% { opacity: 0.55; } }

/* people */
.ctl-people, .ctl-devices, .ctl-pending { list-style: none; margin: 0; padding: 0; }
.ctl-person, .ctl-device {
  width: 100%;
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1.55fr) minmax(0, 1fr) minmax(0, 0.55fr) auto;
  align-items: center;
  gap: calc(var(--u) * 2);
  padding: calc(var(--u) * 1.7) calc(var(--u) * 1.4);
  text-align: left;
  font: inherit;
  color: inherit;
  text-shadow: inherit;
  background: none;
  border: 0;
  border-bottom: calc(var(--u) * 0.2) dashed rgba(220, 228, 240, 0.13);
  cursor: pointer;
  transition: background 0.15s ease;
}
.ctl-device, .ctl-pending .ctl-device { cursor: default; grid-template-columns: minmax(0, 1fr) auto; }
.ctl-dev-main { display: grid; gap: calc(var(--u) * 0.9); min-width: 0; }
.ctl-detail { display: flex; flex-wrap: wrap; align-items: center; gap: calc(var(--u) * 0.8) calc(var(--u) * 3); }
.ctl-detail .ctl-meta, .ctl-detail .ctl-status { white-space: normal; overflow: visible; }
.ctl-person:hover { background: rgba(255, 255, 255, 0.035); }
.ctl-person.is-off .ctl-who b { color: #8a929e; }
.ctl-who { display: flex; align-items: baseline; flex-wrap: wrap; gap: calc(var(--u) * 0.6) calc(var(--u) * 1.4); min-width: 0; }
.ctl-who b { font-size: calc(var(--u) * 3); letter-spacing: 0.06em; color: #ffffff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.ctl-who small { font-size: calc(var(--u) * 2); color: #7c8593; letter-spacing: 0.08em; }
.ctl-now { display: flex; align-items: center; gap: calc(var(--u) * 1.2); min-width: 0; }
.ctl-status { font-size: calc(var(--u) * 2.2); letter-spacing: 0.1em; color: #9aa3b1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctl-status.live { color: #e9fff0; }
.ctl-meta { font-size: calc(var(--u) * 2.1); letter-spacing: 0.1em; color: #aeb6c2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctl-meta.dim { color: #6f7885; }
.ctl-go { font-style: normal; color: #6f7885; font-size: calc(var(--u) * 2.6); }
.ctl-person:hover .ctl-go { color: #ffffff; }
.ctl-empty { padding: calc(var(--u) * 3) calc(var(--u) * 1.4); font-size: calc(var(--u) * 2.3); letter-spacing: 0.16em; color: #6f7885; }

.ctl-lamp {
  flex: 0 0 auto;
  width: calc(var(--u) * 1.3);
  height: calc(var(--u) * 1.3);
  border-radius: 50%;
  background: #2b2f36;
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(0, 0, 0, 0.9);
}
.ctl-lamp.live {
  background: radial-gradient(circle at 35% 30%, #e6ffe9 0%, #74e08e 40%, #1f8a3d 85%);
  box-shadow: 0 0 calc(var(--u) * 1.2) rgba(110, 225, 140, 0.75), 0 0 0 1px rgba(0, 0, 0, 0.9);
  animation: lamp-breathe 2.6s ease-in-out infinite;
}
.ctl-lamp.off { background: radial-gradient(circle at 35% 30%, #ffb3a8 0%, #d84a36 45%, #6b150b 100%); box-shadow: 0 0 calc(var(--u) * 0.8) rgba(255, 80, 60, 0.5), 0 0 0 1px rgba(0, 0, 0, 0.9); }

.ctl-tag {
  display: inline-block;
  padding: calc(var(--u) * 0.35) calc(var(--u) * 0.9);
  font-size: calc(var(--u) * 1.7);
  letter-spacing: 0.18em;
  color: #e6eaf1;
  border: calc(var(--u) * 0.2) solid rgba(230, 236, 245, 0.45);
  border-radius: calc(var(--u) * 0.5);
  white-space: nowrap;
}
.ctl-tag.dim { color: #8e97a4; border-color: rgba(220, 228, 240, 0.2); }
.ctl-tag.warn { color: #ffb0a6; border-color: rgba(255, 110, 90, 0.5); }

/* one person */
.ctl-back { display: inline-block; }
.ctl-person-head { display: flex; align-items: baseline; gap: calc(var(--u) * 2.4); flex-wrap: wrap; }
.ctl-body > .ctl-person-head { margin-top: calc(var(--u) * 0.6); }
.ctl-name {
  margin: 0;
  font-size: calc(var(--u) * 6.2);
  line-height: 1.02;
  letter-spacing: 0.05em;
  color: #ffffff;
  overflow-wrap: anywhere;
  text-shadow:
    calc(var(--u) * -0.16) 0 0 rgba(255, 90, 90, 0.3),
    calc(var(--u) * 0.16) 0 0 rgba(90, 160, 255, 0.3),
    0 0 calc(var(--u) * 1.6) rgba(205, 222, 255, 0.45),
    calc(var(--u) * 0.34) calc(var(--u) * 0.34) 0 rgba(0, 0, 0, 0.92);
}
.ctl-rename { align-items: end; }
.ctl-rename .auth-field { flex: 1 1 calc(var(--u) * 50); grid-template-columns: calc(var(--u) * 11) 1fr; }
.ctl-person-meta { display: flex; align-items: center; flex-wrap: wrap; gap: calc(var(--u) * 1.2) calc(var(--u) * 2.4); }
.ctl-body > .ctl-person-meta { margin-top: calc(var(--u) * 1.2); }
.ctl-tags { display: inline-flex; gap: calc(var(--u) * 1); }
.ctl-tags:empty { display: none; }

.ctl-panel {
  padding: calc(var(--u) * 1.8) calc(var(--u) * 2.2) calc(var(--u) * 2.2);
  border: calc(var(--u) * 0.2) solid rgba(220, 228, 240, 0.12);
  border-radius: calc(var(--u) * 1);
  background: rgba(255, 255, 255, 0.018);
  display: grid;
  gap: calc(var(--u) * 1.6);
}
.ctl-h {
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 1.6);
  margin: 0;
  font-size: calc(var(--u) * 2.1);
  letter-spacing: 0.34em;
  color: #aab2bf;
}
.ctl-h::after { content: ""; flex: 1 1 0; height: calc(var(--u) * 0.18); background: linear-gradient(90deg, rgba(220, 228, 240, 0.35), rgba(220, 228, 240, 0)); }

.ctl-channels { display: grid; gap: calc(var(--u) * 1.4); }
.ctl-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(calc(var(--u) * 21), 1fr)); gap: calc(var(--u) * 0.9); }
.ctl-tile {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  column-gap: calc(var(--u) * 1.2);
  padding: calc(var(--u) * 1) calc(var(--u) * 1.2);
  border: calc(var(--u) * 0.22) solid rgba(220, 228, 240, 0.14);
  border-radius: calc(var(--u) * 0.7);
  background: rgba(0, 0, 0, 0.25);
  cursor: pointer;
  color: #7c8593;
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.ctl-tile input { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }
.ctl-tile b { font-size: calc(var(--u) * 3.2); min-width: 1.6em; text-align: right; color: inherit; }
.ctl-tile span { font-size: calc(var(--u) * 1.8); letter-spacing: 0.08em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctl-tile small { grid-column: 2; font-size: calc(var(--u) * 1.5); letter-spacing: 0.14em; color: #6f7885; }
.ctl-tile:hover { border-color: rgba(240, 244, 250, 0.4); }
.ctl-tile.on { color: #ffffff; border-color: rgba(240, 244, 250, 0.75); background: rgba(225, 235, 255, 0.08); box-shadow: 0 0 calc(var(--u) * 1.4) rgba(215, 228, 255, 0.16); }
.ctl-tile:focus-within { outline: calc(var(--u) * 0.3) solid rgba(255, 255, 255, 0.9); outline-offset: calc(var(--u) * 0.2); }
.ctl-tiles.is-all .ctl-tile { cursor: default; }
.ctl-tiles.is-all .ctl-tile.on { border-color: rgba(240, 244, 250, 0.35); background: rgba(225, 235, 255, 0.04); box-shadow: none; color: #c9d0dc; }
.ctl-tile.is-admin-only { opacity: 0.45; cursor: not-allowed; }
.ctl-save { border-top: calc(var(--u) * 0.2) dashed rgba(220, 228, 240, 0.14); padding-top: calc(var(--u) * 1); }

.ctl-account { display: grid; gap: calc(var(--u) * 1.6); }
.ctl-actions { display: flex; flex-wrap: wrap; gap: calc(var(--u) * 1.2); }
.ctl-btn {
  padding: calc(var(--u) * 1.3) calc(var(--u) * 2);
  font: 700 calc(var(--u) * 2.2)/1 "Courier New", Courier, monospace;
  letter-spacing: 0.14em;
  color: #c9d0dc;
  text-shadow: inherit;
  background: rgba(255, 255, 255, 0.025);
  border: calc(var(--u) * 0.22) solid rgba(220, 228, 240, 0.22);
  border-radius: calc(var(--u) * 0.7);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.ctl-btn:hover { color: #ffffff; border-color: rgba(240, 244, 250, 0.6); }
.ctl-btn:disabled { opacity: 0.4; cursor: default; }
.ctl-btn.armed { color: #0b0d11; text-shadow: none; background: #f2f4f8; border-color: #f2f4f8; animation: armed 0.7s steps(1) infinite; }
.ctl-btn.danger.armed { color: #ffffff; background: #b3261a; border-color: #ff6b5e; box-shadow: 0 0 calc(var(--u) * 1.6) rgba(255, 80, 60, 0.5); }

.ctl-issued { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: calc(var(--u) * 3.4); align-items: center; }
.ctl-issued .auth-qr img { width: calc(var(--u) * 24); height: calc(var(--u) * 24); }
.ctl-issued-text { display: grid; gap: calc(var(--u) * 1.4); min-width: 0; }
.ctl-issued .auth-key { margin: 0; font-size: calc(var(--u) * 2.1); }
.ctl-issued .auth-stamp { font-size: calc(var(--u) * 2.3); }
.ctl-invite .auth-field { grid-template-columns: calc(var(--u) * 20) 1fr; font-size: calc(var(--u) * 2.4); }
.ctl-invite .auth-field input { font-size: max(16px, calc(var(--u) * 3.6)); }

/* --- phones: the room is a sheet of glass over the whole set ------------------------- */

body.control-sheet .control {
  position: fixed;
  inset: 0;
  z-index: 36;
  border-radius: 0;
  container-type: normal;
  padding: max(0.8rem, env(safe-area-inset-top, 0px)) 0.9rem 0.6rem;
  place-items: stretch;
  grid-template-rows: minmax(0, 1fr);
  overflow: clip;
}
body.control-sheet .control .auth-glass { position: fixed; background: radial-gradient(140% 90% at 50% 30%, #0b0e15 0%, #030407 70%, #000 100%); }
body.control-sheet .ctl { --u: 5.2px; width: 100%; height: auto; min-height: 0; }
@media (orientation: landscape) and (max-height: 560px) {
  /* keep the last rows clear of the remote strip floating along the bottom */
  body.control-sheet.has-handset .control { padding-bottom: calc(3.6rem + env(safe-area-inset-bottom, 0px)); }
}
@media (min-width: 600px) { body.control-sheet .ctl { --u: 6.2px; } }
@media (orientation: portrait) and (max-width: 820px) {
  body.control-sheet.has-handset .control {
    bottom: calc(0.6rem + var(--r-head) + 4 * var(--r-key) + 3 * var(--r-gap)
                 + max(var(--r-pad), env(safe-area-inset-bottom, 0px)));
  }
}
@media (max-width: 700px) {
  body.control-sheet .ctl-title { font-size: calc(var(--u) * 5); }
  body.control-sheet .ctl-tabs { margin-left: 0; }
  body.control-sheet .ctl-exit { margin-left: auto; }
  body.control-sheet .ctl-bar { row-gap: calc(var(--u) * 1.6); }
  body.control-sheet .ctl-bar .ctl-title { flex: 1 1 100%; }
  body.control-sheet .ctl-person,
  body.control-sheet .ctl-device,
  body.control-sheet .ctl-pending .ctl-device {
    grid-template-columns: minmax(0, 1fr) auto;
    row-gap: calc(var(--u) * 0.9);
  }
  body.control-sheet .ctl-person .ctl-who,
  body.control-sheet .ctl-device .ctl-who { min-width: 0; }
  body.control-sheet .ctl-person .ctl-now { grid-column: 1 / -1; }
  body.control-sheet .ctl-person .ctl-go { grid-row: 1; grid-column: 2; }
  body.control-sheet .ctl-person .ctl-who { grid-column: 1; }
  body.control-sheet .ctl-device .ctl-dev-main { grid-column: 1 / -1; }
  body.control-sheet .ctl-device > button { grid-column: 1 / -1; justify-self: start; }
  body.control-sheet .ctl-tiles { grid-template-columns: repeat(auto-fill, minmax(8.4rem, 1fr)); }
  body.control-sheet .ctl-issued { grid-template-columns: 1fr; justify-items: center; }
  body.control-sheet .ctl-invite .auth-field, body.control-sheet .ctl-rename .auth-field { grid-template-columns: 1fr; gap: 0.3rem; }
  body.control-sheet .ctl-panel { padding: 0.8rem; }
  body.control-sheet .ctl-row.end { justify-content: flex-start; }
}

/* Desktop: squarer top corners, like the moulding of a real set, and the
   front-bezel seam drawn concentric with them -- its radius is the corner's
   minus its inset, so the line runs parallel to the edge all the way round
   and can never swing out past it at the corners. */
body:not(.has-handset) #cabinet { --cab-r: 0.9cqw; border-top-left-radius: var(--cab-r); border-top-right-radius: var(--cab-r); }
body:not(.has-handset) #cabinet::after {
  /* Rounder than strictly concentric: a seam's curve can be larger than the
     edge's minus its inset and still sit inside it (it only curves further
     in), and at exactly concentric it read as a hard square corner. */
  border-top-left-radius: var(--cab-r);
  border-top-right-radius: var(--cab-r);
  /* ...and the same curve at the bottom of the bezel, not a point. */
  border-bottom-left-radius: var(--cab-r);
  border-bottom-right-radius: var(--cab-r);
}
