/* toi/web/styles.css — a television, not a web page.
 *
 * Modern but nostalgic: near-black with a warm cast, amber phosphor for the
 * things a CRT would have glowed, one cool accent for channel numbers, and
 * restraint everywhere else. No gradients for their own sake, no purple.
 * Tabular numerals throughout, because a clock that reflows is a clock you
 * cannot read at a glance.
 */
:root{
  /* ONE palette, used by the guide AND every piece of chrome. The picture
     sits on black because video must; everything else is paper, sky, sage and
     terracotta so the set reads as one object rather than two. */
  --bg:#0c0b09;            /* behind the picture only            */
  --panel:#f6f0e2;
  --raise:#ece4d0;
  --ink:#463f34;           /* warm ink, never black              */
  --dim:#6f6455;
  --faint:#968975;
  --line:rgba(70,63,52,.18);
  --line-2:rgba(70,63,52,.10);
  --amber:#c87f5c;         /* terracotta carries the accent      */
  --amber-dim:#a9613f;
  --cyan:#6fa2bd;
  --tv-cyan:#63d2dd;       /* the on-screen display, which is a TUBE */
  --tv-amber:#ffb23a;
  --ok:#5c8f6b; --warn:#b8862f; --bad:#b0453c;
  /* guide — soft painted palette: sky, paper, sage, terracotta. Flat washes
     and generous air rather than hard chassis edges; nostalgic in the COLOUR,
     modern in the craft (no bevels, no texture overlays, crisp type). */
  --sky:#9dc3d6; --sky-deep:#6fa2bd; --sky-pale:#d6e7ef;
  --paper:#f6f0e2; --paper-2:#ece4d0; --paper-3:#e0d5bd;
  --gink:#463f34; --gdim:#6f6455; --gfaint:#9689756; --gfaint:#968975;
  --grule:rgba(70,63,52,.16);
  --sage:#93ab7c; --sage-deep:#6f8a5c;
  --terra:#c87f5c; --terra-deep:#a9613f;
  --chassis:var(--paper); --gpanel:var(--paper-2); --gpanel2:var(--paper);
  /* ── THE GUIDE'S OWN SET ───────────────────────────────────────────
     A dark ground and ONE accent.

     It used to cycle three block colours down the rows — teal, blue, sand —
     which reads as decoration rather than information: the colour changed for
     reasons the viewer cannot use, and the eye has to work out that it means
     nothing. Every real EPG does the opposite: a calm dark ground, one
     highlight that means "this is you", one red line that means "this is now".
     Colour carries meaning or it is noise.

     Dark, not warm grey, because the guide is on a television in a room with
     the lights down, and because the picture behind it is black. */
  --g-grey:#0d131f;        /* the ground                              */
  --g-grey-2:#121a28;      /* the track a programme sits on           */
  --g-band:#0a1017;        /* channel column and masthead             */
  --g-band-2:#141d2c;      /* the time ruler                          */
  --g-block:#1b2536;       /* a programme                             */
  --g-block-2:#212d42;     /* alternating, for row separation only    */
  --g-sel:#1e63c8;         /* THE accent: the channel you are on      */
  --g-sel-2:#2a76e0;
  --g-cream:#eef3fa; --g-ink:#0b1119;
  --g-red:#ff4d3d;         /* now, and only now                       */
  --g-yellow:#ffc65c;      /* channel numbers                         */
  --g-dim:#8fa2bd;
  --g-teal:#1b2536; --g-blue:#212d42; --g-sand:#1b2536;
  --chcol:minmax(150px,14vw);
  --red:var(--terra-deep); --blue:var(--sky-deep); --green:var(--sage-deep);
  --mono:ui-monospace,"SF Mono",Menlo,Consolas,monospace;
  --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;
}
*{box-sizing:border-box}
html,body{height:100%;margin:0;background:var(--bg);color:var(--ink);
  font:14px/1.45 var(--sans);overflow:hidden;-webkit-font-smoothing:antialiased}

#tv{position:fixed;inset:0;background:var(--bg);display:grid;place-items:center;overflow:hidden}

