/* Shared chrome for both receiver surfaces: the Cast receiver
   (/cast/receiver, Chromecast) and the TV app (/cast/tv, Tizen/webOS).
   The markup these style is built by receiver-core.js, so the two entry
   points cannot drift apart.

   ENGINE FLOOR: Chromium 55. Set by `async`/`await` in receiver-core.js;
   nothing below that runs the receiver at all. But everything ABOVE that
   floor must keep working, and TV engines are frozen at their model year:
   Samsung 2018-2022 ship Chromium 56-85 and LG 2020-2021 ship 68-79 (LG
   2019 and older sit at 53, below the floor, and never ran at all). A phone,
   a Chromecast and a 2024 set all render this file fine — a 2021 Samsung
   does not, and nobody notices until a family is in front of one.

   Two properties in particular are NOT available here and must not come
   back (tools/cast_checks.py fails the build on them):
     * `inset` shorthand  — Chromium 87. Shipped 2026-08-13, found 2026-08-21:
       an unsupported `inset` leaves `position:absolute` with auto offsets, so
       every full-bleed layer SHRINK-WRAPS. #playerWrap collapsed to the
       iframe's 300x150 default — a postage-stamp video in the corner of a
       1080p TV — and #shield collapsed to 0x0, re-opening the YouTube
       watermark escape it exists to block. Write the four longhands.
     * flex `gap`         — Chromium 84 (grid `gap` is older, so @supports
       cannot tell them apart). Use margins on the children.
   Chromium 55 also predates `conic-gradient` (69), hence the @supports
   guard under #candy. */
:root {
  --orange: #F7A61E;
  --blue: #64A5F4;
  --cream: #F8F3E9;
  --ink: #4B4453;
  --ink-soft: #7A7386;
}
html, body { margin: 0; padding: 0; width: 100vw; height: 100vh; background: #000; overflow: hidden; font-family: "Nunito", -apple-system, Roboto, sans-serif; }
/* Always laid out: the idle overlay (later in the DOM) covers it. A
   display:none wrapper would stop the YouTube iframe initialising and
   defeat the player warm-up in receiver-core.js. */
#playerWrap { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: #000; }
/* YouTube's iframe API replaces this div with an iframe that keeps the id;
   absolute + explicit size means the video fills the wrapper whatever the
   API decides to put in the width/height attributes. */
#player { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0; }
/* Swallows every pointer event before it can reach the embed's escape
   hatches (watermark, title, related). Sits above the player, below the
   loading overlay (z 5) and the idle/pairing screens. */
#shield { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 3; }

/* ---------- idle screen ---------- */
#idle {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  background: var(--cream);
}
#wordmark {
  font-family: "Fredoka", "Nunito", sans-serif; font-weight: 700;
  font-size: 11vw; line-height: 1; letter-spacing: 0.01em;
  animation: breathe 5s ease-in-out infinite;
  margin-bottom: 4.5vh;   /* stands in for #idle's flex gap */
}
#wordmark .bo { color: var(--orange); }
#wordmark .nbon { color: var(--blue); }
@keyframes breathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.02); } }
#hint {
  background: rgba(255, 255, 255, 0.75);
  border-radius: 3vh; padding: 2.2vh 4.5vh; text-align: center;
}
#hint .primary { font-size: 3vw; font-weight: 700; color: var(--ink); }
#hint .secondary { font-size: 1.9vw; font-weight: 600; color: var(--ink-soft); margin-top: 0.8vh; }
/* The cast-button glyph inline in copy: sized to the surrounding text, drawn
   in the same ink so it reads as "this symbol", not decoration. */
.castglyph { display: inline-block; width: 1.15em; height: 1.15em; vertical-align: -0.2em; fill: currentColor; margin-left: 0.15em; }
#pairCode .pairedmark { color: #3FA26B; }
/* Unpair hint on the idle screen: present but quiet — it is for the one
   evening a family needs it, not for every glance at the TV. */
