/* ──────────────────────────────────────────────────────────────────────────
 *  television.css — the Television tab in the band on the front page.
 *
 *  Two halves. The GRID reuses the radio's card idiom deliberately: a channel
 *  and a station are the same kind of object to somebody browsing, and giving
 *  them two visual languages in one band would read as two products bolted
 *  together. What is new is the SCREEN — a picture needs a black frame, a fixed
 *  aspect and controls that read on top of whatever is being broadcast, none of
 *  which the audio surfaces have ever needed.
 *
 *  Which mode the band shows is one attribute, `#radio[data-mode]`, set by
 *  scripts/songs.js. Everything here keys off it. See styles/songs.css for the
 *  same pattern on the Songs half.
 * ────────────────────────────────────────────────────────────────────────── */

/* ── which half of the band is on screen ──────────────────────────────────
 *
 * The radio's own controls, chips, genre finder and grid all belong to Stations,
 * and the Songs wrap belongs to Songs. In Television mode none of them is the
 * thing being looked at. Stated here rather than relying on the `hidden`
 * attribute alone, because a stylesheet that sets `display` on any of them wins
 * over `hidden` and the row reappears underneath the picture.
 *
 * Keyed on the data attributes and not on the classes. The television half reuses
 * `.radio-more` and `.radio-more-btn` for its "show more" row because a row of
 * results is a row of results and should look like one — so a rule written as
 * `.radio-more { display: none }` in this mode hid the television's own button
 * too, which is a control that is rendered, styled and impossible to press.
 * `[data-radio-more]` is the radio's row and nothing else's. */
#radio[data-mode='television'] [data-radio-only],
#radio[data-mode='television'] [data-radio-chips],
#radio[data-mode='television'] [data-radio-genres],
#radio[data-mode='television'] [data-radio-more],
#radio[data-mode='television'] [data-radio-grid],
#radio[data-mode='television'] [data-songs-wrap] { display: none; }

/* And the inverse: the television half exists only in its own mode. Scoped to
   the attribute rather than to `[hidden]` so there is exactly one rule that
   decides, and no state in which both are briefly true. */
#radio:not([data-mode='television']) .tv-wrap { display: none; }
#radio[data-mode='television'] .tv-wrap { display: block; }

.tv-wrap > * + * { margin-top: 14px; }

/* ── the screen ───────────────────────────────────────────────────────────── */

.tv-screen[hidden] { display: none; }

/* Capped at a little under two thirds of the window and centred. At the band's
   full width a 16:9 picture is taller than the screen on a laptop, which pushes
   every channel in the grid below the fold the moment somebody presses one —
   so choosing the next channel means scrolling back up past the thing you are
   watching. The cap is on the panel rather than the frame so the aspect ratio
   stays exactly 16:9 and the width follows the height. */
/* The screen is a dark glass panel: no drawn frame line, its edge is the
   deep shadow under it and the radius. The bar under the picture is the
   same material as the player bar's glass, smoked dark, with the blur seam
   where the picture lets go of the controls. */
.tv-screen {
  background: var(--tg-dark-surface);
  border: 0;
  border-radius: 24px;
  overflow: hidden;
  box-shadow: 0 24px 60px rgba(11, 11, 18, 0.28), 0 4px 14px rgba(11, 11, 18, 0.14);
  max-width: min(100%, calc(62vh * 16 / 9));
  margin-inline: auto;
}

/* 16:9, held by the box and not by the video, so a 4:3 broadcast letterboxes
   inside a frame that does not move rather than resizing the whole band the
   moment a feed's real dimensions arrive. */
.tv-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  background: #000;
  cursor: pointer;
}

.tv-frame video {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: contain;
  background: #000;
}

/* The spinner sits over the picture. `pointer-events: none` so the frame's own
   press-to-pause still works underneath it — a viewer who presses a spinner is
   asking for something to happen, not asking to be blocked. */
.tv-wait {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
}
.tv-wait[hidden] { display: none; }

.tv-wait span {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 3px solid rgba(255, 255, 255, 0.25);
  border-top-color: var(--tg-cyan);
  animation: tv-spin 0.9s linear infinite;
}

@keyframes tv-spin { to { transform: rotate(360deg); } }