/* ── the picture ──────────────────────────────────────────────────────── */
/* The picture is CENTRED in the frame and never enlarged past what the source
 * can carry — modules/screen/h264.mjs sets the ceiling from the frame's own
 * size. Filling the screen is not the same as looking good: a 360p source
 * stretched to a 1920 window is three times its own size and three times as
 * soft. Broadcast pillarboxes an SD feed rather than stretching it. */
#screen{display:none;width:100%;height:100%;object-fit:contain;background:#000;
  margin:auto;
  /* A CHANNEL CHANGE BETWEEN DIFFERENT RESOLUTIONS MOVES THE PICTURE BOX.
     It has to — the cap on enlargement is computed from the source, and the
     sources genuinely differ — but it should GLIDE rather than snap. A jump
     reads as a fault; an ease reads as the set adjusting, which is what it is.
     Short enough not to feel slow behind a 40ms channel change. */
  transition:max-width .22s ease, max-height .22s ease}
#screen.on{display:block}
/* Smooth for real video. The test pattern is a MEASUREMENT and gets
   pixelated instead — a smoothing filter hides the single-pixel defects it
   exists to expose. boot.js sets this per format. */
#screen.measure{image-rendering:pixelated}




/* ══ CHANNEL 0 — a guide CHANNEL ═══════════════════════════════════════
 * Not a web page with nice colours. A listings grid the way television did
 * it: warm grey ground, solid flat colour blocks, type sized to be read
 * across a room, and the grid filling the frame rather than floating in
 * whitespace. 80s/90s consumer-electronics and cel-animation palette,
 * executed with modern craft — crisp type, real spacing, no fake ageing.
 */
#guide{display:none;position:absolute;inset:0;overflow:hidden;
  background:var(--g-grey);color:var(--g-ink);font-family:var(--sans)}
#guide.on{display:flex;flex-direction:column}

.ep{position:absolute;inset:0;display:flex;flex-direction:column}

/* masthead — a solid band, big and calm */
.ep-top{display:flex;align-items:center;justify-content:space-between;gap:1em;
  background:var(--g-band);color:var(--g-cream);padding:1.6vh 2.6vw;flex:0 0 auto}
.ep-brand{display:flex;align-items:center;gap:.55em;
  font-size:clamp(21px,2.6vw,36px);font-weight:800;letter-spacing:.34em;
  color:var(--g-cream);text-transform:uppercase}
.ep-brand::before{content:"";width:.44em;height:.44em;background:var(--g-red);
  box-shadow:.7em 0 0 var(--g-yellow), 1.4em 0 0 var(--g-teal);margin-right:1.85em}
.ep-brand span{color:var(--g-yellow)}
.ep-brand em{display:none}
.ep-clock{font-family:var(--mono);font-variant-numeric:tabular-nums;font-weight:700;
  font-size:clamp(15px,1.7vw,25px);color:var(--g-cream);letter-spacing:.1em}

/* the grid FILLS what is left. No void underneath. */
.ep-grid{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;overflow:hidden;
  /* The remote is fixed to the bottom of the screen and floats OVER whatever
     is behind it. On the guide that was the last channel's row, half hidden
     behind the buttons. A guide with a row you cannot read is missing a
     channel. */
  padding-bottom:64px}
.ep-ruler{flex:0 0 auto;display:grid;grid-template-columns:var(--chcol) 1fr;
  background:var(--g-band-2)}
.ep-chhead{padding:.55em 1.1em;font-size:10.5px;letter-spacing:.3em;
  color:var(--g-cream);font-weight:700;opacity:.75}
.ep-ruler .ep-track{position:relative;height:2.3em}
.ep-mark{position:absolute;top:.55em;padding-left:.7em;font-family:var(--mono);
  font-size:11.5px;color:var(--g-cream);font-variant-numeric:tabular-nums;
  border-left:2px solid rgba(255,255,255,.28);line-height:1.1;letter-spacing:.08em;
  font-weight:600}

/* every row shares the leftover height equally — the grid always fills */
.ep-row{flex:1 1 0;min-height:0;display:grid;grid-template-columns:var(--chcol) 1fr;
  border-top:1px solid rgba(143,162,189,.14)}
.ep-ch{display:flex;align-items:center;gap:.7em;padding:0 1.1em;
  background:var(--g-band);color:var(--g-cream)}