#forgetHint { position: absolute; bottom: 5vh; left: 0; right: 0; text-align: center;
  font-size: 1.6vw; font-weight: 600; color: var(--ink-soft); opacity: 0.85; }
#forgetHint .sub { display: block; font-size: 1.25vw; margin-top: 0.6vh; }
#forgetHint .key { display: inline-block; padding: 0.1vh 0.9vw; border-radius: 0.8vh;
  background: #fff; border: 0.25vh solid var(--orange); color: var(--ink); font-weight: 700; margin: 0 0.2vw; }
/* "Another phone? Code …" under the idle card (transport-relay.js): quieter
   than the card, but the code itself must read from the sofa. Empty on Cast
   and until the relay answers, and then it takes no room at all. */
#addPhone { margin-top: 3.5vh; text-align: center; color: var(--ink-soft);
  display: flex; flex-direction: row; align-items: center; justify-content: center; }
#addPhone:empty { display: none; }
/* The add code's QR, left of its line: smaller than the pairing one, still
   one to point a phone at from the sofa. */
#addPhone .addqr { flex: 0 0 auto; margin-right: 2.2vw; }
#addPhone .addqr .qr { width: 17vh; height: 17vh; border-radius: 1.2vh; }
#addPhone .addqr + .addtext { text-align: left; max-width: 66vw; }
#addPhone .primary { font-size: 1.9vw; font-weight: 700; }
#addPhone .secondary { font-size: 1.35vw; font-weight: 600; margin-top: 0.6vh; }
#addPhone .code {
  display: inline-block; margin: 0 0.3vw; padding: 0.2vh 0.9vw 0.2vh calc(0.9vw + 0.12em);
  font-family: "Fredoka", "Nunito", sans-serif; font-size: 2.6vw; letter-spacing: 0.12em;
  color: var(--orange); background: #fff; border-radius: 1.2vh; vertical-align: -0.1em;
}
/* While the "can't reach YouTube" card is up, adding a phone is not the
   point, and the line would sit where the card grows into. */
#netHint.visible ~ #idle #addPhone { display: none; }
#pairing .pairednote { max-width: 70vw; text-align: center; font-size: 2.4vw; font-weight: 700; color: var(--ink); line-height: 1.35; }
#pairing .pairednote .sub { font-size: 1.6vw; font-weight: 600; color: var(--ink-soft); margin-top: 1vh; }

/* floating background shapes — edges only, center stays clean */
.shape { position: absolute; opacity: 0.16; animation: floaty 8s ease-in-out infinite; }
@keyframes floaty { 0%, 100% { transform: translateY(0) rotate(0deg); } 50% { transform: translateY(-1.6vh) rotate(3deg); } }
.s1 { left: 6vw;  top: 10vh; width: 9vw;  animation-delay: 0s; }
.s2 { right: 7vw; top: 14vh; width: 7vw;  animation-delay: -2.5s; }
.s3 { left: 12vw; bottom: 12vh; width: 6vw; animation-delay: -4s; }
.s4 { right: 12vw; bottom: 10vh; width: 8vw; animation-delay: -1.5s; }
.s5 { left: 42vw; top: 6vh; width: 4.5vw; animation-delay: -3s; }
.s6 { right: 30vw; bottom: 6vh; width: 4vw; animation-delay: -5.5s; }