.tv-screen-meta {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  flex-wrap: wrap;
  /* Dark glass over the panel's own black: translucent, blurred, with the
     highlight along its top edge where it meets the picture. */
  background: rgba(24, 24, 40, 0.72);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
  backdrop-filter: blur(20px) saturate(160%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12);
}

.tv-now { min-width: 0; flex: 1; display: flex; align-items: center; gap: 10px; }

/* The channel's mark in the header sits on the dark screen, where the card's
   dark plate would vanish into the background; the rule line keeps its edge. */
.tv-now-art { flex: 0 0 auto; display: grid; }
.tv-now-art .tv-art { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16); }

.tv-now-text { min-width: 0; flex: 1; }

.tv-now-line { display: flex; align-items: center; gap: 8px; min-width: 0; }

.tv-now-name {
  flex: 0 1 auto;
  min-width: 0;
  color: var(--tg-dark-ink);
  font-weight: 650;
  font-size: 15px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Live means a feed is playing, in the colour every viewer already reads as
   live, with the word beside the dot so colour is never the only signal. */
.tv-live-badge {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 8px;
  border-radius: 999px;
  background: #A4123F;
  color: #fff;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  line-height: 1.5;
}
.tv-live-badge::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #fff;
}
.tv-live-badge[hidden] { display: none; }

.tv-now-where {
  display: block;
  color: var(--tg-dark-ink-mute);
  font-size: 12.5px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tv-screen-controls {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
}

.tv-ctl {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 12px;
  border: 0;
  background: rgba(255, 255, 255, 0.09);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14);
  color: var(--tg-dark-ink);
  cursor: pointer;
  transition: background var(--tg-dur-fast) var(--tg-ease-out),
              color var(--tg-dur-fast) var(--tg-ease-out),
              box-shadow var(--tg-dur-fast) var(--tg-ease-out);
}
.tv-ctl:hover { background: rgba(255, 255, 255, 0.17); color: #fff; }
/* Focus is a filled highlight, never an outline: the button fills with the
   accent and lifts on a soft shadow, readable across a room. */
.tv-ctl:focus-visible {
  background: var(--tg-cyan);
  color: #08080F;
  box-shadow: 0 6px 20px rgba(0, 245, 255, 0.35);
  outline: none;
}
.tv-ctl[hidden] { display: none; }
/* The pair of icons inside each control — pause/play, loud/quiet — is swapped by
   the `hidden` attribute. Stated because the browser's own `[hidden]` rule is
   written for HTML elements and an <svg> in an HTML document is not one: without
   this, BOTH halves of every pair draw, on top of each other, and the button
   shows a pause bar and a play triangle at the same time. */
.tv-ctl svg[hidden] { display: none; }
.tv-ctl-close:hover { background: rgba(255, 45, 209, 0.25); }

.tv-volume {
  width: 96px;
  accent-color: var(--tg-cyan);
  cursor: pointer;
}

.tv-screen-status {
  margin: 0;
  padding: 0 12px 11px;
  color: var(--tg-dark-ink-mute);
  font-size: 12.5px;
  min-height: 1em;   /* the line holds its place, so the grid never jumps */
}
.tv-screen-status[data-bad='true'] { color: #FF8FB0; }

/* The one choice left on a channel whose picture this device cannot
   decode: hear it. A filled pill in the accent, sized for a thumb, drawn
   by scripts/television.js only while that honest state is on screen. */
.tv-listen {
  display: block;
  align-self: flex-start;
  margin: 0 12px 13px;
  padding: 9px 18px;
  border: 0;
  border-radius: 999px;
  background: var(--tg-cyan);
  color: #08080F;
  font: inherit;
  font-size: 13.5px;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 6px 20px rgba(0, 245, 255, 0.28);
}
.tv-listen[hidden] { display: none; }

/* Full screen puts this panel on a black field of its own, so the frame has to
   grow into it — an aspect-locked box would otherwise sit in the middle at its
   original size. Controls stay ours and stay visible. */
.tv-screen:fullscreen { display: flex; flex-direction: column; border-radius: 0; max-width: none; }
.tv-screen:fullscreen .tv-frame { flex: 1; aspect-ratio: auto; min-height: 0; }

/* ── the filters ──────────────────────────────────────────────────────────── */

.tv-selects {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
}

.tv-selects select {
  font: inherit;
  font-size: 14px;
  padding: 9px 12px;
  border-radius: 999px;
  border: 0;
  background: rgba(255, 255, 255, 0.66);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
  backdrop-filter: blur(20px) saturate(160%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.65), var(--tg-shadow-1);
  color: var(--tg-ink);
  max-width: 100%;
  cursor: pointer;
}
.tv-selects select:focus-visible {
  outline: none;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.65),
              0 0 0 3px rgba(122, 60, 255, 0.4), var(--tg-shadow-1);
}

.tv-toggle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  color: var(--tg-ink-mute);
  cursor: pointer;
}
.tv-toggle input { accent-color: var(--tg-purple); cursor: pointer; }

