/*
 * ImageSignage webOS Player — styling mirrors the Android player's
 * theme exactly: same palette, same typography hierarchy, same
 * pulsing background glow, same responsive breakpoints.
 *
 * Palette (from android-player Theme.kt):
 *   Brand        #405189   BrandLight  #6B80B5   BrandDark  #2C3A66
 *   Success      #0AB39C   Danger      #F06548   Warning    #F7B84B
 *   Surface      #0F1117   SurfaceCard #181B24   SurfaceVar #1E2230
 *   OnSurface    #F3F3F9   OnSurfaceDim #878A99  Outline    #2E3244
 *
 * Pairing-screen background gradient: #080B14 → #0F1424 → #080B14.
 */

:root {
  --brand:         #405189;
  --brand-light:   #6B80B5;
  --brand-dark:    #2C3A66;
  --success:       #0AB39C;
  --danger:        #F06548;
  --warning:       #F7B84B;
  --info:          #299CDB;
  --surface:       #0F1117;
  --surface-card:  #181B24;
  --surface-var:   #1E2230;
  --on-surface:    #F3F3F9;
  --on-surface-dim:#878A99;
  --outline:       #2E3244;
  --outline-soft:  #252836;
  --white:         #FFFFFF;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body, #root { width: 100%; height: 100%; overflow: hidden; background: var(--surface); }

body {
  font-family: "Inter", "Segoe UI", -apple-system, BlinkMacSystemFont, "Helvetica Neue",
               Roboto, "Noto Sans", sans-serif;
  color: var(--on-surface);
  user-select: none;
  -webkit-user-select: none;
  cursor: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.mono { font-family: "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace; }

/* Hidden logo preload (composited onto QR via JS). */
#logo-col-source { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }

.screen {
  position: absolute;
  inset: 0;
  display: none;
}
.screen.visible { display: block; }

/* ─── Boot ──────────────────────────────────────────── */
#boot {
  display: none;
  background: linear-gradient(180deg, #080B14 0%, #0F1424 50%, #080B14 100%);
}
#boot.visible { display: flex; align-items: center; justify-content: center; }

.boot-content { display: flex; flex-direction: column; align-items: center; gap: 28px; }
.boot-logo    { width: 96px; height: 96px; opacity: 0.95; }

.spinner {
  width: 44px;
  height: 44px;
  border: 3px solid rgba(255, 255, 255, 0.08);
  border-top-color: var(--brand-light);
  border-radius: 50%;
  animation: spin 1s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ─── Pairing ───────────────────────────────────────── */

#pairing {
  background:
    radial-gradient(ellipse at center, rgba(64, 81, 137, 0.08), transparent 65%),
    linear-gradient(180deg, #080B14 0%, #0F1424 50%, #080B14 100%);
}

.pairing-bg-glow {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 60vmax;
  height: 60vmax;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(64, 81, 137, 0.55) 0%, transparent 70%);
  opacity: 0.06;
  transform: translate(-50%, -50%);
  animation: glow-pulse 3s cubic-bezier(0.65, 0, 0.35, 1) infinite alternate;
  pointer-events: none;
  filter: blur(40px);
}
@keyframes glow-pulse {
  from { transform: translate(-50%, -50%) scale(1); opacity: 0.05; }
  to   { transform: translate(-50%, -50%) scale(1.18); opacity: 0.12; }
}

.pairing-topbar {
  position: absolute;
  top: 28px;
  left: 44px;
  right: 44px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  z-index: 10;
}

.brand-block {
  display: flex;
  align-items: center;
  gap: 14px;
}

.brand-logo {
  height: 44px;
  max-width: 220px;
  object-fit: contain;
  filter: drop-shadow(0 2px 10px rgba(64, 81, 137, 0.4));
}

/* Version pill sitting beside the logo on the pairing screen. */
.version-chip {
  display: inline-flex;
  align-items: center;
  height: 26px;
  padding: 0 12px;
  font-family: "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.3px;
  color: var(--brand-light);
  background: rgba(107, 128, 181, 0.10);
  border: 1px solid rgba(107, 128, 181, 0.28);
  border-radius: 999px;
  line-height: 1;
}

.icon-btn {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: rgba(30, 34, 48, 0.5);
  border: 1px solid var(--outline);
  color: var(--on-surface-dim);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.2s ease, color 0.2s ease, transform 0.2s ease;
  outline: none;
}
.icon-btn svg { width: 22px; height: 22px; display: block; }
.icon-btn:hover,
.icon-btn:focus {
  background: rgba(64, 81, 137, 0.25);
  color: var(--white);
  transform: scale(1.05);
  border-color: var(--brand-light);
}

.pairing-main {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 110px 4% 50px;
  gap: 60px;
}

.pairing-left,
.pairing-right {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  max-width: 540px;
}
.pairing-left  { padding-right: 12px; }
.pairing-right { padding-left: 12px; }

.pairing-divider {
  width: 1px;
  height: 65%;
  background: linear-gradient(180deg, transparent 0%, var(--outline) 50%, transparent 100%);
  flex-shrink: 0;
}

.pairing-title {
  font-size: 42px;
  font-weight: 800;
  text-align: center;
  margin-bottom: 14px;
  letter-spacing: -0.5px;
  color: var(--white);
  background: linear-gradient(180deg, #ffffff 0%, #c8ccdc 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

.pairing-subtitle {
  font-size: 16px;
  color: var(--on-surface-dim);
  text-align: center;
  line-height: 1.6;
  max-width: 90%;
  margin: 0 auto 36px;
}

.code-card {
  width: 100%;
  margin-bottom: 16px;
  padding: 22px 28px;
  border-radius: 22px;
  background: linear-gradient(135deg, var(--surface-var) 0%, #14182a 100%);
  border: 1px solid var(--outline);
  box-shadow:
    0 14px 36px rgba(0, 0, 0, 0.45),
    0 1px 0 rgba(255, 255, 255, 0.04) inset;
  text-align: center;
  position: relative;
  overflow: hidden;
}
.code-card::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(64, 81, 137, 0.08) 0%, transparent 60%);
  pointer-events: none;
}

.code-label {
  font-size: 11px;
  font-weight: 700;
  color: var(--on-surface-dim);
  letter-spacing: 2.5px;
  margin-bottom: 10px;
  position: relative;
}

.code-value {
  font-size: 46px;
  font-weight: 900;
  letter-spacing: 14px;
  color: var(--white);
  line-height: 1.05;
  position: relative;
  text-shadow: 0 2px 10px rgba(64, 81, 137, 0.3);
}
.code-value.pin { letter-spacing: 10px; }

.conn-status {
  margin-top: 26px;
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 13px;
  font-weight: 500;
  text-align: center;
  justify-content: center;
  padding: 10px 18px;
  border-radius: 999px;
  background: rgba(30, 34, 48, 0.5);
  border: 1px solid var(--outline);
  transition: border-color 0.3s ease, color 0.3s ease, opacity 0.3s ease;
  animation: status-pulse 1.5s ease-in-out infinite alternate;
}
.conn-status.connecting { color: var(--on-surface-dim); }
.conn-status.success    { color: var(--success); border-color: rgba(10, 179, 156, 0.35); }
.conn-status.warning    { color: var(--warning); border-color: rgba(247, 184, 75, 0.35); }
.conn-status.error      { color: var(--danger);  border-color: rgba(240, 101, 72, 0.35); animation: none; opacity: 1; }

@keyframes status-pulse {
  from { opacity: 0.6; }
  to   { opacity: 1; }
}

/* Tiny circular spinner — matches Android's CircularProgressIndicator(size=14dp, strokeWidth=2dp). */
.status-spinner {
  width: 14px;
  height: 14px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: status-spin 0.9s linear infinite;
  display: inline-block;
  flex-shrink: 0;
}
@keyframes status-spin { to { transform: rotate(360deg); } }

/* QR side */
.qr-label {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  color: var(--on-surface-dim);
  font-weight: 500;
  margin-bottom: 18px;
}
.qr-icon { width: 18px; height: 18px; }

.qr-wrap {
  width: 320px;
  height: 320px;
  background: var(--white);
  border: 1px solid var(--outline);
  border-radius: 28px;
  box-shadow:
    0 24px 60px rgba(0, 0, 0, 0.55),
    0 4px 12px rgba(64, 81, 137, 0.2);
  padding: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}
.qr-wrap::after {
  /* Subtle highlight bevel along the top so the white card reads as raised. */
  content: "";
  position: absolute;
  inset: 1px;
  border-radius: 27px;
  pointer-events: none;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.5) inset;
}
#qr-canvas { width: 100%; height: 100%; display: block; }

.qr-hint  { margin-top: 18px; font-size: 12px; color: var(--on-surface-dim); text-align: center; }
.qr-hint strong { color: var(--white); font-weight: 600; }

/* ─── Responsive breakpoints ────────────────────────── */

/* Narrow landscape / portrait — stack columns. */
@media (max-width: 1100px), (orientation: portrait) {
  .pairing-main {
    flex-direction: column;
    padding: 100px 6% 40px;
    gap: 28px;
    overflow-y: auto;
  }
  .pairing-divider {
    width: 60%;
    height: 1px;
    background: linear-gradient(90deg, transparent 0%, var(--outline) 50%, transparent 100%);
  }
  .pairing-left, .pairing-right { padding: 0; max-width: 560px; }
}

/* Short landscape (tablet) — pull everything in. */
@media (max-height: 600px) {
  .pairing-topbar { top: 18px; left: 32px; right: 32px; }
  .brand-logo { height: 32px; }
  .version-chip { height: 22px; padding: 0 10px; font-size: 11px; }
  .pairing-title { font-size: 28px; margin-bottom: 8px; }
  .pairing-subtitle { font-size: 13px; margin-bottom: 18px; }
  .code-card { padding: 12px 22px; margin-bottom: 10px; border-radius: 16px; }
  .code-label { font-size: 10px; margin-bottom: 4px; }
  .code-value { font-size: 32px; letter-spacing: 9px; }
  .code-value.pin { letter-spacing: 6px; }
  .conn-status { margin-top: 14px; font-size: 12px; padding: 8px 14px; }
  .qr-wrap { width: 220px; height: 220px; padding: 16px; }
  .qr-label { margin-bottom: 12px; }
  .qr-hint { margin-top: 12px; }
}

/* Large 4K signage — scale up. */
@media (min-width: 1920px) and (min-height: 900px) {
  .pairing-title { font-size: 56px; }
  .pairing-subtitle { font-size: 19px; max-width: 75%; }
  .code-card { padding: 28px 32px; border-radius: 26px; }
  .code-label { font-size: 12px; letter-spacing: 3px; }
  .code-value { font-size: 58px; letter-spacing: 18px; }
  .code-value.pin { letter-spacing: 13px; }
  .conn-status { font-size: 15px; padding: 14px 22px; }
  .qr-wrap { width: 400px; height: 400px; padding: 28px; }
  .brand-logo { height: 56px; max-width: 280px; }
  .version-chip { height: 32px; padding: 0 16px; font-size: 16px; }
}

/* ─── Settings modal ────────────────────────────────── */
.modal-bg {
  position: absolute;
  inset: 0;
  background: rgba(5, 7, 14, 0.7);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  animation: fade-in 0.2s ease;
}
.modal-bg.visible { display: flex; }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

.modal-card {
  width: 560px;
  max-width: 90%;
  background: linear-gradient(180deg, var(--surface-card) 0%, #14182a 100%);
  border: 1px solid var(--outline);
  border-radius: 24px;
  padding: 32px;
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.7);
  animation: slide-in 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes slide-in { from { transform: translateY(20px) scale(0.97); opacity: 0; } }

.modal-header { display: flex; align-items: center; gap: 14px; margin-bottom: 12px; }
.modal-icon {
  width: 44px;
  height: 44px;
  border-radius: 12px;
  background: rgba(64, 81, 137, 0.18);
  color: var(--brand-light);
  display: flex;
  align-items: center;
  justify-content: center;
}
.modal-icon svg { width: 22px; height: 22px; }

.modal-title { font-size: 22px; font-weight: 700; color: var(--white); }
.modal-sub   { font-size: 13px; color: var(--on-surface-dim); line-height: 1.6; margin-bottom: 24px; }

.modal-field-label {
  display: block;
  font-size: 11px;
  font-weight: 700;
  color: var(--on-surface-dim);
  letter-spacing: 1.5px;
  margin-bottom: 8px;
}

#server-url-input {
  width: 100%;
  padding: 16px 18px;
  font-size: 15px;
  background: var(--surface);
  color: var(--white);
  border: 1px solid var(--outline);
  border-radius: 14px;
  outline: none;
  margin-bottom: 26px;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
  font-family: inherit;
}
#server-url-input:focus {
  border-color: var(--brand-light);
  box-shadow: 0 0 0 4px rgba(64, 81, 137, 0.18);
}

.modal-actions { display: flex; gap: 12px; justify-content: flex-end; }

.btn-primary, .btn-secondary {
  padding: 12px 24px;
  border-radius: 12px;
  font-weight: 600;
  font-size: 14px;
  cursor: pointer;
  font-family: inherit;
  outline: none;
  transition: transform 0.15s ease, box-shadow 0.2s ease, background 0.2s ease;
}
.btn-primary {
  background: linear-gradient(180deg, var(--brand-light) 0%, var(--brand) 100%);
  color: var(--white);
  border: 1px solid var(--brand-dark);
  box-shadow: 0 6px 16px rgba(64, 81, 137, 0.4);
}
.btn-primary:hover, .btn-primary:focus { transform: translateY(-1px); box-shadow: 0 8px 22px rgba(64, 81, 137, 0.55); }
.btn-secondary {
  background: transparent;
  color: var(--on-surface-dim);
  border: 1px solid var(--outline);
}
.btn-secondary:hover, .btn-secondary:focus { color: var(--white); border-color: var(--on-surface-dim); }

.btn-danger {
  padding: 12px 18px;
  border-radius: 12px;
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
  font-family: inherit;
  outline: none;
  background: transparent;
  color: var(--danger);
  border: 1px solid rgba(240, 101, 72, 0.4);
  transition: background 0.2s ease, color 0.2s ease;
}
.btn-danger:hover, .btn-danger:focus {
  background: rgba(240, 101, 72, 0.12);
  color: var(--white);
  border-color: var(--danger);
}

.modal-spacer { flex: 1; }

/* ─── Diagnostics block inside the settings modal ───── */
.diag-block {
  display: none;
  background: var(--surface);
  border: 1px solid var(--outline);
  border-radius: 12px;
  padding: 14px 18px;
  margin-bottom: 22px;
  font-size: 12px;
}
.diag-block.visible { display: block; }
.diag-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 4px 0;
  gap: 16px;
}
.diag-row + .diag-row { border-top: 1px solid var(--outline-soft); }
.diag-key { color: var(--on-surface-dim); font-weight: 500; flex-shrink: 0; }
.diag-val { color: var(--white); text-align: right; word-break: break-all; font-size: 11px; }

/* ─── Player ────────────────────────────────────────── */
#player { background: #080B14; }

.stage {
  position: absolute;
  inset: 0;
  overflow: hidden;
  /* BACKSPLASH — like the LG reference player, the stage is NEVER pure black:
     a branded logo on the brand gradient sits behind the video layers, so any
     decoder warm-up / transition gap (when the hardware video plane is OFF)
     shows this loading-style backdrop instead of a black screen. A playing
     clip covers it entirely. */
  background:
    url('../icons/logo_col.png') center center / min(36vw, 340px) no-repeat,
    linear-gradient(180deg, #080B14 0%, #0F1424 50%, #080B14 100%);
  /* Squeeze-back for L-shape: the WHOLE content frame scales uniformly into the
     top-right corner (transform-origin), mirroring the dashboard preview and
     the Android player's 600ms broadcast-style ease. JS sets the transform (see
     applyLshape). Using transform (not width/height) keeps the squeeze smooth
     and identical in both directions, and scales content as one unit instead of
     re-letterboxing it frame-by-frame. */
  transform-origin: 100% 0%;
  transition: transform 600ms cubic-bezier(0.4, 0, 0.2, 1);
}

.layer {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Transparent so the stage's branded backsplash shows through during a
     clip's warm-up (never black). A playing video / image fills the layer. */
  background: transparent;
}
.layer.active   { opacity: 1; pointer-events: auto; z-index: 2; }
.layer.outgoing { z-index: 1; }

.layer img, .layer video { display: block; max-width: 100%; max-height: 100%; }
.layer.fit-contain img, .layer.fit-contain video { width: auto; height: auto; max-width: 100%; max-height: 100%; }
.layer.fit-cover   img, .layer.fit-cover video   { width: 100%; height: 100%; object-fit: cover; }
.layer.fit-stretch img, .layer.fit-stretch video { width: 100%; height: 100%; object-fit: fill; }
.layer iframe { width: 100%; height: 100%; border: 0; }

/* ─── Video wrap (poster img layered over <video>) ──── */
/* The poster <img> sits in the HTML composited layer above the
 * <video> element's native plane. It covers the moment between
 * layer-activation and the decoder pushing its first frame, then
 * fades out — see armSwap in player.js, which adds .playing to the
 * wrap one requestAnimationFrame after media.play() resolves. */
.video-wrap {
  position: absolute;
  inset: 0;
  background: #000;
}
.video-wrap .video-element {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 1;
  /* Transparent until a real decoded frame is on screen. On webOS
     Signage the <video> renders into a HARDWARE video plane that on
     many panels composites ABOVE the HTML graphics plane regardless
     of z-index — so a poster <img> can't cover it. Keeping the
     element itself transparent stops that black plane from being
     composited at all, letting the HTML poster show through. The
     reveal is instant (no transition) so the live video pops in fully
     opaque the moment requestVideoFrameCallback confirms the first
     frame painted — never a half-faded, half-black plane. */
  opacity: 0;
}
.video-wrap.playing .video-element { opacity: 1; }
.video-wrap .video-poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 2;                 /* covers <video> until .playing fires */
  opacity: 1;
  transition: opacity 220ms linear;
  pointer-events: none;
}
.video-wrap.playing .video-poster { opacity: 0; }
/* Honour the rotation fit modes on both children */
.layer.fit-contain .video-element,
.layer.fit-contain .video-poster { object-fit: contain; }
.layer.fit-cover   .video-element,
.layer.fit-cover   .video-poster { object-fit: cover; }
.layer.fit-stretch .video-element,
.layer.fit-stretch .video-poster { object-fit: fill; }

/* ─── Inline video (LG video-sync model) ─────────────── */
/* The video is appended on top of the content already on screen inside
 * the SAME visible layer, so webOS decodes it immediately. It is hidden
 * via `visibility` (which toggles the hardware video plane) until the
 * first real frame paints, then flipped visible while the old content /
 * poster — which masked the warm-up — is removed. */
.video-inline { position: absolute; inset: 0; }
.video-inline .video-inline-el,
.video-inline .video-inline-poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
.video-inline.fit-contain .video-inline-el,
.video-inline.fit-contain .video-inline-poster { object-fit: contain; }
.video-inline.fit-cover   .video-inline-el,
.video-inline.fit-cover   .video-inline-poster { object-fit: cover; }
.video-inline.fit-stretch .video-inline-el,
.video-inline.fit-stretch .video-inline-poster { object-fit: fill; }

/* ─── Native widget renderers ──────────────────────── */
/* News carousel */
.widget-native {
  /* inset:0 instead of flex centering so the widget covers EVERY
     pixel of the layer. With width/height:100% inside the layer's
     `display: flex; align-items: center` parent, a 1–2px gap could
     appear on some webOS resolutions and the layer's #000 bled
     through as a black flash during transitions. */
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at center, rgba(64, 81, 137, 0.10), transparent 60%),
    linear-gradient(180deg, #0F1424 0%, #080B14 100%);
  color: var(--on-surface);
  overflow: hidden;
  display: flex;
  align-items: stretch;
  justify-content: center;
}
.widget-native-empty {
  margin: auto;
  text-align: center;
  padding: 40px;
}
.widget-native-empty-title { font-size: 28px; font-weight: 700; margin-bottom: 8px; }
.widget-native-empty-msg   { font-size: 16px; color: var(--on-surface-dim); }

.news-stage {
  position: relative;
  width: 100%;
  height: 100%;
}
.news-slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  display: flex;
  align-items: stretch;
  transition: opacity 0.5s ease;
}
.news-slide.active { opacity: 1; }

