/* ═══════════════════════════════════════════════════════
   SlideCast - Student Viewer (iPad Safari first)

   Reuses TestLab tokens (--tl-*). Local namespace: .sc-*. No collisions
   with .tl-* classes. Dark mode follows TestLab: [data-theme="dark"] on
   <html>. State machine is data-state on <body>.
   ═══════════════════════════════════════════════════════ */

html, body {
  height: 100%;
}

body {
  /* dvh handles iOS Safari address-bar collapse; the vh line is the
     fallback for older Safaris where dvh is unrecognised. */
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--tl-bg);
  color: var(--tl-text);
  overflow: hidden;
  /* The viewer is a self-contained app surface - footer disclaimer from
     shared.css would steal pixels from the slide. Suppress it here. */
}

/* TestLab's shared.css fixes a copyright footer to the bottom of every
   page. We do NOT include results/student.css here, so the footer never
   appears; this rule is defensive in case shared.css evolves. */
.tl-disclaimer { display: none !important; }

/* ── State machine: show one section at a time ─────── */

.sc-state {
  display: none;
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100vh;
  height: 100dvh;
}

body[data-state="join"]    #state-join,
body[data-state="loading"] #state-loading,
body[data-state="showing"] #state-showing {
  display: flex;
}

/* Cross-state fade. Pure CSS so we don't fight Safari's compositing. */
.sc-state {
  animation: sc-fade 220ms ease;
}

@keyframes sc-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ── Toolbar (lang + theme) ────────────────────────── */

/* Inherits .tl-student-toolbar from TestLab (top-right fixed). Bump z-index
   so it sits above the slide stage. */
.sc-toolbar {
  z-index: 1100;
}

/* Hide the toolbar in showing mode? No - keep it accessible so the student
   can toggle theme/lang mid-lesson. It sits over the slide unobtrusively. */

/* ── Connection dot (top-right, beside toolbar) ────── */

.sc-connection-dot {
  position: fixed;
  top: calc(var(--tl-space-sm) + 11px); /* vertical-centre against 32px toolbar */
  right: calc(var(--tl-space-sm) + 92px); /* clear of lang selector + theme button */
  z-index: 1100;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.6);
  transition: background-color 200ms ease;
}

[data-theme="dark"] .sc-connection-dot {
  box-shadow: 0 0 0 2px rgba(15, 23, 42, 0.6);
}

/* Three-state colour scheme - mirrors TestLab's pattern. */
.sc-connection-dot.connected    { background: var(--tl-success); }
.sc-connection-dot.reconnecting { background: var(--tl-warning); animation: sc-pulse 1.2s infinite; }
.sc-connection-dot.disconnected { background: var(--tl-danger);  animation: sc-pulse 1.2s infinite; }

@keyframes sc-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.4; }
}

/* ── JOIN state ────────────────────────────────────── */

/* Aurora mesh background.

   Architecture:
   - .sc-state-join sets a calm base (linear gradient, no strong color).
   - ::before and ::after carry the two color blobs (rose top-right,
     indigo bottom-left). Heavy blur and corner placement make the
     state feel atmospheric rather than decorated.
   - The blobs are STATIC by default. The drift animation only kicks in
     for users without prefers-reduced-motion -- see the @media block
     further down.
   - `overflow: hidden` clips the blobs (they extend with `inset: -20%`
     to keep edges soft); `position: relative` anchors them.
   - `.sc-join-inner` is bumped to z-index 1 so the card sits above
     the blob layers regardless of their painted order. */

.sc-state-join {
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--tl-space-xl);
  background: linear-gradient(135deg, #f8fafc 0%, #ffffff 100%);
  position: relative;
  overflow: hidden;
}

