/* ---------------------------------------------------------------
   leetv.tv

   The set is a transparent PNG photograph. The animated static is a
   canvas positioned behind it, revealed only through the screen
   cut-out — the photograph itself does the masking.

   Screen rectangle measured from the PNG's alpha channel:
     fully transparent   x 946  y 570  723 x 540
     any transparency    x 886  y 510  842 x 658   (~60px feathered edge)
   The canvas covers the FULL transparency extent plus a 6px margin,
   so nothing dark shows through the feather. Both boxes share the
   same centre, so the ident still lands centred in the opening.

   Layout unit: --u == 1% of the stage width, as before. The stage
   carries the photograph's own 2944:1648 aspect, and is sized with
   max() so it always COVERS the viewport - it fills edge to edge at
   any window shape, overflowing on whichever axis is longer. The
   anchor point below decides what stays in frame when it crops.
   --------------------------------------------------------------- */

:root {
  --aw: 100;
  --ah: 55.9783;                 /* 1648 / 2944 * 100 */
  /* max() => cover. 1.7865 rather than the exact 1.78641 so rounding
     can never leave a sub-pixel gap along an edge. */
  --u: max(1vw, 1.7865vh);

  --sw: calc(var(--u) * var(--aw));
  --sh: calc(var(--u) * var(--ah));
  --ox: calc(var(--sw) - 100vw);   /* horizontal overflow, never < 0 */
  --oy: calc(var(--sh) - 100vh);   /* vertical overflow,   never < 0 */

  /* Point of the photograph pulled toward the centre of the window, as
     a fraction. Horizontal only bites in tall windows, vertical only in
     wide ones. 0.492 is the cabinet's centre. Vertically, 0.52 keeps
     the pedestal's foot in frame in wide windows - the set reads as
     ungrounded if its base is cut, whereas losing a little antenna
     just reads as framing (the rods are already cropped in the
     source photograph). */
  --anchor-x: 0.492;
  --anchor-y: 0.52;
  --lift: 0px;          /* extra upward nudge, per breakpoint */

  /* screen cut-out, as a fraction of the photograph */
  --screen-left:   29.8913%;   /* 880 / 2944 */
  --screen-top:    30.5825%;   /* 504 / 1648 */
  --screen-width:  29.0082%;   /* 854 / 2944 */
  --screen-height: 40.6553%;   /*  670 / 1648 */
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  overflow: hidden;
  background: #1a0d12;
}

html { overflow: hidden; }

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- stage -------------------------------------------------------- */

.stage {
  position: fixed;
  /* Pull the anchor toward the centre, but clamp so the stage can never
     be dragged away from an edge: the offset is limited to however much
     the stage actually overflows. Without the clamp a barely-oversized
     stage leaves a sliver of background showing. */
  left: clamp(calc(-1 * var(--ox)), calc(50vw - var(--anchor-x) * var(--sw)), 0px);
  top:  clamp(calc(-1 * var(--oy)), calc(50vh - var(--anchor-y) * var(--sh) - var(--lift)), 0px);
  z-index: 1;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
          user-select: none;
  width: var(--sw);
  height: var(--sh);
}

.stage:focus-visible {
  outline: 3px solid #ff3399;
  outline-offset: 4px;
}

.set {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  display: block;
  /* the photograph's own aspect matches the stage, so this never crops */
  object-fit: fill;
  pointer-events: none;
  user-select: none;
}

/* A very tall window crops hard horizontally. Re-anchor on the screen
   cut-out (centre x 1307 / 2944) so the picture survives even when the
   cabinet's edges do not. */
@media (max-aspect-ratio: 4 / 5) {
  :root { --anchor-x: 0.444; }
}

/* Phone held sideways: the photograph has more headroom than footroom,
   so the default anchor leaves the set sitting low — roughly 49px of
   space above it and 15px below. Nudging the anchor down the image
   lifts the set to sit evenly in the frame. */
@media (orientation: landscape) and (max-height: 500px) {
  /* The lift is bounded by the photograph's bottom edge: once the
     stage bottom reaches the viewport bottom the clamp holds it there,
     or background would show. About 15px is reachable at native scale;
     the request for 30 is clamped to that rather than clipping the
     cabinet or breaking full-bleed. */
  :root {
    --anchor-y: 0.558;
    --lift: 30px;
  }
}

/* --- sound control ------------------------------------------------ */

.sound {
  position: fixed;
  z-index: 3;
  right: 3vmin;
  bottom: 3vmin;
  width: 7vmin;
  height: 7vmin;
  min-width: 34px;
  min-height: 34px;
  padding: 0;
  border: 2px solid rgba(255, 255, 255, 0.78);
  border-radius: 50%;
  background: rgba(16, 6, 12, 0.72);
  color: rgba(255, 255, 255, 0.9);
  font-size: 3.2vmin;
  line-height: 1;
  cursor: pointer;
  -webkit-backdrop-filter: blur(4px);
          backdrop-filter: blur(4px);
  transition: color 0.18s, border-color 0.18s, background 0.18s;
}

.sound__glyph { display: block; transform: translateY(-2%); }

/* Bigger on touch devices — 30% up on the desktop size, which also
   clears the 44px minimum touch target. */