/* ---------- loading overlay (the ONLY loader ever visible) ---------- */
#loading {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0;
  display: none; flex-direction: column;
  align-items: center; justify-content: center;
  background: var(--cream); z-index: 5;
}
#candy {
  width: 11vh; height: 11vh; border-radius: 50%;
  margin-bottom: 4vh;   /* stands in for #loading's flex gap */
  /* Flat disc below the engine floor for conic-gradient (Chromium 69), or the
     spinner is an invisible transparent circle on the oldest sets. It has to
     be an @supports guard and not the usual two-declaration fallback: the
     conic value contains var(), and a declaration containing var() is parsed
     as VALID and only fails at computed-value time — where it computes to
     `unset`, discarding the earlier declaration instead of losing to it. */
  background: var(--orange);
  animation: candySpin 1.15s cubic-bezier(0.45, 0.1, 0.55, 0.9) infinite;
  position: relative;
}
@supports (background: conic-gradient(#000, #000)) {
  #candy { background: conic-gradient(var(--orange) 0 25%, var(--blue) 25% 50%, var(--orange) 50% 75%, var(--blue) 75% 100%); }
}
#candy::after {
  content: ""; position: absolute; top: 30%; right: 30%; bottom: 30%; left: 30%;
  background: #fff; border-radius: 50%;
}
@keyframes candySpin { to { transform: rotate(360deg); } }
#loadingTitle {
  max-width: 70vw; font-size: 2.4vw; font-weight: 700; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: center;
}
/* "This TV can't reach YouTube" — a card low on the screen, above the loading
   overlay (z 5) so it reads over the spinner a stalled load leaves up.
   Appears when a load sits 20 s with no picture or the embed refuses video
   after video; gone the moment anything plays. Longhands and margins only:
   engine floor, see tools/cast_checks.py. */
#netHint {
  position: absolute; left: 10vw; right: 10vw; bottom: 8vh;
  display: none; text-align: center;
  background: rgba(255, 255, 255, 0.94); border-radius: 3vh; padding: 2.2vh 4vh;
  z-index: 7; box-shadow: 0 1vh 4vh rgba(0, 0, 0, 0.22);
}
#netHint.visible { display: block; }
#netHint .primary { font-size: 2.6vw; font-weight: 700; color: var(--ink); }
#netHint .secondary { font-size: 1.8vw; font-weight: 600; color: var(--ink-soft); margin-top: 0.8vh; }
#netHint .tertiary { font-size: 1.4vw; font-weight: 600; color: var(--ink-soft); margin-top: 1vh; opacity: 0.85; }
/* One advice block at a time: the plain one, or the VPN one once the TV's own
   exit country says a VPN is what is missing (receiver-core.js
   askExitCountry). */
#netHint .advice-vpn { display: none; }
#netHint.vpnAdvice .advice-plain { display: none; }
#netHint.vpnAdvice .advice-vpn { display: block; }
/* The card grows upward from its bottom edge, and the VPN advice runs a line
   longer than the plain one: with the real fonts it reached the idle screen's
   "pick a video" card (2026-09-21). While the card is up, the idle content
   sits higher instead: padding on a centred flex column moves its children
   by half of it. #forgetHint and the shapes are absolute and stay put. The
   sibling selector is why receiver-core.js builds #netHint before #idle;
   tools/cast_smoke.py measures the gap. */
#netHint.visible ~ #idle { padding-bottom: 22vh; }

/* "Sound is off: this TV's browser won't turn it on by itself" (receiver-core.js
   lockSound), or `.video`, it won't even start the video muted. Top centre,
   clear of the pause chip and the track at the bottom, and of #netHint, which
   is also a bottom card. z 7 like #netHint: the `.video` form has to sit over
   the loading cover (5), which is still up when nothing will start. */
#soundHint {
  position: absolute; left: 12vw; right: 12vw; top: 5vh;
  display: none; text-align: center; pointer-events: none;
  background: rgba(255, 255, 255, 0.94); border-radius: 3vh; padding: 2vh 4vh;
  z-index: 7; box-shadow: 0 1vh 4vh rgba(0, 0, 0, 0.22);
}
#soundHint.visible { display: block; }
#soundHint .primary { font-size: 2.2vw; font-weight: 700; color: var(--ink); }
#soundHint .secondary { font-size: 1.6vw; font-weight: 600; color: var(--ink-soft); margin-top: 0.8vh; }
#soundHint .tertiary { font-size: 1.25vw; font-weight: 600; color: var(--ink-soft); margin-top: 0.8vh; opacity: 0.85; }
#soundHint .p-video, #soundHint .t-video { display: none; }
#soundHint.video .p-sound, #soundHint.video .t-sound { display: none; }
#soundHint.video .p-video, #soundHint.video .t-video { display: block; }