[data-theme="dark"] .sc-state-join {
  background: linear-gradient(135deg, #0a0612 0%, #150c24 50%, #0d0a1e 100%);
}

.sc-state-join::before,
.sc-state-join::after {
  content: '';
  position: absolute;
  inset: -20%;
  pointer-events: none;
  z-index: 0;
  filter: blur(80px);
}

.sc-state-join::before {
  background: radial-gradient(circle at 80% 20%, rgba(255, 107, 138, 0.55), transparent 60%);
  opacity: 0.55;
}
.sc-state-join::after {
  background: radial-gradient(circle at 20% 80%, rgba(99, 102, 241, 0.45), transparent 60%);
  opacity: 0.5;
}

[data-theme="dark"] .sc-state-join::before {
  background: radial-gradient(circle at 80% 20%, rgba(255, 107, 138, 1), transparent 60%);
  opacity: 0.75;
}
[data-theme="dark"] .sc-state-join::after {
  background: radial-gradient(circle at 20% 80%, rgba(99, 102, 241, 0.85), transparent 60%);
  opacity: 0.65;
}

.sc-join-inner {
  width: 100%;
  max-width: 460px;
  text-align: center;
  position: relative;
  z-index: 1;
}

.sc-join-logo {
  width: 56px;
  height: 56px;
  margin: 0 auto var(--tl-space-md);
  color: var(--tl-primary);
}
.sc-join-logo svg { width: 100%; height: 100%; }

.sc-join-title {
  font-size: clamp(1.5rem, 5vw, 2rem);
  font-weight: 700;
  margin-bottom: var(--tl-space-xs);
  color: var(--tl-text);
}

.sc-join-subtitle {
  color: var(--tl-text-secondary);
  margin-bottom: var(--tl-space-lg);
  font-size: var(--tl-text-base);
}

.sc-join-card {
  display: flex;
  flex-direction: column;
  gap: var(--tl-space-md);
}

.sc-join-label {
  display: block;
  text-align: left;
  font-size: var(--tl-text-sm);
  font-weight: 600;
  color: var(--tl-text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.sc-code-input {
  text-align: center;
  font-family: var(--tl-font-mono);
  font-size: clamp(1.5rem, 7vw, 2.25rem);
  font-weight: 700;
  letter-spacing: 0.4em;
  text-indent: 0.4em; /* offsets the letter-spacing so the text appears centred */
  text-transform: uppercase;
  padding: var(--tl-space-md);
  min-height: 64px;
}

.sc-join-btn {
  width: 100%;
}

.sc-join-error {
  color: var(--tl-danger);
  font-size: var(--tl-text-sm);
  font-weight: 500;
  min-height: 1.2em;
  margin: 0;
}

/* ── LOADING state ─────────────────────────────────── */

/* Carries a single muted rose halo for continuity with the join state.
   Lower intensity than .sc-state-join so the progress ring stays the
   focal point. */
.sc-state-loading {
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, #f8fafc 0%, #ffffff 100%);
  position: relative;
  overflow: hidden;
}

[data-theme="dark"] .sc-state-loading {
  background: linear-gradient(135deg, #0a0612 0%, #0d0a1e 100%);
}

.sc-state-loading::before {
  content: '';
  position: absolute;
  inset: -20%;
  pointer-events: none;
  z-index: 0;
  filter: blur(80px);
  background: radial-gradient(circle at 50% 30%, rgba(255, 107, 138, 0.35), transparent 60%);
  opacity: 0.4;
}

[data-theme="dark"] .sc-state-loading::before {
  background: radial-gradient(circle at 50% 30%, rgba(255, 107, 138, 0.7), transparent 60%);
  opacity: 0.5;
}

.sc-loading-inner {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--tl-space-md);
  position: relative;
  z-index: 1;
}

.sc-progress-ring {
  display: block;
}

.sc-progress-ring-track {
  stroke: var(--tl-border);
}

.sc-progress-ring-fill {
  stroke: var(--tl-primary);
  transition: stroke-dashoffset 250ms ease;
}

.sc-progress-ring-text {
  font-family: var(--tl-font);
  font-size: 22px;
  font-weight: 700;
  fill: var(--tl-text);
}

.sc-loading-label {
  font-size: var(--tl-text-lg);
  font-weight: 500;
  color: var(--tl-text);
}

.sc-loading-counter {
  font-size: var(--tl-text-sm);
  color: var(--tl-text-secondary);
  font-family: var(--tl-font-mono);
}

/* ── SHOWING state ─────────────────────────────────── */

.sc-state-showing {
  background: #000;
  /*
    touch-action: none disables ALL native browser gestures on this surface.
    We implement pinch + pan in view.js so the canvas overlay can transform
    in lockstep. WITHOUT this rule iOS Safari would intercept two-finger
    pinch as a page zoom and our gesture handler would never see it.
  */
  touch-action: none;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

[data-theme="dark"] .sc-state-showing {
  background: #000;
}

.sc-stage {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  /* Block default Safari scrolling/elastic-bounce inside the stage. */
  overscroll-behavior: contain;
  touch-action: none;
}

/*
  sc-slide-frame is the transform target. Both image and canvas live inside
  it so a single transform on the parent moves them together - this is the
  key trick to keep annotations aligned with the slide while zoomed/panned.

  --sc-tx / --sc-ty / --sc-scale are written by view.js via element.style
  on each gesture frame. translate3d() forces a GPU layer in Safari and
  is faster than translate() + scale() on iPadOS.
*/
.sc-slide-frame {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  transform-origin: 0 0;
  will-change: transform;
  transform: translate3d(var(--sc-tx, 0), var(--sc-ty, 0), 0) scale(var(--sc-scale, 1));
  transition: transform 220ms cubic-bezier(0.2, 0.7, 0.2, 1);
}

.sc-slide-frame.sc-gesturing {
  /* While the user is pinching, disable the transition so motion tracks
     the fingers 1:1. The view.js handler toggles this class. */
  transition: none;
}

.sc-slide-img {
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  /*
    Letterbox the slide to fit the stage while preserving aspect. The
    canvas overlay is sized to MATCH the image's rendered box (set in
    view.js after each load + resize), so coordinates stay 1:1.
  */
  object-fit: contain;
  user-select: none;
  -webkit-user-drag: none;
  pointer-events: none;
}

.sc-annot-canvas {
  position: absolute;
  /* Positioned by JS via inline left/top/width/height to match the image. */
  pointer-events: none;
  /* Defensive: never let the canvas grab gestures even if positioning slips. */
  touch-action: none;
}

/* ── Phase 2: capture-mode swap (slides vs. screen) ──
   The viewer keeps two <img> elements in the same .sc-slide-frame box so the
   pinch/pan transform on the frame applies identically to whichever is shown.
   data-mode on <body> is written by view.js when capture:mode-changed arrives
   (or when the join ack carries the current mode). Default is 'slides'. */

/* Frame image matches the slide image's letterbox layout so the swap is
   visually seamless. Hidden by default via [hidden]; the body[data-mode]
   rules below override that attribute when screen mode is active. */
.sc-frame-img {
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  user-select: none;
  -webkit-user-drag: none;
  pointer-events: none;
}

/* Slides mode (default): show #slide, hide #frame-img. The [hidden] attribute
   on #frame-img already handles this on first paint; this rule keeps the
   intent explicit + survives a future agent removing the attribute. */
body[data-mode="slides"] #frame-img {
  display: none;
}

/* Screen mode: hide the deck slide, show the live frame. We use !important
   on #frame-img's display so the static [hidden] attribute can't accidentally
   win once view.js has removed it - belt and braces given the Phase-1
   [hidden] reset rule in shared.css. */
body[data-mode="screen"] #slide {
  display: none;
}
body[data-mode="screen"] #frame-img {
  display: block !important;
}

/*
  "Live screen" indicator: subtle pulsing badge anchored top-left of the
  stage so the student knows this is a live capture rather than a static
  slide. Rendered as a single ::before pseudo on the stage so we don't need
  another DOM node, and hidden in slides mode.

  Localisation: the badge text is a static English string for now; Phase 2
  spec keeps i18n out of the viewer chrome and EN is the global fallback.
*/
body[data-mode="screen"] .sc-stage::before {
  content: "● Live screen";
  position: absolute;
  top: calc(env(safe-area-inset-top, 0px) + var(--tl-space-sm));
  left: calc(env(safe-area-inset-left, 0px) + var(--tl-space-sm));
  z-index: 1050;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(220, 38, 38, 0.85);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  font-family: var(--tl-font);
  pointer-events: none;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
  animation: sc-live-pulse 1.8s ease-in-out infinite;
}

@keyframes sc-live-pulse {
  0%, 100% { opacity: 0.92; }
  50%      { opacity: 0.55; }
}

/* ── Phase 3: video mode (LL-HLS) ──────────────────────
   A third capture mode where the stage shows a native <video> element fed by
   a Safari-played .m3u8 manifest. Reuses .sc-slide-img layout so the video
   letterboxes into the same letterboxed area as the deck slide and the live
   snapshot - swap-in is visually seamless. Pinch/pan + the annotation
   canvas overlay continue to work without further wiring because all three
   sources live in the same transformed .sc-slide-frame. */

.sc-slide-video {
  /*
    Keep the element's own box aspect. view.js sets an explicit pixel width
    and height from videoWidth/videoHeight after metadata arrives; this avoids
    Safari stretching the native HLS layer to the full stage rectangle.
  */
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  user-select: none;
  -webkit-user-drag: none;
  pointer-events: none;
  /* Black background while segments are buffering so a half-loaded video
     doesn't reveal the page background between frames. */
  background: #000;
}

/* Video mode: hide the deck slide AND the live snapshot, show the <video>. */
body[data-mode="video"] #slide {
  display: none;
}
body[data-mode="video"] #frame-img {
  display: none;
}
body[data-mode="video"] #hls-video {
  display: block !important;
}