.news-slide-img {
  width: 50%;
  height: 100%;
  object-fit: cover;
  filter: brightness(0.95);
}
.news-slide-body {
  flex: 1;
  padding: 6vmin 5vmin;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2vmin;
  background: linear-gradient(135deg, rgba(8, 11, 20, 0.85), rgba(15, 20, 36, 0.95));
}
.news-source {
  font-size: 1.6vmin;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--brand-light);
}
.news-title {
  font-size: 4.4vmin;
  font-weight: 800;
  line-height: 1.15;
  color: var(--white);
  letter-spacing: -0.5px;
}
.news-desc {
  font-size: 2vmin;
  line-height: 1.5;
  color: var(--on-surface-dim);
}
/* Image-less slide: text fills the full width. We add .no-img via JS
   (server-side when imageUrl is absent, runtime via onerror when the
   CDN 404s) — :has() isn't supported on every webOS WebKit build. */
.news-slide.no-img .news-slide-body { width: 100%; }
.news-slide.no-img .news-slide-img  { display: none; }

/* Weather card */
.weather-card {
  width: 100%;
  height: 100%;
  display: grid;
  grid-template-rows: 1fr auto;
  padding: 6vmin 7vmin;
  gap: 4vmin;
}
.weather-now {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: 4vmin;
}
.weather-loc-name   { font-size: 4.5vmin; font-weight: 800; letter-spacing: -0.5px; }
.weather-loc-region { font-size: 2vmin; color: var(--on-surface-dim); margin-top: 0.6vmin; }
.weather-now-icon   { font-size: 16vmin; line-height: 1; }
.weather-now-temp   { font-size: 14vmin; font-weight: 200; letter-spacing: -3px; }
.weather-now-feels  { grid-column: 1 / -1; font-size: 2vmin; color: var(--on-surface-dim); }

