/* css/app.css — Phase 2 DV YouTube viewer.
   Design tokens + page shell + sidebar + boot states.
   Home grid → home.css, player + chrome → player.css, overlays → watch.css. */

:root {
  /* Design tokens (Cobalt smart-TV YouTube target) */
  --yt-bg-overlay: rgba(15, 15, 15, 0.92);

  --focus-ring:    #FFFFFF;
  --focus-ring-w:  3px;
  --focus-scale:   1.06;
  --focus-radius:  8px;
  --thumb-radius:  12px;

  --safe-x:        56px;
  --safe-y:        28px;
  --grid-gap:      24px;
  --row-gap:       40px;

  --sidebar-w:     80px;
  --sidebar-w-x:   280px;

  --fs-display:    48px;
  --fs-h2:         32px;
  --fs-card-title: 24px;
  --fs-card-meta:  18px;
  --fs-body:       20px;
  --fs-label:      16px;

  --t-fast:        120ms;
  --t-med:         200ms;
  --t-slow:        320ms;
  --ease:          cubic-bezier(0.2, 0, 0, 1);
}

html, body { height: 100%; overflow: hidden; }

body {
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  grid-template-rows: 100%;
  font-size: var(--fs-body);
  color: var(--yt-text);
  background: var(--yt-bg);
}

/* ========== Sidebar ========== */

.sidebar {
  grid-column: 1;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  padding: var(--safe-y) 0;
  gap: 4px;
  background: var(--yt-bg);
  z-index: 10;
}

/* Cecilia avatar at top of sidebar — teal #0098A6 derived from real YT TV (capture
   2026-05-02 of youtube.com/tv signed-in). */
.sidebar-avatar {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 8px 22px 16px;
  height: 64px;
  flex-shrink: 0;
}
.sidebar-avatar img {
  border-radius: 50%;
  flex-shrink: 0;
  display: block;
}
.avatar-name {
  font-weight: 500;
  white-space: nowrap;
  color: var(--yt-text);
  opacity: 0;
  transition: opacity var(--t-fast) var(--ease);
}
/* .is-focused (D-pad) always reveals the name; the :hover / :focus-within
   variants are mouse-only and live in the gated media query below so touch
   doesn't stick the expanded state. */
.sidebar.is-focused .avatar-name { opacity: 1; }

.nav-item {
  appearance: none;
  background: transparent;
  border: 0;
  color: var(--yt-text-2);
  font-family: inherit;
  font-size: var(--fs-label);
  width: 100%;
  height: 64px;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 22px;
  cursor: pointer;
  border-radius: 12px;
  transition: background var(--t-fast) var(--ease),
              color var(--t-fast) var(--ease),
              transform var(--t-fast) var(--ease);
}

.nav-item:hover { color: var(--yt-text); background: var(--yt-surface); }
.nav-item.is-active { color: var(--yt-text); }

.nav-icon { width: 28px; height: 28px; flex-shrink: 0; }

.nav-label {
  font-weight: 500;
  white-space: nowrap;
  opacity: 0;
  transition: opacity var(--t-fast) var(--ease);
}

/* Sidebar collapsed (default): hide labels.
   Expanded on hover/focus/.is-focused: widen + show labels.
   .is-focused is set by dpad.js whenever the focused element is inside .sidebar
   (Bravia/Roku DPad don't fire :hover and :focus-within is unreliable on
   smart-TV browsers, so we manage the class explicitly). */
.sidebar:not(:hover):not(:focus-within):not(.is-focused) .nav-label { opacity: 0; }

/* TV / D-pad path: the explicit .is-focused class (set by dpad.js) always
   expands. :hover / :focus-within are unreliable on smart-TV browsers, which is
   why the class exists — so this rule is unconditional. */
body:has(.sidebar.is-focused) {
  grid-template-columns: var(--sidebar-w-x) 1fr;
  transition: grid-template-columns var(--t-med) var(--ease);
}
.sidebar.is-focused .nav-label { opacity: 1; }

/* Pointer path (real mouse only): hover / focus-within expand the rail. Gated to
   hover-capable FINE pointers so TOUCH never auto-expands. On a touchscreen
   :hover and :focus-within "stick" after a tap, which (a) kept the rail open
   while DV navigated home↔video and (b) reflowed the page grid mid-tap, so a
   card tap landed on its neighbour ("wrong video"). On touch the rail now stays
   a fixed icon strip — the labels it would reveal are unreadable to DV anyway.
   2026-06-08. */
@media (hover: hover) and (pointer: fine) {
  body:has(.sidebar:hover),
  body:has(.sidebar:focus-within) {
    grid-template-columns: var(--sidebar-w-x) 1fr;
    transition: grid-template-columns var(--t-med) var(--ease);
  }
  .sidebar:hover .avatar-name,
  .sidebar:focus-within .avatar-name,
  .sidebar:hover .nav-label,
  .sidebar:focus-within .nav-label { opacity: 1; }
}