/* ---------- good night (receiver-core.js showGoodnight) ----------
   Today's watch time ran out while the TV was carrying on without the phone.
   Covers the stopped player and the idle screen; below the pairing screen
   (8) and the hatch (9). No video is loaded, so the remote keys do nothing. */
#goodnight {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 6;
  display: none; flex-direction: column;
  align-items: center; justify-content: center; text-align: center;
  background: #1E1B3A;
}
#goodnight .moon { width: 16vh; height: 16vh; fill: #FFE7A8; margin-bottom: 4vh; animation: breathe 5s ease-in-out infinite; }
#goodnight .primary { font-family: "Fredoka", "Nunito", sans-serif; font-size: 4vw; font-weight: 700; color: #FFF6E0; }
#goodnight .secondary { font-size: 2.6vw; font-weight: 700; color: #FFE7A8; margin-top: 1.2vh; }
#goodnight .tertiary { font-size: 1.7vw; font-weight: 600; color: rgba(255, 246, 224, 0.75); margin-top: 2.5vh; max-width: 70vw; }
#goodnight .parent { position: absolute; bottom: 5vh; left: 0; right: 0; font-size: 1.4vw; font-weight: 600; color: rgba(255, 246, 224, 0.55); }

/* ---------- pairing screen (TV app only, owned by transport-relay.js) ----------
   Shown until this TV is bound to a household; never appears on Cast. */
#pairing {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0;
  display: none; flex-direction: column;
  align-items: center; justify-content: center;
  background: var(--cream); z-index: 8;
}
#pairing > * + * { margin-top: 3.5vh; }   /* stands in for the flex gap */
/* "Point your phone's camera at the QR code", the ways to type the code under
   it. A line that runs long (a TV's fallback font is wider than Nunito)
   wraps in from the screen's edges, never at them. */
#pairing .lead { max-width: 88vw; font-size: 2.6vw; font-weight: 700; color: var(--ink); text-align: center; }
#pairing .lead .sub { display: block; font-size: 1.6vw; font-weight: 600; color: var(--ink-soft); margin-top: 0.8vh; }
/* Under the QR and the code: what the QR does for a phone without Bonbon, and
   that any Wi-Fi will do. The screen's small print, quieter than the lead. */
#pairing .pairfoot { max-width: 84vw; text-align: center; font-size: 1.45vw; font-weight: 600; color: var(--ink-soft); line-height: 1.35; }
#pairing .pairfoot .sub { display: block; font-size: 1.2vw; margin-top: 0.4vh; opacity: 0.85; }
.nw { white-space: nowrap; }
#pairCode {
  font-family: "Fredoka", "Nunito", sans-serif; font-weight: 700;
  font-size: 12vw; line-height: 1; letter-spacing: 0.14em;
  color: var(--orange); background: #fff;
  border-radius: 3vh; padding: 2.5vh 5vw 2.5vh calc(5vw + 0.14em);
  box-shadow: 0 1.2vh 3vh rgba(75, 68, 83, 0.12);
}
/* Waiting for the code: the loading candy in the code's white box, which
   keeps its size (a code-high box) so nothing jumps when the code lands. */