.weather-forecast {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 2vmin;
  border-top: 1px solid var(--outline-soft);
  padding-top: 3vmin;
}
.weather-day {
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 1vmin;
  padding: 1.5vmin 1vmin;
}
.weather-day-label { font-size: 2vmin; font-weight: 600; color: var(--on-surface-dim); text-transform: uppercase; letter-spacing: 1px; }
.weather-day-icon  { font-size: 5vmin; }
.weather-hi { font-size: 2.6vmin; font-weight: 700; color: var(--white); }
.weather-lo { font-size: 2.6vmin; font-weight: 400; color: var(--on-surface-dim); }

.wx-icon       { display: inline-block; }
.wx-sun        { color: #f7b84b; }
.wx-pcloud     { color: #6B80B5; }
.wx-cloud      { color: #878A99; }
.wx-rain       { color: #5084ff; }
.wx-storm      { color: #b6bccd; }
.wx-snow       { color: #ffffff; }
.wx-fog        { color: #878A99; }

/* Sandboxed widget container — wraps iframe + spinner + fallback.
   inset:0 (instead of width/height:100% in a flex parent) guarantees
   the wrap covers every pixel of the layer so no #000 bleeds through. */
.widget-wrap { position: absolute; inset: 0; background: #0b0e15; }
.widget-iframe {
  width: 100%;
  height: 100%;
  border: 0;
  background: #fff;
  display: block;
  position: relative;
  z-index: 1;
}

/* Loading spinner sits BEHIND the iframe (z-index 0). Once the
   iframe paints, JS removes the spinner so we don't pay for a
   compositing layer we don't need. */
.widget-spinner {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(ellipse at center, rgba(64, 81, 137, 0.10), transparent 60%),
    linear-gradient(180deg, #080B14 0%, #0F1424 50%, #080B14 100%);
  color: var(--on-surface-dim);
  font-size: 16px;
  font-weight: 500;
  gap: 18px;
}
.widget-spinner-ring {
  width: 44px;
  height: 44px;
  border: 3px solid rgba(255, 255, 255, 0.08);
  border-top-color: var(--brand-light);
  border-radius: 50%;
  animation: spin 1s linear infinite;
}
.widget-spinner-label { opacity: 0.7; }

.widget-fallback {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(ellipse at center, rgba(240, 101, 72, 0.12), transparent 60%),
    linear-gradient(180deg, #080B14 0%, #0F1424 50%, #080B14 100%);
}
.widget-fallback-card {
  text-align: center;
  max-width: 70%;
  padding: 40px;
  border-radius: 20px;
  background: rgba(30, 34, 48, 0.5);
  border: 1px solid var(--outline);
}
.widget-fallback-title { font-size: 26px; font-weight: 700; color: var(--white); margin-bottom: 12px; }
.widget-fallback-msg   { font-size: 14px; color: var(--on-surface-dim); margin-bottom: 16px; line-height: 1.6; }
.widget-fallback-url   {
  font-family: "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
  font-size: 11px;
  color: var(--on-surface-dim);
  word-break: break-all;
  padding: 8px 12px;
  background: var(--surface);
  border: 1px solid var(--outline);
  border-radius: 8px;
  display: inline-block;
}

/* Invisible corner zone for opening settings from the player screen.
   ~6% of the screen in the bottom-right. Triple-click within 1.5 s. */
.player-secret-tap {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 6vw;
  height: 6vh;
  min-width: 60px;
  min-height: 60px;
  z-index: 200;
  background: transparent;
  cursor: pointer;
  pointer-events: auto;
}

/* Build-version watermark on the live player screen, bottom-right.
   Sits above content (z-index 60) but is click-through so the secret
   settings tap-zone (z-index 200) still receives taps. Kept low-contrast
   so it never competes with the playing media. */
.player-version {
  position: absolute;
  right: 18px;
  bottom: 16px;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px 6px 8px;
  border-radius: 999px;
  background: rgba(8, 11, 20, 0.42);
  border: 1px solid rgba(255, 255, 255, 0.08);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  font-family: "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.4px;
  color: rgba(243, 243, 249, 0.66);
  pointer-events: none;
  opacity: 0.55;
  transition: opacity 0.3s ease;
}
.player-version-logo {
  width: 18px;
  height: 18px;
  object-fit: contain;
  opacity: 0.85;
}

/* Suppress the watermark while the full-screen status overlay is up —
   that card shows its own version line, so we never double up. */
#player-status.visible ~ #player-version { opacity: 0; }

/* 4K signage — scale the watermark up a touch. */
@media (min-width: 1920px) and (min-height: 900px) {
  .player-version { right: 26px; bottom: 24px; font-size: 16px; padding: 8px 16px 8px 10px; }
  .player-version-logo { width: 22px; height: 22px; }
}

.logo-overlay {
  position: absolute;
  z-index: 5;
  pointer-events: none;
  overflow: hidden;
  background: transparent;
}
.logo-overlay img { width: 100%; height: 100%; object-fit: contain; display: block; }

.lshape-overlay {
  position: absolute;
  inset: 0;
  z-index: 10;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  /* Always laid out; faded via opacity so the backdrop eases in/out with the
     squeeze (instead of popping). Same 600ms curve as the .stage squeeze so the
     fade and the squeeze move in lockstep. Invisible + click-through inactive. */
  opacity: 0;
  transition: opacity 600ms cubic-bezier(0.4, 0, 0.2, 1);
  pointer-events: none;
}
.lshape-overlay.active { opacity: 1; }

/* Player status card (download / no-content / closed / error) */
.player-status {
  position: absolute;
  inset: 0;
  z-index: 50;
  display: none;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(ellipse at center, rgba(64, 81, 137, 0.12), transparent 60%),
    linear-gradient(180deg, #080B14 0%, #0F1424 50%, #080B14 100%);
}
.player-status.visible { display: flex; }

.status-card {
  text-align: center;
  padding: 50px 70px;
  max-width: 720px;
}
.status-logo { width: 88px; height: 88px; opacity: 0.9; margin-bottom: 28px; }
.status-title { font-size: 38px; font-weight: 800; color: var(--white); margin-bottom: 14px; letter-spacing: -0.5px; }
.status-msg   { font-size: 16px; color: var(--on-surface-dim); line-height: 1.6; }

/* Version line under the status card (download / no-content / error). */
/* Company logo watermark. Transparent full-screen layer that sits ABOVE the
   content but BELOW the status overlay (z-index 50) — so it shows during
   playback and is naturally covered on loading / no-content / closed screens.
   The logo is absolutely positioned per the 9-grid anchor; size/position/offset
   are set inline by JS. */
.brand-watermark {
  position: absolute;
  inset: 0;
  z-index: 40;
  display: none;
  pointer-events: none;
}
.brand-watermark.visible { display: block; }
.brand-watermark-img {
  position: absolute;
  width: 25%;
  height: auto;
  max-height: 90%;
  object-fit: contain;
}

.status-version {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 34px;
  font-family: "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.5px;
  color: var(--on-surface-dim);
  opacity: 0.7;
}
.status-version-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--brand-light);
  box-shadow: 0 0 8px rgba(107, 128, 181, 0.7);
}

.download-bar {
  width: 100%;
  max-width: 560px;
  height: 8px;
  background: rgba(255, 255, 255, 0.06);
  border-radius: 999px;
  margin: 32px auto 0;
  overflow: hidden;
  display: none;
}
.download-bar.visible { display: block; }
.download-bar-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--brand) 0%, var(--brand-light) 100%);
  width: 0%;
  transition: width 0.4s ease;
  box-shadow: 0 0 12px rgba(107, 128, 181, 0.6);
}

/* Corner download card — shown over live (streaming) content while media
   downloads in the background. Bottom-left, glassy, brand-styled. */
.download-hud {
  position: absolute;
  left: 26px;
  bottom: 26px;
  z-index: 60;
  display: none;
  align-items: center;
  gap: 16px;
  min-width: 320px;
  max-width: 30vw;
  padding: 16px 20px;
  border-radius: 16px;
  background: linear-gradient(135deg, rgba(24, 27, 36, 0.92) 0%, rgba(15, 17, 23, 0.92) 100%);
  border: 1px solid rgba(107, 128, 181, 0.28);
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.04);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  animation: dl-hud-in 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
.download-hud.visible { display: flex; }

@keyframes dl-hud-in {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}

.dl-hud-logo {
  width: 44px;
  height: 44px;
  object-fit: contain;
  opacity: 0.95;
  flex: 0 0 auto;
}
.dl-hud-body { flex: 1 1 auto; min-width: 0; }

.dl-hud-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 9px;
}
.dl-hud-title {
  font-size: 15px;
  font-weight: 700;
  color: var(--white);
  letter-spacing: -0.2px;
}
.dl-hud-pct {
  font-family: "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
  font-size: 14px;
  font-weight: 700;
  color: var(--brand-light);
}

.dl-hud-bar {
  width: 100%;
  height: 6px;
  background: rgba(255, 255, 255, 0.07);
  border-radius: 999px;
  overflow: hidden;
}
.dl-hud-bar-fill {
  height: 100%;
  width: 0%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--brand) 0%, var(--brand-light) 100%);
  box-shadow: 0 0 10px rgba(107, 128, 181, 0.6);
  transition: width 0.4s ease;
}

.dl-hud-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 8px;
  font-size: 12.5px;
  color: var(--on-surface-dim);
}
.dl-hud-eta { font-variant-numeric: tabular-nums; }

.dl-hud-ver {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-top: 8px;
  font-family: "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.4px;
  color: var(--on-surface-dim);
  opacity: 0.62;
}
.dl-hud-ver-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--brand-light);
  box-shadow: 0 0 7px rgba(107, 128, 181, 0.7);
}