.tv-chips {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

/* ── the grid ─────────────────────────────────────────────────────────────── */

.tv-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 12px;
}

/* A channel card is tinted glass, the same material as a station tile: a
   translucent wash cycling through the brand ramp, an inset highlight for
   an edge, no drawn border, no backdrop blur per card (a grid this size
   blurs the page's scroll, not its cards). */
.tv-card {
  display: flex;
  align-items: center;
  gap: 12px;
  text-align: left;
  background: rgba(255, 255, 255, 0.68);
  border: 0;
  border-radius: 20px;
  padding: 10px;
  color: inherit;
  font: inherit;
  cursor: pointer;
  min-height: 72px;
  width: 100%;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.65), var(--tg-shadow-1);
  transition: background var(--tg-dur-fast) var(--tg-ease-out),
              box-shadow var(--tg-dur-fast) var(--tg-ease-out),
              transform var(--tg-dur-fast) var(--tg-ease-out);
}

.tv-grid > .tv-card:nth-child(5n + 2) { background: rgba(255, 235, 248, 0.72); }
.tv-grid > .tv-card:nth-child(5n + 3) { background: rgba(235, 242, 255, 0.72); }
.tv-grid > .tv-card:nth-child(5n + 4) { background: rgba(232, 252, 255, 0.72); }
.tv-grid > .tv-card:nth-child(5n + 5) { background: rgba(243, 238, 255, 0.72); }

.tv-card:hover {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.65), var(--tg-shadow-2);
  transform: translateY(-1px);
}
/* Focus and current are filled highlights, not outlines: focus floods the
   card with the accent wash and an inset ring of shadow; the playing
   channel keeps a steadier version of the same fill. Both read across a
   room, which is the distance this grid gets used at. */
.tv-card:focus-visible {
  outline: none;
  background: rgba(122, 60, 255, 0.16);
  box-shadow: inset 0 0 0 3px rgba(122, 60, 255, 0.65), var(--tg-shadow-2);
}

.tv-card[aria-current='true'] {
  background: rgba(122, 60, 255, 0.13);
  box-shadow: inset 0 0 0 2px rgba(122, 60, 255, 0.55),
              inset 0 1px 0 rgba(255, 255, 255, 0.65), var(--tg-shadow-2);
}

/* Wider than it is tall, and `contain` rather than `cover`: a channel logo is a
   wordmark with the name in it, and cropping it to a square cuts the name off.
   The dark plate is because most of them are white-on-transparent. */
.tv-art {
  flex: 0 0 auto;
  width: 64px;
  height: 40px;
  border-radius: 8px;
  object-fit: contain;
  background: var(--tg-ink);
  padding: 3px;
  display: grid;
  place-items: center;
  font-family: var(--tg-font-display);
  font-weight: 700;
  font-size: 17px;
  color: #fff;
}

.tv-art.letter {
  background: var(--tg-grad-diagonal);
  padding: 0;
  text-shadow: 0 1px 2px rgba(11, 11, 18, 0.3);
}

.tv-text { min-width: 0; flex: 1; }