.ep-ch b{font-family:var(--mono);font-size:clamp(24px,3vw,42px);
  color:var(--g-yellow);min-width:1.15em;text-align:right;
  font-variant-numeric:tabular-nums;font-weight:700;line-height:1}
.ep-ch span{font-size:clamp(11px,1.15vw,15px);letter-spacing:.18em;
  text-transform:uppercase;font-weight:600;opacity:.92}
.ep-track{position:relative;overflow:hidden;background:var(--g-grey-2)}

/* programmes are SOLID BLOCKS, cycling a small cel palette */
.ep-prog{position:absolute;top:3px;bottom:3px;left:0;
  background:var(--g-block);color:var(--g-cream);
  border-radius:3px;
  padding:.5em .8em;overflow:hidden;display:flex;flex-direction:column;
  justify-content:center;gap:.2em;border-right:3px solid var(--g-grey)}
.ep-row:nth-child(even) .ep-prog{background:var(--g-block-2)}
.ep-prog b{font-size:clamp(12px,1.25vw,18px);font-weight:700;letter-spacing:.01em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ep-prog i{font-style:normal;font-family:var(--mono);font-size:10.5px;
  font-variant-numeric:tabular-nums;color:var(--g-dim);font-weight:600}
.ep-prog.live{background:var(--g-block-2);color:var(--g-cream);
  box-shadow:inset 3px 0 0 var(--g-red)}
.ep-prog.live i{opacity:.85}
.ep-prog.strand{background:var(--g-block);color:var(--g-dim)}
.ep-nowbar{position:absolute;top:0;bottom:0;width:2px;background:var(--g-red);
  box-shadow:0 0 8px rgba(255,77,61,.55);z-index:3}

/* THE DETAIL BAND — what you are watching, above the grid. */
.ep-detail{flex:0 0 auto;display:flex;align-items:center;gap:1.4vw;
  padding:1.5vh 2.6vw;background:linear-gradient(180deg,#12203a,#0d1626);
  border-bottom:1px solid rgba(143,162,189,.18)}
.ep-detail-num{font-family:var(--mono);font-weight:700;line-height:1;
  font-size:clamp(30px,4.2vw,58px);color:var(--g-yellow);
  font-variant-numeric:tabular-nums;min-width:1.4em;text-align:right}
.ep-detail-body{min-width:0;display:flex;flex-direction:column;gap:.35em}
.ep-detail-title{font-size:clamp(17px,2.3vw,34px);font-weight:700;
  color:var(--g-cream);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ep-detail-meta{display:flex;flex-wrap:wrap;gap:.5em 1.1em;align-items:center;
  font-size:clamp(10px,1.05vw,14px);letter-spacing:.14em;text-transform:uppercase;
  color:var(--g-dim);font-weight:600}
.ep-live{color:#fff;background:var(--g-red);padding:.18em .6em;border-radius:2px;
  letter-spacing:.12em}

/* THE ROW YOU ARE ON. The one place colour means something. */
.ep-row.tuned .ep-ch{background:var(--g-sel);color:#fff}
.ep-row.tuned .ep-ch b{color:#fff}
.ep-row.tuned .ep-prog.live{background:var(--g-sel-2);color:#fff;
  box-shadow:inset 3px 0 0 var(--g-red)}
.ep-row.tuned .ep-prog.live i{color:rgba(255,255,255,.82)}

/* ══ CHANNEL BANNER ════════════════════════════════════════════════════ */
#banner{position:absolute;top:5vh;left:4vw;z-index:9;display:flex;align-items:baseline;
  gap:.65em;padding:.42em 1.05em;background:rgba(14,14,16,.9);
  border-left:5px solid var(--tv-amber);
  opacity:0;transform:translateY(-8px);transition:opacity .18s,transform .18s}
#banner.on{opacity:1;transform:none}
#banner b{font-family:var(--mono);font-size:clamp(30px,4vw,52px);line-height:1;
  color:var(--tv-cyan);font-weight:700;text-shadow:0 0 14px rgba(99,210,221,.45)}
#banner span{color:#efe9dc;font-size:clamp(12px,1.35vw,17px);letter-spacing:.2em;
  text-transform:uppercase}

/* ══ NOW/NEXT + POSITION BAR ═══════════════════════════════════════════ */
#info{position:absolute;left:0;right:0;bottom:0;z-index:6;padding:2.4vh 4vw 8.5vh;
  background:linear-gradient(transparent,rgba(12,11,9,.92) 42%);
  opacity:0;transform:translateY(10px);transition:opacity .22s,transform .22s;pointer-events:none}
#info.on{opacity:1;transform:none}
.nf-title{font-size:clamp(17px,2.2vw,30px);font-weight:600;color:var(--ink);letter-spacing:.01em}
.nf-sub{display:flex;gap:1.4em;flex-wrap:wrap;margin-top:.35em;
  font-family:var(--mono);font-size:clamp(10.5px,1.05vw,13.5px);color:var(--dim)}
.nf-sub .live{color:var(--amber)}
.nf-bar{margin-top:.9em;height:3px;background:rgba(242,237,227,.12);position:relative;overflow:hidden}
.nf-fill{position:absolute;inset:0 auto 0 0;background:var(--amber);
  box-shadow:0 0 8px rgba(255,178,58,.5)}
.nf-times{display:flex;justify-content:space-between;margin-top:.4em;
  font-family:var(--mono);font-size:11px;color:var(--faint);font-variant-numeric:tabular-nums}

/* ══ THE REMOTE ════════════════════════════════════════════════════════ */
#bar{position:absolute;bottom:0;left:0;right:0;z-index:7;display:flex;gap:9px;
  padding:14px 4vw;justify-content:center;align-items:stretch;
  opacity:.4;transition:opacity .18s}