/* While the watch view is mounted, hide the sidebar entirely. The fullscreen
   player is `position: fixed` and SHOULD cover the sidebar via z-index, but
   in practice the chrome gradient is partially transparent at the top edge,
   leaking the avatar through. Removing the sidebar from layout is simpler
   and more reliable than fighting stacking-context quirks. */
body.is-watching {
  display: block;
}
body.is-watching .sidebar { display: none; }
body.is-watching .view    { width: 100vw; height: 100vh; padding: 0; }

/* ========== Main view ========== */

.view {
  grid-column: 2;
  height: 100vh;
  overflow-y: auto;
  overflow-x: hidden;
  padding: var(--safe-y) var(--safe-x);
  scroll-behavior: smooth;
}

/* Each view's content fades + lifts in on render — softens the hard innerHTML
   swap between Home / Search / Channels / Channel into an app-like transition.
   The CHILDREN animate (not .view itself), because .view persists across renders
   while its innerHTML is replaced, so the fresh children re-trigger the keyframe. */
@keyframes view-enter {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
.view > * { animation: view-enter 200ms var(--ease) both; }
@media (prefers-reduced-motion: reduce) { .view > * { animation: none; } }

/* Mobile: tighter chrome. Real YT mobile uses ~12px page padding, not 56px. */
@media (max-width: 1100px) {
  :root {
    --safe-x: 12px;
    --safe-y: 16px;
    --row-gap: 24px;
    --sidebar-w: 56px;
    --sidebar-w-x: 220px;
  }
  .nav-item { height: 56px; padding: 0 16px; }
  .sidebar-avatar { padding: 4px 16px 12px; height: 56px; }
  .sidebar-avatar img { width: 32px; height: 32px; }
}

/* Phone portrait: sidebar disappears entirely. Real YT mobile uses a bottom
   tab bar — until we ship one, hiding the rail wins back ~64px of card width.
   DV uses Home only, so the missing nav items aren't reached anyway. */
@media (max-width: 600px) {
  body { display: block; }
  .sidebar { display: none; }
  .view { width: 100vw; height: 100vh; }
}

/* Shared thumbnail placeholder — shows under every thumb until its image decodes,
   and stays visible if a thumb URL 404s. Replaces the old "no thumb" text and the
   browser's broken-image glyph. Images fade in over it via .is-loaded. */
.thumb-ph {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--yt-surface);
  color: var(--yt-text-3);
}
.thumb-ph svg {
  width: 22%;
  height: 22%;
  max-width: 64px;
  max-height: 64px;
  opacity: 0.5;
}

/* ========== Boot states ========== */

.boot-state {
  font-size: var(--fs-body);
  color: var(--yt-text-2);
  padding: 80px 0;
  text-align: center;
}

.boot-err {
  color: var(--yt-bad);
  background: rgba(244, 67, 54, 0.08);
  padding: 24px;
  border-radius: 12px;
  font-family: ui-monospace, Menlo, monospace;
}

/* ========== Boot skeleton (shimmer placeholder during library load) ========== */
/* Lives in index.php so it paints BEFORE the JS module even loads. app.js swaps
   it for the real grid once the library resolves. Wordless — better for DV than
   the old "Loading library…" text. */
.skeleton { padding: var(--safe-y) var(--safe-x); }
.skel-row { margin-bottom: var(--row-gap); }
.skel-title { width: 220px; height: 24px; border-radius: 6px; margin-bottom: 16px; }
.skel-rail { display: flex; gap: var(--grid-gap); overflow: hidden; }
.skel-card { flex: 0 0 480px; }
.skel-thumb { width: 100%; aspect-ratio: 16 / 9; border-radius: var(--thumb-radius); margin-bottom: 12px; }
.skel-line { height: 14px; border-radius: 4px; margin-bottom: 8px; }
.skel-line.short { width: 55%; }
@media (max-width: 1100px) {
  .skeleton { padding: 16px 12px; }
  .skel-rail { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 16px; }
  .skel-card { flex-basis: auto; }
  .skel-title { width: 160px; height: 20px; }
}
.shimmer {
  background: linear-gradient(100deg, var(--yt-surface) 30%, var(--yt-surface-2) 50%, var(--yt-surface) 70%);
  background-size: 200% 100%;
  animation: shimmer 1.4s ease-in-out infinite;
}
@keyframes shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) { .shimmer { animation: none; } }

/* ========== Friendly boot failure (wordless icon + retry) ========== */
.boot-fail {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 24px;
  padding: 80px 20px;
  color: var(--yt-text-2);
}
.boot-fail > svg { width: 96px; height: 96px; opacity: 0.6; }
.boot-retry {
  appearance: none;
  border: 0;
  width: 72px;
  height: 72px;
  border-radius: 50%;
  background: var(--yt-surface);
  color: var(--yt-text);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.boot-retry svg { width: 40px; height: 40px; }
.boot-retry:focus,
.boot-retry:focus-visible,
.boot-retry.is-focused { outline: 3px solid var(--focus-ring); outline-offset: 3px; }