/* Reuse the Phase 2 "● Live screen" badge for video mode too. Same colour +
   pulse so the student gets a consistent "this is live" signal whether the
   teacher is in Snapshot or Video mode. */
body[data-mode="video"] .sc-stage::before {
  content: "● Live video";
  position: absolute;
  top: calc(env(safe-area-inset-top, 0px) + var(--tl-space-sm));
  left: calc(env(safe-area-inset-left, 0px) + var(--tl-space-sm));
  z-index: 1050;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(220, 38, 38, 0.85);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  font-family: var(--tl-font);
  pointer-events: none;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
  animation: sc-live-pulse 1.8s ease-in-out infinite;
}

/* Loading shimmer for the race window between "joined session in video mode"
   and "first segment buffered". view.js writes data-state="loading" on the
   <video> element (or body) while the m3u8 is still being primed. */
#hls-video[data-state="loading"] {
  /* Soft diagonal shimmer so the student sees the stage is alive even before
     the first .m4s arrives. Uses two linear-gradient layers for the moving
     stripe. */
  background:
    linear-gradient(110deg,
      rgba(255, 255, 255, 0.04) 0%,
      rgba(255, 255, 255, 0.12) 45%,
      rgba(255, 255, 255, 0.04) 90%) #0a0a0a;
  background-size: 200% 100%;
  animation: sc-hls-shimmer 1.6s linear infinite;
}