#tv:hover #bar,#bar:focus-within{opacity:1}
#bar .grp{display:flex;border:1px solid var(--ink);background:var(--panel);
  backdrop-filter:blur(8px)}
#bar button{padding:.5em 1.05em;cursor:pointer;background:transparent;color:var(--dim);
  border:0;border-right:1px solid var(--line-2);font:inherit;font-size:12.5px;
  letter-spacing:.1em;text-transform:uppercase;transition:color .12s,background .12s}
#bar .grp button:last-child{border-right:0}
#bar button:hover{background:var(--sky-pale);color:var(--ink)}
#bar button.on{background:var(--terra-deep);color:var(--panel)}

/* the CH readout: shows the channel you are ON, and accepts a new one */
#bar .chnum{display:flex;align-items:center;gap:.6em;padding:.25em .85em;
  border:1px solid var(--ink);background:var(--panel)}
#bar .chnum label{font-size:10px;color:var(--faint);letter-spacing:.24em}
#bar .chnum input{width:2.6em;background:transparent;border:0;outline:0;
  color:var(--amber);font-family:var(--mono);font-size:19px;font-variant-numeric:tabular-nums;
  text-align:center;padding:0}
#bar .chnum input::placeholder{color:var(--faint)}
#bar .chnum.typing{border-color:var(--red)}
#bar .chnum.typing input{color:var(--red)}

/* the big number while you type, like a TV OSD */
#entry{position:absolute;top:5vh;left:4vw;z-index:10;display:none;
  padding:.3em .85em;background:rgba(14,14,16,.9);border-left:5px solid var(--tv-amber);
  font-family:var(--mono);font-size:clamp(30px,4vw,52px);line-height:1.1;
  color:var(--tv-cyan);letter-spacing:.14em;font-variant-numeric:tabular-nums;
  font-weight:700;text-shadow:0 0 14px rgba(99,210,221,.45)}
#entry.on{display:block}

/* ══ INSTRUMENTATION ═══════════════════════════════════════════════════ */
#osd{position:absolute;top:4vh;right:4vw;z-index:7;display:none;
  background:var(--panel);border:2px solid var(--ink);padding:14px 16px;
  font-family:var(--mono);font-size:11.5px;backdrop-filter:blur(8px);min-width:300px;
  max-height:82vh;overflow:auto}
