/* base.css — page layout, the video screen, the two display modes, the static
 * overlay, the interaction shield, the channel badge, and the title strip. */

* { box-sizing: border-box; }

/* nothing on the TV is selectable (it's an appliance) — except real text inputs */
* { -webkit-user-select: none; user-select: none; }
input, textarea { -webkit-user-select: text; user-select: text; }

html, body {
  margin: 0;
  height: 100%;
  background: #000;
  color: #ddd;
  font-family: "Courier New", monospace;
  overflow: hidden;
}
/* stacked regions: video / now-playing title / controls — never overlaid */
body { display: flex; flex-direction: column; }

#screen {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  background: #000;
  overflow: hidden;
  container-type: size;                  /* enables cqw/cqh sizing below */
  isolation: isolate;                    /* a stacking context: everything inside (incl. the
                                            z70 scanlines) stays SCOPED to the tube, so the
                                            page modals (panel/settings/debug) layer above it */
}
/* BLACK caps at the very top & bottom of the PICTURE (the frame edges, where the OSD bars meet the
   video), ABOVE both the video and the bars. The robust mask for the 1px edge seam — whether it's a
   sub-pixel clip, the TUNING shake's residual, or an overlay effect — in the site's black so it
   reads as a thin CRT overscan border, never a coloured/flashing line, in any letterbox. */
/* Black caps over the PICTURE's top & bottom edges. They live at the SCREEN level (NOT inside
   #frame), positioned ON the frame edges via the same letterbox math, so they're NOT subject to
   #frame's fractional clip — which is what left a 1px seam of video right at the edge (and why it
   differed top vs bottom: the frame sits at fractional pixels). Each straddles its edge so the seam
   sits in the cap's solid middle — covered on both edges, any aspect/letterbox, a thin CRT border. */
#capt, #capb {
  position: absolute; left: 0; right: 0; height: 5px;
  background: #000; z-index: 80; pointer-events: none;
}
body.mode-wide  #capt { top:    calc((100cqh - min(100cqh, calc(100cqw * 9 / 16))) / 2 - 2px); }
body.mode-wide  #capb { bottom: calc((100cqh - min(100cqh, calc(100cqw * 9 / 16))) / 2 - 2px); }
body.mode-retro #capt { top:    calc((100cqh - min(100cqh, calc(100cqw * 3 / 4))) / 2 - 2px); }
body.mode-retro #capb { bottom: calc((100cqh - min(100cqh, calc(100cqw * 3 / 4))) / 2 - 2px); }
/* hide the caps DURING the CRT power on/off animation (the frame scales, so a fixed-position cap
   would float over the animation) — they reappear the moment the animation class is removed. */
body.crt-on #capt, body.crt-on #capb, body.crt-off #capt, body.crt-off #capb { display: none; }

/* Two display modes (toggled on <body>), no auto-detection of black bars:
   - mode-wide:  a 16:9 box that fits the screen (whole widescreen video).
   - mode-retro: a 4:3 box; the 16:9 player overflows it horizontally so the
     central 4:3 region fills the frame (sides cropped) — a true 4:3 CRT. */
#frame {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  /* overflow:CLIP (not hidden) — a hard paint-clip that contains the composited video layer (its
     translate3d/scale during tuning would otherwise escape a plain overflow:hidden and bleed past
     the edge). Unlike clip-path it doesn't add a clip layer that streaked a black seam across the
     centre of large (4K) frames during tuning. */
  overflow: clip;
  z-index: 1;
  background: #000;
}
/* Four stacked player slots fill the frame. The live one sits on top and opaque;
   the others keep PLAYING underneath (muted), fully occluded — moving an iframe in
   the DOM would reload it, so we never move them, we just restack. */
#players { position: absolute; inset: 0; z-index: 2; }
.pslot {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: #000;
  pointer-events: none;                  /* never interact with YT directly */
}
.pslot.live { z-index: 2; }
.pslot.bg { z-index: 0; }                 /* behind the live slot, still streaming */
.pslot > div, .pslot iframe {
  display: block;
  border: 0;
  background: #000;
  position: absolute;
}

body.mode-wide #frame {
  width: min(100cqw, calc(100cqh * 16 / 9));
  height: min(100cqh, calc(100cqw * 9 / 16));
}
body.mode-wide .pslot iframe, body.mode-wide .pslot > div {
  /* BASE overscan past the frame on every edge — kills sub-pixel edge gaps AND gives the tuning
     hunt enough margin to TRANSLATE (±12px) without revealing the frame's black, so the hunt needs
     no scale (scaling re-rastered the 4K layer and left a centre seam). The frame clips the overscan. */
  top: -16px; left: -10px; width: calc(100% + 20px) !important; height: calc(100% + 32px) !important;
}