@keyframes sc-hls-shimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: -100% 0; }
}

/* ── Phase 3: audio-unmute hint pill ────────────────────
   Surfaced only when entering video mode WITH audio. Centred near the top of
   the stage so it doesn't fight the "● Live video" badge or the connection
   dot in the top-right. Tap target sized to iPad-friendly 44px minimum. */

.sc-audio-unmute-hint {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + var(--tl-space-sm) + 36px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 1080;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 8px 16px;
  border: 0;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.85);
  color: #fff;
  font-family: var(--tl-font);
  font-size: var(--tl-text-sm);
  font-weight: 600;
  letter-spacing: 0.01em;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  -webkit-appearance: none;
  appearance: none;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  animation: sc-audio-hint-pulse 2.4s ease-in-out infinite;
}

.sc-audio-unmute-hint:active {
  transform: translateX(-50%) scale(0.97);
}

.sc-audio-unmute-hint svg {
  width: 18px;
  height: 18px;
  flex: 0 0 18px;
  /* The unicode bell-like speaker icon would clash with the rest of the SC
     iconography; we keep it stroke-only to match the join/ended cards. */
  color: #fff;
}

@keyframes sc-audio-hint-pulse {
  0%, 100% { box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45); }
  50%      { box-shadow: 0 4px 22px rgba(99, 102, 241, 0.55); }
}

/* ── ENDED overlay (shown over showing or loading) ── */

.sc-ended-overlay {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.85);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  animation: sc-fade 250ms ease;
  padding: var(--tl-space-lg);
}

.sc-ended-card {
  background: var(--tl-bg-card);
  color: var(--tl-text);
  border-radius: var(--tl-radius-xl);
  padding: var(--tl-space-xl);
  text-align: center;
  max-width: 360px;
  width: 100%;
  box-shadow: var(--tl-shadow-xl);
}

.sc-ended-card svg {
  width: 64px;
  height: 64px;
  color: var(--tl-text-muted);
  margin: 0 auto var(--tl-space-md);
}

.sc-ended-card h2 {
  font-size: var(--tl-text-2xl);
  font-weight: 700;
  margin-bottom: var(--tl-space-sm);
}

.sc-ended-card p {
  color: var(--tl-text-secondary);
}

/* ── Safe-area / iPad orientation tweaks ───────────── */

@supports (padding: env(safe-area-inset-top)) {
  .sc-toolbar {
    top: calc(env(safe-area-inset-top) + var(--tl-space-sm));
    right: calc(env(safe-area-inset-right) + var(--tl-space-sm));
  }
  .sc-connection-dot {
    top: calc(env(safe-area-inset-top) + var(--tl-space-sm) + 11px);
    right: calc(env(safe-area-inset-right) + var(--tl-space-sm) + 92px);
  }
}

/* Landscape (typical iPad presentation orientation): tighten loading
   layout so the progress ring isn't squeezed by the bottom toolbar. */
@media (orientation: landscape) and (max-height: 540px) {
  .sc-progress-ring { width: 96px; height: 96px; }
  .sc-loading-inner { gap: var(--tl-space-sm); }
}

/* ── Aurora drift animation (motion-safe only) ──────
   Slowly translates the two color blobs across opposing axes. The cycle
   is 28s on alternate so a full back-and-forth takes 56s -- well below
   the threshold where the eye reads it as "moving" rather than "alive."
   Animating `transform` (not `background-position`) keeps the work on
   the GPU compositor and avoids the rasterization jank Safari hits when
   it has to re-paint a 1024px blurred surface every frame.
   `will-change: transform` hints the compositor to keep the layer
   promoted. */
@media (prefers-reduced-motion: no-preference) {
  .sc-state-join::before {
    animation: sc-aurora-drift 28s ease-in-out infinite alternate;
    will-change: transform;
  }
  .sc-state-join::after {
    animation: sc-aurora-drift 28s ease-in-out infinite alternate;
    animation-delay: -14s;
    will-change: transform;
  }
}

@keyframes sc-aurora-drift {
  from { transform: translate3d(-8%, -4%, 0); }
  to   { transform: translate3d( 8%,  4%, 0); }
}