#osd.on{display:block}
#stats table{border-collapse:collapse;width:100%}
#stats td{padding:1.5px 0}
#stats td:first-child{color:var(--faint);padding-right:1.8em;white-space:nowrap}
#stats td:last-child{text-align:right;color:var(--dim);font-variant-numeric:tabular-nums}
#stats .hi{color:var(--terra-deep);font-weight:700}
#stats .ok{color:var(--ok)} #stats .warn{color:var(--warn)} #stats .bad{color:var(--bad)}

#status{position:absolute;bottom:11vh;left:4vw;z-index:6;font-family:var(--mono);
  font-size:11.5px;color:var(--panel);letter-spacing:.04em;
  background:rgba(12,11,9,.6);padding:.2em .6em}
#status.bad{color:var(--bad)}

/* audio readout — source layout, what it is downmixed to, and a live meter */
.nf-audio{display:flex;align-items:center;gap:1.1em;flex-wrap:wrap;margin-top:.7em;
  font-family:var(--mono);font-size:clamp(10px,1vw,12.5px);color:var(--dim);
  letter-spacing:.04em}
.nf-audio b{color:var(--gfaint);font-weight:600;letter-spacing:.22em;font-size:.92em}
.nf-audio.muted{color:var(--gfaint)}
.nf-fmt{color:var(--ink);font-weight:600}
.nf-fmt em{color:var(--red);font-style:normal;padding:0 .15em}
.nf-note{color:var(--gfaint)}
.nf-cta{color:#fff;background:var(--red);padding:.1em .55em;font-weight:600;letter-spacing:.1em}
.nf-meter{display:inline-flex;gap:2px;align-items:flex-end;height:12px;margin-left:auto}
.nf-meter i{width:3px;height:100%;background:rgba(246,240,226,.22);display:block}
.nf-meter i.on{background:var(--amber)}
.nf-meter i.hot{background:var(--red)}

/* ══ AUDIO / EQ PANEL ══════════════════════════════════════════════════ */
#eq{position:absolute;left:4vw;bottom:9vh;z-index:7;display:none;
  background:var(--panel);border:2px solid var(--ink);
  padding:14px 16px 12px;min-width:330px;
  font-family:var(--mono);font-size:11.5px;color:var(--dim)}
#eq.on{display:block}
.eq-head{display:flex;align-items:baseline;gap:.9em;margin-bottom:10px;
  padding-bottom:8px;border-bottom:1px solid rgba(255,255,255,.1)}
.eq-head b{color:var(--ink);letter-spacing:.24em;font-weight:700}
.eq-head span{color:var(--gfaint);font-size:10.5px}
.eq-none{color:var(--gfaint);padding:6px 0 2px}

.eq-presets{display:flex;gap:5px;margin-bottom:14px;flex-wrap:wrap}
.eq-presets button{padding:.38em .75em;cursor:pointer;background:transparent;
  color:var(--dim);border:1px solid var(--line);font:inherit;font-size:11px;
  letter-spacing:.06em}