.tv-name {
  font-weight: 650;
  font-size: 14.5px;
  color: var(--tg-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tv-where {
  font-size: 12.5px;
  color: var(--tg-ink-mute);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tv-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 5px;
  flex-wrap: wrap;
  font-size: 11.5px;
  color: var(--tg-ink-mute);
}

/* Three states, three colours, and a word in each — never colour alone. The
   third is not a softer version of the second: grey means nobody has looked. */
.tv-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 7px;
  border-radius: 999px;
  font-weight: 600;
  line-height: 1.5;
}
.tv-pill::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.tv-pill.live { color: #067647; background: #D9F7E7; }
.tv-pill.unknown { color: var(--tg-ink-mute); background: var(--tg-surface-2); }
.tv-pill.down { color: #A4123F; background: #FBDCE7; }

.tv-q, .tv-feeds, .tv-cat {
  padding: 2px 7px;
  border-radius: 999px;
  background: rgba(11, 11, 18, 0.06);
}

/* ── games: the guides' answer to a typed phrase ────────────────────────────
 *
 * Same card idiom as the grid (a game is a channel wearing a programme),
 * in a group of its own above the channels, under a plain heading. The
 * group exists in the page only when a search found games; the [hidden]
 * rule below is what keeps an empty answer from leaving a heading over
 * a gap. */
.tv-games[hidden] { display: none; }

.tv-games-title {
  font-family: var(--tg-font-display);
  font-size: 17px;
  font-weight: 700;
  color: var(--tg-ink);
  margin: 0 0 10px;
}

.tv-games-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 12px;
}

.tv-games-grid > .tv-card:nth-child(5n + 2) { background: rgba(255, 235, 248, 0.72); }
.tv-games-grid > .tv-card:nth-child(5n + 3) { background: rgba(235, 242, 255, 0.72); }
.tv-games-grid > .tv-card:nth-child(5n + 4) { background: rgba(232, 252, 255, 0.72); }
.tv-games-grid > .tv-card:nth-child(5n + 5) { background: rgba(243, 238, 255, 0.72); }

/* The programme title is the headline here and it is a fact as listed,
   so it wraps rather than truncating: a cut title is a changed one. The
   card aligns to the top for the same reason, a two-line title should
   not push the channel's mark halfway down the card. */
.tv-game-card { align-items: flex-start; }
.tv-game-name { white-space: normal; line-height: 1.35; }

/* ── the guide: what is on in the view being browsed ─────────────────────
 *
 * One group per channel that carries times, in the grid's own card
 * material, headed by the channel and read top down: what is on now
 * first (wearing the same filled On now pill the games group uses),
 * then what follows, each with its start in the viewer's own clock.
 * The coverage line above the groups is part of the design, not a
 * footnote: the schedules we hold cover a minority of any view, and
 * the number is printed where the list is, so the list cannot read as
 * more than it is. */
.tv-guide[hidden] { display: none; }
.tv-guide { margin-top: 4px; }

.tv-guide-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
  margin: 0 0 10px;
}

.tv-guide-title {
  font-family: var(--tg-font-display);
  font-size: 17px;
  font-weight: 700;
  color: var(--tg-ink);
  margin: 0;
}

.tv-guide-cov {
  margin: 0;
  font-size: 13px;
  color: var(--tg-ink-mute);
}

.tv-guide-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 12px;
}

.tv-gchan {
  background: rgba(255, 255, 255, 0.68);
  border-radius: 20px;
  padding: 12px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.65), var(--tg-shadow-1);
}

.tv-guide-list > .tv-gchan:nth-child(5n + 2) { background: rgba(255, 235, 248, 0.72); }
.tv-guide-list > .tv-gchan:nth-child(5n + 3) { background: rgba(235, 242, 255, 0.72); }
.tv-guide-list > .tv-gchan:nth-child(5n + 4) { background: rgba(232, 252, 255, 0.72); }
.tv-guide-list > .tv-gchan:nth-child(5n + 5) { background: rgba(243, 238, 255, 0.72); }