/* 4K signage — scale the corner card up a touch. */
@media (min-width: 1920px) and (min-height: 900px) {
  .download-hud { left: 40px; bottom: 40px; min-width: 420px; padding: 20px 26px; gap: 20px; border-radius: 20px; }
  .dl-hud-logo { width: 58px; height: 58px; }
  .dl-hud-title { font-size: 19px; }
  .dl-hud-pct { font-size: 18px; }
  .dl-hud-bar { height: 8px; }
  .dl-hud-foot { font-size: 15px; }
  .dl-hud-ver { font-size: 13px; }
}

/* Welcome splash (first-paired launch) */
.welcome {
  position: absolute;
  inset: 0;
  z-index: 100;
  display: none;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(ellipse at center, rgba(10, 179, 156, 0.12), transparent 60%),
    linear-gradient(180deg, #080B14 0%, #0F1424 50%, #080B14 100%);
  animation: fade-in 0.4s ease;
}
.welcome.visible { display: flex; }

.welcome-card { text-align: center; }

.welcome-checkmark {
  width: 140px;
  height: 140px;
  margin: 0 auto 28px;
  color: var(--success);
  filter: drop-shadow(0 0 24px rgba(10, 179, 156, 0.5));
  animation: pop 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}
.welcome-checkmark svg { width: 100%; height: 100%; }
@keyframes pop { from { transform: scale(0.5); opacity: 0; } }

.welcome-title { font-size: 56px; font-weight: 800; color: var(--white); margin-bottom: 12px; letter-spacing: -1px; }
.welcome-sub   { font-size: 22px; color: var(--on-surface-dim); font-weight: 500; }

/* ─── Transitions (used by the rotation engine) ─────── */
@keyframes t-fade-in       { from { opacity: 0; } to { opacity: 1; } }
@keyframes t-fade-out      { from { opacity: 1; } to { opacity: 0; } }
@keyframes t-slide-right-in  { from { transform: translateX(-100%); } to { transform: translateX(0); } }
@keyframes t-slide-right-out { from { transform: translateX(0); }    to { transform: translateX(100%); } }
@keyframes t-slide-up-in     { from { transform: translateY(100%); } to { transform: translateY(0); } }
@keyframes t-slide-up-out    { from { transform: translateY(0); }    to { transform: translateY(-100%); } }
@keyframes t-scale-in        { from { transform: scale(0.6); opacity: 0; } to { transform: scale(1); opacity: 1; } }
@keyframes t-scale-out       { from { transform: scale(1); opacity: 1; }   to { transform: scale(1.4); opacity: 0; } }
@keyframes t-bounce-in       {
  0%   { transform: scale(0.5); opacity: 0; }
  60%  { transform: scale(1.05); opacity: 1; }
  80%  { transform: scale(0.95); }
  100% { transform: scale(1); }
}
@keyframes t-bounce-out      { from { transform: scale(1); opacity: 1; } to { transform: scale(0.7); opacity: 0; } }
@keyframes t-flip-in         { from { transform: perspective(800px) rotateY(90deg); opacity: 0; } to { transform: perspective(800px) rotateY(0); opacity: 1; } }
@keyframes t-flip-out        { from { transform: perspective(800px) rotateY(0); opacity: 1; }    to { transform: perspective(800px) rotateY(-90deg); opacity: 0; } }
@keyframes t-zoom-in         { from { transform: scale(1.5); opacity: 0; } to { transform: scale(1); opacity: 1; } }
@keyframes t-zoom-out        { from { transform: scale(1); opacity: 1; }   to { transform: scale(0.5); opacity: 0; } }
@keyframes t-snap-in         { from { transform: scale(1.04); opacity: 0; } to { transform: scale(1); opacity: 1; } }
@keyframes t-snap-out        { from { opacity: 1; } to { opacity: 0; } }
@keyframes t-glitch-in       {
  0%   { transform: translate(-4px, 0); opacity: 0.6; clip-path: inset(0 0 60% 0); }
  20%  { transform: translate(6px, -4px); clip-path: inset(40% 0 0 0); }
  40%  { transform: translate(-3px, 4px); clip-path: inset(0 30% 0 0); }
  60%  { transform: translate(2px, -2px); clip-path: inset(0 0 0 30%); }
  100% { transform: translate(0, 0); opacity: 1; clip-path: inset(0); }
}
@keyframes t-glitch-out      { from { opacity: 1; } to { opacity: 0; transform: translate(4px, 0); } }
@keyframes t-swipe-in        { from { transform: translateX(100%); } to { transform: translateX(0); } }
@keyframes t-swipe-out       { from { transform: translateX(0); }   to { transform: translateX(-100%); } }
@keyframes t-float-in        { from { transform: translateY(60px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
@keyframes t-float-out       { from { transform: translateY(0); opacity: 1; }   to { transform: translateY(-60px); opacity: 0; } }