.eq-presets button:hover{border-color:var(--red);color:#fff}
.eq-presets button.on{background:var(--terra-deep);border-color:var(--terra-deep);color:var(--panel)}
.eq-presets button[disabled]{opacity:.75;cursor:default}

.eq-bands{display:flex;gap:14px;justify-content:space-between;margin-bottom:14px}
.eq-band{display:flex;flex-direction:column;align-items:center;gap:6px}
.eq-db{color:var(--amber);font-variant-numeric:tabular-nums;min-height:1.1em;font-size:11px}
.eq-hz{color:var(--gfaint);font-size:10px}
.eq-band input[type=range]{writing-mode:vertical-lr;direction:rtl;
  width:16px;height:92px;accent-color:var(--terra-deep);background:transparent;cursor:pointer}

.eq-rows label{display:flex;align-items:center;gap:.8em;margin-top:7px}
.eq-rows label span{flex:0 0 9.5em;color:var(--gfaint)}
.eq-rows label b{flex:0 0 4em;text-align:right;color:var(--amber);
  font-variant-numeric:tabular-nums;font-weight:500}
.eq-rows input[type=range]{flex:1;accent-color:var(--sky-deep);cursor:pointer}

/* A strand is continuous content, not a programme — flatter, no start time.
 *
 * This rule is HERE, after the palette block, and it used to paint cream
 * (--paper-2) from the old warm-grey guide. It quietly overrode the dark rule
 * above it, so on a dark grid exactly one block — channel 3 — came out light,
 * which reads as a highlight and means nothing. Two rules for one selector in
 * one stylesheet is a coin toss decided by line order. */
.ep-prog.strand{border-left-color:var(--g-dim);background:var(--g-block)}
.ep-prog.strand b{color:var(--g-dim)}
.ep-prog.strand i{font-style:italic;color:var(--g-dim)}

/* ══ TOUCH & SMALL SCREENS ═════════════════════════════════════════════
 * A phone is a television that happens to be small, not a desktop that got
 * narrow. Three things break without this, and the first is fatal:
 *
 *  1. The remote reveals on :hover, and TOUCH HAS NO HOVER — so on a phone
 *     the only control surface sat at 40% opacity and never came up. The app
 *     was unusable, not merely awkward.
 *  2. The listings grid is positioned by TIME across the width. At 390px a
 *     two-hour window puts a programme in 40 pixels. Narrow screens get the
 *     LIVE programme full-width instead — "what is on now", which is what a
 *     phone is actually asked.
 *  3. Tap targets. 12px buttons are a desktop affordance.
 */
@media (hover: none), (pointer: coarse) {
  /* No hover means no reveal — the remote is always up. */
  #bar { opacity: 1; }
  #bar button { padding: .72em 1.1em; font-size: 13px; }
  #bar .chnum input { font-size: 21px; }
}

@media (max-width: 820px) {
  /* Respect notches and home indicators. */
  #bar { padding: 10px max(3vw, env(safe-area-inset-left)) calc(10px + env(safe-area-inset-bottom)); 
         gap: 7px; flex-wrap: wrap; }
  #banner, #entry { top: max(3vh, env(safe-area-inset-top)); left: max(4vw, env(safe-area-inset-left)); }
  #osd { top: auto; bottom: 13vh; right: 3vw; left: 3vw; min-width: 0; max-height: 52vh; font-size: 11px; }
  #eq  { left: 3vw; right: 3vw; bottom: 13vh; min-width: 0; }
  #info { padding: 2vh 4vw 13vh; }
  .ep-grid { padding-bottom: 92px; }

  /* The guide becomes a NOW list: one row per channel, the live programme
     full width. The time ruler means nothing at this width. */
  .ep-top { padding: 1.4vh 4vw; }
  .ep-brand { letter-spacing: .22em; }
  .ep-brand::before { margin-right: 1.35em; }
  .ep-ruler { display: none; }
  .ep-row { grid-template-columns: minmax(92px, 26vw) 1fr; }
  .ep-ch { padding: 0 .8em; }
  .ep-prog { display: none; }
  .ep-prog.live, .ep-prog.strand { display: flex; left: 0 !important; width: 100% !important;
                                   border-right: 0; }
  .ep-nowbar { display: none; }
}

/* Very small / portrait phones: the channel column shrinks again. */
@media (max-width: 480px) {
  .ep-row { grid-template-columns: minmax(74px, 30vw) 1fr; min-height: 62px; }
  .ep-ch b { font-size: 26px; }
  .ep-ch span { font-size: 10px; letter-spacing: .1em; }
  .ep-prog b { font-size: 13px; }
  #bar button { padding: .7em .9em; font-size: 12px; }
}

/* Landscape phone: the picture is the point — get the chrome out of the way. */
@media (max-height: 480px) and (orientation: landscape) {
  #bar { padding: 6px 3vw; }
  #banner b, #entry { font-size: 28px; }
  #info { padding: 1.5vh 3vw 9vh; }
}

/* The tuner's card: shown when there is nothing else to show, never over a
   running picture. A receiver that renders nothing is a receiver that looks
   broken, even when the fault is upstream and stated. */
#tell{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:8;
  display:none;padding:.8em 1.4em;background:rgba(14,14,16,.86);
  border-left:5px solid var(--tv-amber);color:#efe9dc;
  font-family:var(--mono);font-size:clamp(11px,1.35vw,17px);letter-spacing:.16em;
  text-align:center;max-width:80vw}
#tell.on{display:block}