#pairCode.waiting { width: 3.6em; height: 1em; position: relative; }
.paircandy {
  position: absolute; top: 50%; left: 50%;
  width: 11vh; height: 11vh; margin: -5.5vh 0 0 -5.5vh; border-radius: 50%;
  box-sizing: border-box;
  /* Below conic-gradient (Chromium 69) a flat disc would turn invisibly: a
     two-colour ring shows the spin there. See #candy for why @supports. */
  border: 2.2vh solid var(--blue); border-top-color: var(--orange); border-bottom-color: var(--orange);
  background: #fff;
  animation: candySpin 1.15s cubic-bezier(0.45, 0.1, 0.55, 0.9) infinite;
}
@supports (background: conic-gradient(#000, #000)) {
  .paircandy { border: 0; background: conic-gradient(var(--orange) 0 25%, var(--blue) 25% 50%, var(--orange) 50% 75%, var(--blue) 75% 100%); }
  .paircandy::after {
    content: ""; position: absolute; top: 30%; right: 30%; bottom: 30%; left: 30%;
    background: #fff; border-radius: 50%;
  }
}
#pairStatus { font-size: 1.7vw; font-weight: 600; color: var(--ink-soft); min-height: 2.4vh; }
/* The code as a QR beside it (transport-relay.js, qr.js): big enough to
   read from the sofa — 40vh is 29 modules plus the quiet zone at ~11 px each
   on a 1080p set — black on white, the quiet zone inside the SVG. The code
   gets a little smaller to make room; the pair stays centred. */
#pairRow { display: flex; flex-direction: row; align-items: center; justify-content: center; }
#pairQr { display: flex; flex-direction: column; align-items: center; margin-right: 5vw; }
#pairQr .qrbox {
  width: 40vh; height: 40vh; background: #fff; border-radius: 2vh; overflow: hidden;
  box-shadow: 0 1.2vh 3vh rgba(75, 68, 83, 0.12);
}
.qr { display: block; width: 100%; height: 100%; }
#pairing.withqr #pairCode { font-size: 9.5vw; }

/* ---------- on-TV playback feedback (built by receiver-core.js) ----------
   Reflects phone commands on the TV: pause chip stays while paused, the
   track appears on pause/seek. Sits above the shield (3), below loading (5).
   pointer-events none: the shield owns all input. */
#status {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 4;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  opacity: 0; transition: opacity 0.25s ease;
  pointer-events: none;
}
#status.visible { opacity: 1; }
#statusGlyph {
  width: 11vh; height: 11vh; border-radius: 50%;
  background: rgba(255, 255, 255, 0.94);
  border: 0.6vh solid var(--orange);
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 1vh 4vh rgba(0, 0, 0, 0.35);
}
#statusGlyph svg { width: 55%; height: 55%; fill: var(--ink); }
#statusBar {
  position: absolute; left: 6vw; right: 6vw; bottom: 7vh;
  display: flex; align-items: center;
}
#statusTrack {
  position: relative; flex: 1; height: 1.4vh; border-radius: 0.7vh;
  background: rgba(255, 255, 255, 0.35);
}
#statusFill {
  position: absolute; left: 0; top: 0; bottom: 0; width: 0;
  border-radius: 0.7vh; background: var(--orange);
}
#statusDot {
  position: absolute; top: 50%; left: 0;
  width: 3.2vh; height: 3.2vh; border-radius: 50%;
  transform: translate(-50%, -50%);
  background: #fff; border: 0.5vh solid var(--orange);
  box-shadow: 0 0.4vh 1.5vh rgba(0, 0, 0, 0.35);
}
#statusTime {
  margin-left: 2vw;   /* stands in for #statusBar's flex gap */
  font-size: 2.6vh; font-weight: 700; color: #fff;
  text-shadow: 0 0.3vh 1vh rgba(0, 0, 0, 0.6);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* ---------- forget & re-pair confirm (hatch.js, TV surface only) ---------- */
#hatch {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 9; display: none;
  align-items: center; justify-content: center;
  background: rgba(20, 16, 24, 0.55);
}
#hatch .box {
  background: var(--cream); border-radius: 3vh; padding: 4vh 5vw;
  max-width: 70vw; text-align: center; box-shadow: 0 2vh 6vh rgba(0,0,0,0.4);
}
#hatch .title { font-size: 3vw; font-weight: 700; color: var(--ink); }
#hatch .sub { font-size: 1.7vw; font-weight: 600; color: var(--ink-soft); margin-top: 1.5vh; line-height: 1.4; }
#hatch .keys { font-size: 1.6vw; color: var(--ink); margin-top: 2vh; }
#hatch .key {
  display: inline-block; padding: 0.3vh 1.2vw; border-radius: 1vh;
  background: #fff; border: 0.3vh solid var(--orange); font-weight: 700; margin: 0 0.3vw;
}
