/* Layout and mechanics shared by both themes. Colors, type and decoration
   live in modern.css (the default) and retro.css (the TV Guide mode); each
   theme also sets the sizing tokens used here. */

:root {
  --gutter: 0px;
  --channel-w: 220px;
  --row-h: 64px;
  --row-gap: 3px;
  --header-h: 40px;
  --screen-aspect: 16 / 9;
  /* The preview takes most of the top half, but always leaves the guide room
     for about three rows (the 430px covers the bars, filters and guide header). */
  --monitor-h: clamp(150px, min(58vh - 110px, 100vh - 430px), 600px);
}

/* Guide categories and platforms each carry their own color. */
[data-category="gaming"] { --cat: #3ddc84; }
[data-category="chatting"] { --cat: #ff9f1c; }
[data-category="irl"] { --cat: #b5e550; }
[data-category="news"] { --cat: #ff5a5f; }
[data-category="music"] { --cat: #c77dff; }
[data-category="asmr"] { --cat: #5ee6f7; }
[data-category="sports"] { --cat: #ffd23f; }
[data-category="creative"] { --cat: #ff70a6; }
[data-category="always-on"] { --cat: #9aa5b1; }
[data-category="gambling"] { --cat: #3fbfa8; }

[data-platform="twitch"] { --platform: #9146ff; --platform-ink: #c3a2ff; --platform-on: #fff; }
[data-platform="youtube"] { --platform: #ff0033; --platform-ink: #ff8595; --platform-on: #fff; }
[data-platform="kick"] { --platform: #53fc18; --platform-ink: #79ff4a; --platform-on: #06210a; }

*, *::before, *::after { box-sizing: border-box; }

html { color-scheme: dark; }

body {
  margin: 0;
  -webkit-font-smoothing: antialiased;
}

button { font: inherit; color: inherit; }

[hidden] { display: none !important; }

.app {
  display: grid;
  grid-template-rows: auto auto minmax(240px, 1fr) auto;
  height: 100dvh;
  max-width: 1800px;
  margin: 0 auto;
}

/* ───────────── Marquee: brand bar, monitor, info panel ───────────── */

.marquee {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas:
    "brand brand"
    "monitor info";
}

.brandbar {
  grid-area: brand;
  display: flex;
  align-items: center;
  min-width: 0;
}

.logo { margin: 0; white-space: nowrap; }

.tagline {
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.brandbar-right {
  margin-left: auto;
  display: flex;
  align-items: center;
}

.demo-bug, .clock { white-space: nowrap; }

/* The monitor sits above the retro CRT overlay: live players may refuse to
   autoplay when something covers them. */
.monitor {
  grid-area: monitor;
  align-self: start;
  position: relative;
  z-index: 60;
}

.screen {
  position: relative;
  height: var(--monitor-h);
  aspect-ratio: var(--screen-aspect);
  overflow: hidden;
  background: #000;
}

/* Title card shown before (or instead of) a live player. */
.screen-card {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  justify-items: center;
  text-align: center;
}

.screen-thumb {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  background: #000;
}

.screen-name { overflow-wrap: anywhere; }

/* The live player covers everything on the screen. The platform's player API
   puts its own iframe inside. */
.screen-embed {
  position: absolute;
  inset: 0;
  z-index: 6;
  background: #000;
}

.screen-embed iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

.bug { position: absolute; z-index: 2; }

/* Channel-change transition, replayed every time the screen switches streams.
   Each theme gives it an animation; it removes itself when that ends. */
.static {
  position: absolute;
  inset: 0;
  z-index: 5;
  opacity: 0;
  pointer-events: none;
}

.no-signal {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
}

/* Info panel */
.info {
  grid-area: info;
  align-self: center;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  min-width: 0;
}

.info p { margin: 0; }

.info-actions { display: flex; flex-wrap: wrap; align-items: baseline; }

.info-action {
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

.info-tags { display: flex; flex-wrap: wrap; }

.info-name {
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.info-title {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

.info-stats { display: flex; flex-wrap: wrap; }
.info-next:empty { display: none; }
.info-notice { margin: 0; }
/* Stacked, one per platform, all as wide as the widest. */
.watch-links { display: grid; width: max-content; max-width: 100%; }
.watch-btn { justify-content: center; text-align: center; text-decoration: none; }

.info[data-empty="true"] :is(.info-tags, .info-stats, .info-next, .watch-links, .info-actions) { display: none; }

/* ───────────── Filters and toggles ───────────── */

.filter-bar { min-width: 0; }

/* One strip that scrolls sideways when it doesn't fit, so it never eats rows. */
.filters {
  display: flex;
  align-items: center;
  overflow-x: auto;
  scrollbar-width: none;
}

.filters::-webkit-scrollbar { display: none; }

.filter-group {
  display: flex;
  flex: none;
}

.chip {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  border: 0;
  cursor: pointer;
}

.chip:is([data-category="all"], [data-category="favorites"], [data-category="trending"]) .swatch { display: none; }

.chip--icon svg { display: block; width: 16px; height: 16px; }

/* Every open popover gets a full-screen ::backdrop behind it. It's
   transparent, but Chrome's visibility check (which Twitch's player uses)
   counts it as covering the player, so Twitch pauses. Neither popover here
   needs one. */
.search-suggestions::backdrop,
.settings-panel::backdrop { display: none; }

/* ───────────── Settings menu ───────────── */

/* A native popover (in the top layer, above the player and CRT overlay),
   placed under the cog by main.js rather than the browser's default centering. */
.settings-panel {
  position: fixed;
  inset: auto;
  margin: 0;
  width: min(320px, calc(100vw - 16px));
  border: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.settings-heading { margin: 0; }

.setting {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  cursor: pointer;
}

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

/* Each platform's switch carries its color. */
.platform-dot { display: inline-block; vertical-align: middle; background: var(--platform); }
.setting input[role="switch"]:disabled { cursor: not-allowed; opacity: 0.5; }

/* A choice of a few, drawn as one segmented control (Appearance). The radio
   buttons stay over their labels, invisible, so arrow keys still work. */
.segmented { display: inline-flex; flex: none; }
.segmented label { position: relative; cursor: pointer; }
.segmented input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
.segmented span { display: block; white-space: nowrap; }

/* Checkboxes drawn as on/off switches; each theme styles the track and knob. */
.setting input[role="switch"] {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  flex: none;
  margin: 0;
  cursor: pointer;
}

.setting input[role="switch"]::before {
  content: "";
  position: absolute;
  transition: transform 0.15s;
}

/* ───────────── Search ───────────── */

.search { position: relative; display: flex; align-items: center; }

.search-icon {
  position: absolute;
  pointer-events: none;
}

.search-input {
  appearance: none;
  -webkit-appearance: none;
  margin: 0;
  border: 0;
  font: inherit;
  color: inherit;
}

.search-input::-webkit-search-cancel-button { -webkit-appearance: none; }

/* Suggestions: a native popover (in the top layer, above the player and the
   CRT overlay), placed under the search box by search.js. */
.search-suggestions {
  position: fixed;
  inset: auto;
  margin: 0;
  border: 0;
  max-height: min(440px, 70vh);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.suggestion-heading { margin: 0; }

.suggestion {
  display: flex;
  flex-direction: column;
  min-width: 0;
  cursor: pointer;
}

.suggestion-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.suggestion-meta {
  display: flex;
  align-items: baseline;
  white-space: nowrap;
  overflow: hidden;
}

.suggestion-platforms { display: inline-flex; flex: none; gap: 6px; }
.suggestion-what { overflow: hidden; text-overflow: ellipsis; }

.chip--search .search-icon { position: static; flex: none; }

/* The language dropdown, dressed as a chip. */
.chip--select {
  position: relative;
  flex: none;
  padding: 0;
}

.chip--select select {
  appearance: none;
  -webkit-appearance: none;
  margin: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  cursor: pointer;
}

.chip--select::after {
  position: absolute;
  top: 50%;
  translate: 0 -50%;
  pointer-events: none;
}

/* ───────────── The guide grid ───────────── */

.guide {
  position: relative;
  display: grid;
  grid-template-rows: var(--header-h) minmax(0, 1fr);
  min-height: 0;
}

.guide-header {
  display: grid;
  grid-template-columns: var(--channel-w) minmax(0, 1fr);
  padding-inline: var(--gutter);
}

.date-cell {
  display: flex;
  align-items: center;
  white-space: nowrap;
  overflow: hidden;
}

.slots { position: relative; }

.slot {
  position: absolute;
  top: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  white-space: nowrap;
}

.guide-body {
  position: relative;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
}

.guide-body::-webkit-scrollbar { display: none; }

.row {
  position: relative;
  display: grid;
  grid-template-columns: var(--channel-w) minmax(0, 1fr);
  height: var(--row-h);
  padding: 0 var(--gutter) var(--row-gap);
}

/* The invisible button over the whole row that picks the stream. */
.row-pick {
  position: absolute;
  inset: 0 var(--gutter) var(--row-gap);
  z-index: 1;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

/* Quick Favorite and Block, at the right of the channel box, on hover. On
   touch screens there's no hover (and little room): the info panel has them. */
.row-actions {
  position: absolute;
  z-index: 2;
  top: calc((var(--row-h) - var(--row-gap)) / 2);
  left: calc(var(--gutter) + var(--channel-w) - var(--row-gap));
  translate: -100% -50%;
  display: flex;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s;
}

.row:hover .row-actions,
.row-actions:focus-within { opacity: 1; pointer-events: auto; }

@media (hover: none) {
  .row-actions { display: none; }
}

.row-action {
  display: grid;
  place-items: center;
  padding: 0;
  cursor: pointer;
}

.row-action svg {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.row-action[aria-pressed="true"] svg { fill: currentColor; }

.channel {
  position: relative;
  display: grid;
  align-items: center;
  margin-right: var(--row-gap);
  min-width: 0;
}

.ch-name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Every platform a stream is live on. Phones only have room for the
   most-watched one and a count of the rest ("Twitch +2"). */
.ch-platform { white-space: nowrap; }
.ch-more { display: none; }

.ch-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.programs { position: relative; }

.cell {
  container-type: inline-size;
  position: absolute;
  top: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  overflow: hidden;
}

/* Category marker on the left edge of each program; themes give it a shape. */
.cell::before {
  content: "";
  position: absolute;
  background: var(--cat, transparent);
}

.cell-title {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cell-meta {
  display: flex;
  white-space: nowrap;
}

.cell-meta > :first-child { overflow: hidden; text-overflow: ellipsis; }
.cell-viewers { flex: none; }
.trend, .cell-mature { flex: none; }

.cell-arrow {
  position: absolute;
  top: 50%;
  translate: 0 -50%;
}

@container (max-width: 110px) { .cell-meta { display: none; } }
@container (max-width: 44px) { .cell-title { visibility: hidden; } }

.now-line {
  position: absolute;
  top: var(--header-h);
  bottom: 0;
  left: calc(var(--gutter) + var(--channel-w) + (100% - var(--channel-w) - 2 * var(--gutter)) * var(--now, 0));
  width: 2px;
  margin-left: -1px;
  pointer-events: none;
  z-index: 2;
}

.now-line::before {
  content: "";
  position: absolute;
  top: 0;
}

.empty {
  margin: 0;
  padding: 40px 20px;
  text-align: center;
}

.toast {
  position: fixed;
  left: 50%;
  bottom: 28px;
  z-index: 70;
  /* Its own width up to the screen's (centring it with left: 50% would
     otherwise squeeze it into half the screen). */
  width: max-content;
  max-width: calc(100vw - 32px);
  opacity: 0;
  transform: translate(-50%, 16px);
  transition: opacity 0.2s, transform 0.2s;
  pointer-events: none;
}

.toast.show { opacity: 1; transform: translate(-50%, 0); }

/* A notice with something to do ("Watch", "Undo") can be clicked. */
.toast.show.has-action { pointer-events: auto; }

.toast-action {
  margin-left: 12px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

/* The listings may be out of date (a platform, or the server, isn't answering). */
.stale-note { margin: 0; }

/* ───────────── Multiview ───────────── */

/* It takes the place of the guide and the single player (and the search box
   and sound switch that go with them), between the brand bar and the footer.
   Settings goes too: its menu would open over a player, which Twitch doesn't
   allow. Both are a click away, back in the guide. */
.app.is-multiview { grid-template-rows: auto minmax(0, 1fr) auto; }
.app.is-multiview :is(.monitor, .info, .filter-bar, .guide, .search, .chip--sound, #settings-toggle) { display: none; }

.multiview { min-height: 0; overflow-y: auto; }

/* Two across; two rows once there are three or four places. Rows share the
   height, but never shrink a player below Twitch's minimum (300 tall): in a
   short window the grid scrolls a little instead. */
.mv-grid {
  display: grid;
  height: 100%;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-rows: minmax(auto, 1fr);
}
.mv-grid:is([data-slots="3"], [data-slots="4"]) { grid-template-rows: repeat(2, minmax(auto, 1fr)); }

/* A caption bar, then the player under it (nothing may sit on a player). */
.mv-tile {
  display: grid;
  grid-template-rows: auto minmax(300px, 1fr);
  min-width: 0;
}

.mv-caption { display: flex; align-items: center; min-width: 0; white-space: nowrap; }
.mv-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.mv-sound { margin-left: auto; }

.mv-button {
  display: grid;
  flex: none;
  place-items: center;
  padding: 0;
  cursor: pointer;
}

.mv-button svg { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

.mv-screen { position: relative; overflow: hidden; background: #000; }
.mv-thumb { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
.mv-embed { position: absolute; inset: 0; z-index: 1; background: #000; }
.mv-embed iframe { display: block; width: 100%; height: 100%; border: 0; }

/* Shown on the still when a tile's too small for its platform's player. */
.mv-note { display: none; }
.mv-screen[data-playable="false"] .mv-note {
  display: block;
  position: absolute;
  left: 50%;
  top: 50%;
  max-width: 90%;
  translate: -50% -50%;
  text-align: center;
}

.mv-add { place-content: center; justify-items: center; grid-template-rows: auto auto; text-align: center; }
.mv-add-hint { max-width: 340px; margin: 0; }
.mv-add-hint svg { display: inline-block; vertical-align: -3px; fill: none; stroke: currentColor; stroke-width: 2; }

/* Notices go over the brand bar in multiview, so they never cover a player. */
.app.is-multiview ~ .toast { top: 10px; bottom: auto; }

/* ───────────── Footer, and agreeing to the terms ───────────── */

.site-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 2px 16px;
}

.footer-links { display: flex; flex-wrap: wrap; gap: 2px 14px; }

/* Over everything (the guide can't be used until it's answered). */
.agreement:not([hidden]) {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  place-items: center;
  padding: 16px;
  overflow-y: auto;
}

.agreement-card { width: min(440px, 100%); }

/* ───────────── Your channels (in Settings) ───────────── */

.channel-list summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  cursor: pointer;
  list-style: none;
}

.channel-list summary::-webkit-details-marker { display: none; }
/* A chevron drawn with borders (not every font has arrow characters): right, then down when open. */
.channel-list summary::after {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  margin-right: 3px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 0.15s;
}
.channel-list[open] summary::after { transform: rotate(45deg); }
.channel-count { margin-left: auto; }

.channel-list ul { margin: 0; padding: 0; list-style: none; }

.channel-list li {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.channel-list .channel-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.channel-list .ch-feed { flex: none; }

.channel-remove {
  flex: none;
  margin-left: auto;
  padding: 0 4px;
  border: 0;
  background: none;
  cursor: pointer;
}

/* The row for sending your lists to another device: only its button does anything. */
.setting--transfer { cursor: default; }
.transfer-fallback { box-sizing: border-box; }

@media (max-width: 1000px) {
  .tagline { display: none; }
}

/* ───────────── Phones ───────────── */

@media (max-width: 760px) {
  .site-footer { justify-content: center; text-align: center; }

  /* No room for multiview on a phone. */
  .chip--multiview, #multiview-button, .row-action[data-action="multiview"] { display: none; }

  /* The screen across the full width (edge to edge, about 375×211 on most
     phones: big enough for YouTube's and Kick's players, though not Twitch's,
     which needs 400 across), a compact info panel under it, then the guide. */
  .marquee { grid-template-columns: minmax(0, 1fr); grid-template-areas: "brand" "monitor" "info"; }
  .screen { width: 100%; height: auto; }
  .info { align-self: start; }
  .info-label, .info-tags, .info-stats, .info-next { display: none; }
  .info-title { -webkit-line-clamp: 1; }
  .watch-links { display: flex; flex-wrap: wrap; width: auto; }

  .screen[data-playable="false"] .screen-hint {
    display: block;
    position: absolute;
    left: 50%;
    top: 50%;
    z-index: 7;
    translate: -50% -50%;
    white-space: nowrap;
  }
  .ch-platform .ch-feed ~ .ch-feed { display: none; }

  /* Search shrinks to its icon, and spreads across the top bar while in use. */
  .brandbar { position: relative; }
  .search-input { cursor: pointer; }
  .search:focus-within { position: absolute; inset: 0; z-index: 3; }
  .search:focus-within .search-input { flex: 1; cursor: text; }
  .ch-more { display: inline; }
  /* No room for the Sound button; the player's own mute button does the job
     (and the sound setting follows it). */
  .chip--sound { display: none; }
}

/* Shown on phones when a picked stream's player can't fit (see the phone layout). */
.screen-hint { display: none; }

/* A Twitch stream on a phone at least 400 wide gets a box tall enough for
   Twitch's 400×300 minimum, with the video letterboxed inside (preview.js,
   LETTERBOX_TWITCH_ON_WIDE_PHONES). */
.screen[data-letterbox="true"] { height: 300px; aspect-ratio: auto; }

@media (prefers-reduced-motion: reduce) {
  .static { display: none; }
}