body.mode-retro #frame {
  width: min(100cqw, calc(100cqh * 4 / 3));
  height: min(100cqh, calc(100cqw * 3 / 4));
}
body.mode-retro .pslot iframe, body.mode-retro .pslot > div {
  top: -16px;                            /* vertical overscan margin for the hunt translate (width already overscans) */
  left: 50%;
  transform: translateX(-50%);
  height: calc(100% + 32px) !important;
  width: 133.34% !important;             /* 16:9 relative to the 4:3 frame */
}

/* faint always-on reception-noise veil; opacity set per channel by JS */
#reception {
  position: absolute;
  inset: 0;
  z-index: 5;                          /* snow overlays the PICTURE (video z2, static z4, shield z3)
                                          but sits UNDER the OSD: black OSD bars (z6) + volume (z7),
                                          the cut/DECODING (z9), roll (z10), TUNING (z11). The OSD is
                                          graphics painted over a snowy tube — noise must not fleck it. */
  width: 100%;
  height: 100%;
  display: none;
  pointer-events: none;
  image-rendering: pixelated;
  transform: translateZ(0);            /* own GPU layer for cheap compositing */
  will-change: opacity;
}

/* CRT SCANLINES — horizontal dark lines over the tube, the way an analog set draws the picture in
   discrete raster lines. At the #screen level (not inside #frame), over the video + OSD + TUNING card,
   but BELOW the off-state standby. ONE dark-over-clear tile tiled vertically via background-size+repeat
   (NOT a repeating-gradient — Firefox caps those at a fine period). Fully GPU-composited (own layer,
   no per-frame cost). The period scales with the display: finer on high-DPI so the lines stay crisp
   instead of fat. Analog only. --gr-a (line darkness) / --gr-size (line period) are FX-debug tweakable. */
#grille {
  --gr-a: 0.20;
  --gr-size: 3px;
  position: fixed;
  inset: 0;
  z-index: 1000;            /* TOP-LEVEL: above every overlay (menus, OSD, standby) so scanlines cover all */
  display: block;
  pointer-events: none;
  background-image: linear-gradient(0deg, rgba(0, 0, 0, var(--gr-a)) 0 50%, transparent 50% 100%);
  background-size: 100% var(--gr-size);
  background-repeat: repeat;
  transform: translateZ(0);
}
/* high-DPI: tighter line pitch so retina screens get finer, more authentic scanlines (respect the dpi) */
@media (min-resolution: 1.5dppx) { #grille { --gr-size: 2px; } }

/* TV static burst — covers the player's start-of-video chrome until it fades.
   A tiny noise canvas scaled up pixelated. */
#static {
  position: absolute;
  inset: 0;
  z-index: 4;
  width: 100%;
  height: 100%;
  display: none;
  pointer-events: none;
  background: #000;
  image-rendering: pixelated;
  opacity: 1;
  transition: opacity 0.15s linear;
  transform: translateZ(0);            /* own GPU layer */
  will-change: opacity;
}
#static.fade { opacity: 0; }

/* DIGITAL transition: a plain black layer that holds during the load and fades back when
   the video is live (digitaltv.js). Pure opacity → GPU, no canvas, no snow. */
#digital {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: none;
  pointer-events: none;
  background: #000;
  opacity: 1;
  transition: opacity 0.15s linear;
  transform: translateZ(0);
  will-change: opacity;
}
#digital.fade { opacity: 0; }

/* Analog CRT "tube" — a SLIGHT faked bulge: the picture's edges + corners darken as if the
   glass curves away. Pure static radial gradient + inset edge-shadow → one GPU-composited
   layer, no per-frame cost, no blend-mode, so it's free on any device. Analog only (digital
   panels are flat). A true geometric barrel-distortion would need an SVG/shader filter that
   isn't reliably GPU-accelerated on mobile, so we fake it with shadows per the brief. */