@media (max-width: 640px), (pointer: coarse) {
  .sound {
    width: 9.1vmin;
    height: 9.1vmin;
    min-width: 44px;
    min-height: 44px;
    font-size: 4.2vmin;
  }
}

/* struck through while muted, so the state reads at a glance */
.sound::after {
  content: "";
  position: absolute;
  left: 18%;
  right: 18%;
  top: 50%;
  height: 2px;
  transform: rotate(-45deg);
  background: rgba(255, 255, 255, 0.9);
}

.sound[aria-pressed="true"] {
  color: #ff3399;
  border-color: #ff3399;
  background: rgba(60, 4, 30, 0.7);
  box-shadow: 0 0 12px rgba(255, 51, 153, 0.55);
}

.sound[aria-pressed="true"]::after { display: none; }

.sound:focus-visible { outline: 3px solid #ff3399; outline-offset: 3px; }

/* --- screen ------------------------------------------------------- */

.screen {
  position: absolute;
  z-index: 1;
  left: var(--screen-left);
  top: var(--screen-top);
  width: var(--screen-width);
  height: var(--screen-height);
  overflow: hidden;
  background: #000;
}

#tube {
  display: block;
  width: 100%;
  height: 100%;
  /* low internal res blown up: keep the pixels chunky, never smoothed */
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}

/* Everything below is sized in percentages of the screen, so the CRT
   treatment holds its proportions independently of the stage unit. */

.roll {
  position: absolute;
  left: 0; right: 0; top: 0;
  height: 20%;
  mix-blend-mode: screen;
  background: linear-gradient(
    to bottom,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0.06) 28%,
    rgba(255, 255, 255, 0.15) 50%,
    rgba(255, 255, 255, 0.05) 74%,
    rgba(255, 255, 255, 0) 100%);
  animation: roll 6.5s linear infinite;
}

@keyframes roll {
  from { transform: translateY(-110%); }
  to   { transform: translateY(510%); }
}

.scanlines {
  position: absolute;
  inset: 0;
  mix-blend-mode: multiply;
  background: repeating-linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0.30) 0%,
    rgba(0, 0, 0, 0.30) 0.45%,
    rgba(0, 0, 0, 0) 0.45%,
    rgba(0, 0, 0, 0) 1.1%);
}

/* Soft bulge and corner falloff. Kept restrained: the photograph
   carries its own glass and lighting, so a strong CSS reflection
   would fight it. */
.glass {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(74% 64% at 44% 34%,
      rgba(255, 255, 255, 0.10) 0%,
      rgba(255, 255, 255, 0.02) 48%,
      rgba(255, 255, 255, 0) 74%),
    radial-gradient(118% 112% at 50% 48%,
      rgba(0, 0, 0, 0) 46%,
      rgba(0, 0, 0, 0.30) 78%,
      rgba(0, 0, 0, 0.72) 100%);
}

/* --- the set switching itself off --------------------------------- */

/* The classic tube power-down: the picture collapses to a line, the
   line to a dot, the dot to nothing. The end state is declared as
   well as animated, so with motion turned off it simply goes dark
   rather than staying on. */

@keyframes tv-off {
  0%   { transform: scale(1, 1);        filter: brightness(1); opacity: 1; }
  30%  { transform: scale(1, 0.015);    filter: brightness(2.8); opacity: 1; }
  58%  { transform: scale(0.22, 0.012); filter: brightness(3.6); opacity: 1; }
  100% { transform: scale(0, 0.008);    filter: brightness(0); opacity: 0; }
}

.screen.is-off #tube {
  transform: scale(0, 0.008);
  opacity: 0;
  animation: tv-off 900ms cubic-bezier(0.45, 0, 0.2, 1) forwards;
}

.screen.is-off .roll,
.screen.is-off .scanlines,
.screen.is-off .glass { opacity: 0; transition: opacity 0.3s ease; }

/* display:flex on its own would beat the browser's own [hidden] rule,
   which leaves the message laid out and waiting to be revealed by any
   stray opacity change */
.off[hidden] { display: none; }

.off {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4%;
  margin: 0;
  padding: 0 8%;
  text-align: center;
  pointer-events: none;
  /* opacity lives here rather than in the keyframes, so killing the
     animation reveals the message instead of hiding it forever */
  opacity: 1;
  animation: off-in 700ms ease 900ms backwards;
}

@keyframes off-in {
  from { opacity: 0; transform: translateY(6%); }
}

.off__big, .off__sub {
  margin: 0;
  font-family: "Arial Narrow", "Helvetica Neue", Arial, sans-serif;
  font-weight: 700;
  letter-spacing: 0.04em;
  line-height: 1.1;
  text-transform: uppercase;
  color: #ff3399;
  text-shadow: 0 0 8px rgba(255, 51, 153, 0.55);
}

.off__big { font-size: 15%; font-size: clamp(18px, 3.4vmin, 46px); }
.off__sub {
  font-size: clamp(9px, 1.5vmin, 20px);
  color: rgba(255, 214, 234, 0.92);
  text-shadow: 0 0 6px rgba(255, 51, 153, 0.4);
}

/* --- reduced motion ----------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .roll { display: none; }
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
  }
}