.tv-gchan-head {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 0;
  background: none;
  border: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

.tv-gchan-text { display: flex; flex-direction: column; min-width: 0; }

.tv-gchan-name {
  font-weight: 650;
  font-size: 14.5px;
  color: var(--tg-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tv-gchan-where { font-size: 12.5px; color: var(--tg-ink-mute); }

.tv-gprog {
  display: flex;
  align-items: baseline;
  gap: 8px;
  width: 100%;
  margin-top: 6px;
  padding: 7px 9px;
  background: rgba(11, 11, 18, 0.045);
  border: 0;
  border-radius: 12px;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  flex-wrap: wrap;
}

.tv-gprog:hover { background: rgba(11, 11, 18, 0.09); }

.tv-gprog-title {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--tg-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1 1 12ch;
  min-width: 0;
}

.tv-gprog-when { font-size: 12px; color: var(--tg-ink-mute); white-space: nowrap; }
.tv-gprog-sub { font-size: 12px; color: var(--tg-ink-mute); flex: 1 1 100%; }

/* The guide's line on a grid card: what is on this channel, in the
   card's own facts, under the reach and quality it already carries. */
.tv-onprog { margin-top: 5px; font-size: 12px; }

/* A grid, not a flex row: a flex row of nowrap spans reports its full
   unwrapped width as the card's minimum content size, the card (a grid
   item with automatic minimum width) grows past its track to fit it,
   and the time at the end is clipped by the card edge instead of the
   title truncating. A minmax(0, 1fr) title track keeps the line's
   minimum honest, so the card stays in its track and the title takes
   the ellipsis. Measured at 390 before the fix: card 415 wide in a
   342 track, "until" time cut mid-render. */
.tv-onprog-line {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}

.tv-onprog-k { font-weight: 700; color: #067647; white-space: nowrap; }
.tv-onprog-line + .tv-onprog-line .tv-onprog-k { color: var(--tg-ink); }

.tv-onprog-t {
  color: var(--tg-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.tv-onprog-w { color: var(--tg-ink-mute); white-space: nowrap; }

/* ── nothing here, and nothing yet ────────────────────────────────────────── */

.tv-empty {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: flex-start;
  padding: 20px 4px;
}
.tv-empty p { margin: 0; color: var(--tg-ink-soft); font-size: 15px; }
.tv-try { display: flex; gap: 8px; flex-wrap: wrap; }

.tv-card.skeleton {
  cursor: default;
  pointer-events: none;
  box-shadow: none;
  animation: tv-pulse 1.4s var(--tg-ease-out) infinite;
}
.tv-card.skeleton .tv-art { background: var(--tg-surface-3); }
.tv-card.skeleton .bar {
  height: 10px;
  border-radius: 5px;
  background: var(--tg-surface-3);
}
.tv-card.skeleton .bar + .bar { margin-top: 8px; width: 60%; }

@keyframes tv-pulse { 50% { opacity: 0.55; } }

@media (prefers-reduced-motion: reduce) {
  .tv-card.skeleton { animation: none; }
  .tv-card:hover { transform: none; }
  .tv-wait span { animation-duration: 3s; }
}

/* ── a phone ──────────────────────────────────────────────────────────────── */

@media (max-width: 640px) {
  .tv-grid { grid-template-columns: 1fr; }
  .tv-games-grid { grid-template-columns: 1fr; }
  .tv-guide-list { grid-template-columns: 1fr; }
  /* The controls wrap under the name rather than squeezing it to nothing. */
  .tv-screen-meta { align-items: flex-start; }
  .tv-now { flex: 1 0 100%; }
  .tv-volume { width: 72px; }
  .tv-screen { border-radius: var(--tg-r); }
}

/* No blur, no glass: solid fills in the same shapes. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .tv-screen-meta { background: var(--tg-dark-surface-2); }
  .tv-selects select { background: #fff; }
  .tv-card,
  .tv-grid > .tv-card:nth-child(5n + 2),
  .tv-grid > .tv-card:nth-child(5n + 3),
  .tv-grid > .tv-card:nth-child(5n + 4),
  .tv-grid > .tv-card:nth-child(5n + 5),
  .tv-games-grid > .tv-card:nth-child(5n + 2),
  .tv-games-grid > .tv-card:nth-child(5n + 3),
  .tv-games-grid > .tv-card:nth-child(5n + 4),
  .tv-games-grid > .tv-card:nth-child(5n + 5),
  .tv-gchan,
  .tv-guide-list > .tv-gchan:nth-child(5n + 2),
  .tv-guide-list > .tv-gchan:nth-child(5n + 3),
  .tv-guide-list > .tv-gchan:nth-child(5n + 4),
  .tv-guide-list > .tv-gchan:nth-child(5n + 5) { background: #fff; }
  .tv-card[aria-current='true'],
  .tv-card:focus-visible { background: rgba(122, 60, 255, 0.13); }
}