#tube {
  position: absolute;
  inset: 0;
  z-index: 69;                         /* ABOVE the OSD bars + TUNING (so the vignette darkens them too,
                                          like they're under the glass), just below the grille (z70) */
  display: none;
  opacity: 0.72;                       /* user-dialled analog combo (FX-debug): softer vignette/sheen */
  pointer-events: none;
  background:
    /* curved-glass sheen sweeping from the top-left, like light on the bulged glass */
    linear-gradient(123deg, rgba(255,255,255,0.075) 0%, rgba(255,255,255,0.015) 34%, rgba(255,255,255,0) 60%),
    /* curvature vignette — edges & corners fall off as the glass turns away (kept gentle) */
    radial-gradient(142% 142% at 50% 47%, transparent 52%, rgba(0,0,0,0.10) 80%, rgba(0,0,0,0.275) 100%);
  background-size: 100% 100%, 100% 100%;
  /* (the vertical R·G·B striping is the dedicated #grille layer — not duplicated here) */
  /* ONLY soft, blurred inset shadows — the curvature vignette. NO sharp 0-blur edge lines: with
     #tube above the OSD (z69), a crisp top-rim / bottom-shade line would draw a 1px streak across
     the OSD bars at the screen edges. */
  box-shadow:
    inset 0 0 95px rgba(0,0,0,0.16),         /* deep edge fall-off (curvature) */
    inset 0 0 30px rgba(0,0,0,0.12);         /* tighter inner edge */
}
body:not(.digital) #tube { display: block; }   /* analog signal only */
body.digital #grille { display: none; }     /* digital panels are flat — no aperture grille */


/* (scanlines are a single static GPU-composited layer, ~free.) */

/* standby: power "off" blanks the whole TV to black (video keeps running, muted) */
#standby {
  position: absolute;
  inset: 0;
  z-index: 75;                /* ABOVE the grille (z70) so the off-screen + power button stay clean */
  background: #000;
  display: none;
}
body.standby #standby { display: block; }

/* While the TV warms up the screen is just black; the loading.js teletext (raised above #standby) shows
   SERVICE CONNECTING, then a "START WATCHING" row that turns the set on. (No in-screen power button.) */

/* CRT power animation (GPU: transform + opacity). Off: the picture collapses to a
   bright horizontal line, then folds to a centre dot and vanishes — with a flash.
   On: the reverse. The frame keeps its centring translate in every keyframe. */
body.crt-off #frame { animation: crt-off 0.5s ease-in forwards; }
body.crt-on  #frame { animation: crt-on 0.55s cubic-bezier(.2,.7,.3,1) forwards; }
@keyframes crt-off {
  0%   { transform: translate(-50%, -50%) scale(1, 1); }
  48%  { transform: translate(-50%, -50%) scale(1.04, 0.014); }   /* collapse to a wide bright line */
  100% { transform: translate(-50%, -50%) scale(0, 0.014); }      /* fold the line to a centre dot, gone */
}
@keyframes crt-on {
  0%   { transform: translate(-50%, -50%) scale(0, 0.014); }
  45%  { transform: translate(-50%, -50%) scale(1.04, 0.014); }   /* dot opens into a line */
  100% { transform: translate(-50%, -50%) scale(1, 1); }
}
/* the flash: a quick whole-screen brighten as the line snaps to / from the dot */
#crtflash {
  position: absolute;
  inset: 0;
  z-index: 60;
  background: #fff;
  opacity: 0;
  pointer-events: none;
}
body.crt-off #crtflash { animation: crt-flash-off 0.5s ease-in; }
body.crt-on  #crtflash { animation: crt-flash-on 0.55s ease-out; }
/* a quick instability shake of the whole TV during the power transition */
body.crt-off #screen, body.crt-on #screen { animation: crt-shake 0.07s steps(2) infinite; }
@keyframes crt-shake {
  0%   { transform: translate(0, 0); }
  25%  { transform: translate(-1px, 2px); }
  50%  { transform: translate(1px, -2px); }
  75%  { transform: translate(-1px, 1px); }
  100% { transform: translate(1px, 0); }
}
@keyframes crt-flash-off { 0%,42% { opacity: 0; } 56% { opacity: 0.6; } 72% { opacity: 0.12; } 100% { opacity: 0; } }
@keyframes crt-flash-on  { 0% { opacity: 0.55; } 22% { opacity: 0.12; } 100% { opacity: 0; } }

/* transparent layer over the player: swallows ALL YouTube interaction. There
   is no pausing — at most a click starts playback if autoplay was blocked. */
#shield {
  position: absolute;
  inset: 0;
  z-index: 3;
  cursor: default;
}

/* channel name (top bar) + program name (bottom bar) are styled in osd.css */
