* { box-sizing: border-box; }
html, body {
  margin: 0; padding: 0; width: 100%; height: 100%;
  background: #000; color: #eee; font-family: system-ui, sans-serif;
  user-select: none;
}
/* Locked/fixed layout is specific to the swipe feed (index.html sets <html class="feed">) —
   grid.html loads this same stylesheet but needs the page to actually scroll, so this must not
   be a blanket html,body rule. Scoping it here, at the source, means grid.css never has to fight
   it: per the CSS overflow-propagation spec, body's own overflow only becomes the page's actual
   scrolling behavior when html's computed overflow is the default `visible` — leaving html
   untouched on grid.html (rather than overriding it back) is what makes that propagation work. */
html.feed, html.feed body {
  overflow: hidden; touch-action: none;
}
#stage {
  position: fixed; inset: 0; display: flex; align-items: center; justify-content: center;
  background: #000;
}
/* A native scroll-snap feed, not a fixed 3-slide track — the browser's own scroll physics
   drives vertical navigation (momentum, snapping, all of it), and only a small bounded window
   of slides (current ± a couple) ever exists in the DOM at once regardless of catalogue size.
   touch-action:pan-y hands vertical gestures to the browser entirely; JS only ever intercepts
   horizontal ones (fav/hide), which act on the current slide alone. */
#feed {
  position: absolute; inset: 0;
  overflow-y: auto; overflow-x: hidden;
  /* One-to-one is the "exactly one media on screen" destination, so the browser's own snapping
     is what enforces that — mandatory, not proximity, and not a JS correction after the fact.
     A JS settle (ease onto the nearest slide once scrolling stops) was the previous approach and
     is what produced the "double scroll": the fling came to rest straddling two slides, then a
     second, separate animation dragged the view forward or back onto one. Native mandatory snap
     folds that into the fling itself — one gesture, one motion, always ending on a whole slide.
     scroll-snap-stop:always additionally forbids flying past a slide inside one fling, which is
     what made a fast swipe silently skip an item. */
  scroll-snap-type: y mandatory;
  /* Scroll anchoring compensates for content inserted/removed above the view on its own, which
     double-counts against the explicit compensation materializeBehind/pruneBehindFar do (see
     keepingScrollAt in app.js) — the result was a full-slide jump. This window is virtualized,
     so the compensation has to be ours alone. */
  overflow-anchor: none;
  touch-action: pan-y;
  scrollbar-width: none;
}
#feed::-webkit-scrollbar { display: none; }
.slide {
  position: relative; width: 100%; height: 100%;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: flex; align-items: center; justify-content: center;
  background: #000; overflow: hidden;
}
/* Wraps one slide's media so a horizontal swipe can drag it without moving the vertical scroll
   position or the fixed overlays. Transform and opacity only, so dragging stays on the
   compositor rather than forcing layout each frame. */
.slideMedia {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  will-change: transform, opacity;
}
.slideMedia img, .slideMedia video {
  width: 100%; height: 100%; object-fit: contain;
  /* A horizontal drag on an <img> is a native image-drag gesture on desktop, which swallows the
     pointer stream mid-swipe (the fav/hide drag just stops) and auto-scrolls the page near its
     edges. Nothing here is meant to be dragged out of the page. */
  -webkit-user-drag: none; user-drag: none;
}
.slideMedia img.fit-cover, .slideMedia video.fit-cover { object-fit: cover; }

#hint {
  position: absolute; color: #ccc; font-size: 15px; line-height: 1.9;
  text-align: center; pointer-events: none;
  padding: 14px 18px; border-radius: 10px; background: rgba(0,0,0,0.55);
}
#hint b { font-weight: 700; color: #fff; }

/* swipe feedback: the side zones flash ★ / ✕ when a horizontal swipe acts on an item */
.tapzone {
  position: absolute; top: 0; bottom: 0; width: 30%;
  display: flex; align-items: center; pointer-events: none;
  background: rgba(255,255,255,0); transition: background 150ms ease-out;
}
.tapzone span {
  color: #fff; font-size: 40px; opacity: 0; transform: scale(0.7);
  transition: opacity 150ms ease-out, transform 150ms ease-out;
}
#zoneLeft { left: 0; justify-content: flex-start; padding-left: 14px; }
#zoneRight { right: 0; justify-content: flex-end; padding-right: 14px; }
.tapzone.flash { background: rgba(255,255,255,0.08); }
.tapzone.flash span { opacity: 0.85; transform: scale(1); }

/* one-time discoverability hint for the invisible swipe gestures */
.tapzone.hintpulse span { opacity: 0.35; transform: scale(1); transition: opacity 900ms ease-in-out; }

#favBurst {
  position: absolute; top: 50%; left: 50%; color: #fff; font-size: 72px; pointer-events: none;
  opacity: 0; transform: translate(-50%, -50%) scale(0.6);
}
#favBurst.burst {
  animation: favburst 500ms ease-out;
}
@keyframes favburst {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(0.5); }
  35% { opacity: 1; transform: translate(-50%, -50%) scale(1.15); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.3); }
}

/* small corner status badge, not a centered modal-like overlay — paused just means
   "autoplay is off, navigate at your own pace," it shouldn't block the view */
#pauseOverlay {
  position: absolute; top: calc(10px + env(safe-area-inset-top)); right: 10px;
  color: #fff; font-size: 14px; pointer-events: none;
  opacity: 0; transform: scale(0.8);
  transition: opacity 150ms ease-out, transform 150ms ease-out;
  background: rgba(0,0,0,0.5); width: 28px; height: 28px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
}
#pauseOverlay.show { opacity: 0.9; transform: scale(1); }

#authOverlay {
  position: fixed; inset: 0; z-index: 30;
  background: rgba(0,0,0,0.85);
  display: flex; align-items: center; justify-content: center;
  touch-action: auto;
}
/* an ID selector beats the UA stylesheet's [hidden] rule on specificity alone, so
   display:flex above would win even while hidden without this override */
#authOverlay[hidden] { display: none; }
#authBox {
  width: 280px; max-width: 85vw; padding: 20px;
  border: 1px solid #444; border-radius: 10px; background: #111;
  text-align: center; font-size: 13px;
}
#authBox p { margin: 0 0 12px; }
#authBox input {
  width: 100%; padding: 8px; margin-bottom: 8px; box-sizing: border-box;
  background: #000; color: #eee; border: 1px solid #444; border-radius: 6px; font-size: 14px;
}
#authBox button {
  width: 100%; padding: 8px; background: #eee; color: #000; border: none;
  border-radius: 6px; font-weight: 600; cursor: pointer;
}
#authBox button:active { background: #ccc; }
#authError { color: #f66; min-height: 14px; font-size: 12px; }

#ctrl {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 10;
  padding: 4px 6px calc(4px + env(safe-area-inset-bottom));
  background: rgba(0,0,0,0.6);
  font-size: 11px; color: #ccc;
  touch-action: auto;
}
#ctrlMain {
  display: flex; flex-wrap: nowrap; align-items: center; gap: 8px;
  overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
#ctrlMain::-webkit-scrollbar { display: none; }
#ctrlExtra {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px;
  margin-top: 6px; padding-top: 6px; border-top: 1px solid #333;
  font-size: 11px;
}
#ctrlExtra[hidden] { display: none; }
/* shared pill-button look — reused by grid.css for the favorites grid page's nav/pager buttons.
   min-height 44px meets the mobile touch-target minimum (Apple HIG / Material). */
.btn {
  font-size: 13px; padding: 6px 10px; min-height: 44px;
  border: 1px solid #444; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  text-decoration: none; white-space: nowrap; flex: none;
  background: none; color: #eee;
}
.btn:active { background: rgba(255,255,255,0.12); }
#ctrlMain #settingsToggle { opacity: 0.8; }
#ctrlExtra button {
  background: none; color: #eee; border: none; padding: 2px 0;
  font-size: 11px; text-decoration: underline; cursor: pointer;
}
#ctrlExtra button:active { color: #888; }
#favBtn.active, #randomBtn.active {
  background: #eee; color: #000; border-color: #eee; font-weight: 700;
}
@media (max-width: 420px) {
  /* Wrap instead of scrolling sideways. The row holds enough buttons to overflow a phone, and
     the horizontal scrollbar is hidden — so anything past the edge (hide, favs, settings) was
     effectively invisible rather than merely off-screen. */
  #ctrlMain { gap: 6px; flex-wrap: wrap; justify-content: center; }
  .btn { font-size: 12px; padding: 5px 7px; }
}
#ctrl label { display: flex; align-items: center; gap: 3px; white-space: nowrap; }
#ctrl input[type="checkbox"] { width: 12px; height: 12px; }
#ctrl input[type="number"] {
  width: 40px; background: #111; color: #eee; border: 1px solid #444;
  font-size: 11px; padding: 1px 2px;
}
.muted { color: #888; }

/* Long-video position scrubber. Sits above #ctrl rather than inside it: it belongs to the media
   on screen, appears and disappears with it, and must stay reachable with a thumb. touch-action
   is none on purpose — html.feed sets touch-action:none globally, and the range still needs the
   browser to route the whole drag to the control rather than treating it as a page gesture. */
#vidScrub {
  position: absolute; left: 0; right: 0;
  bottom: calc(58px + env(safe-area-inset-bottom));
  z-index: 12;
  display: flex; align-items: center; gap: 10px;
  padding: 8px 14px;
  background: linear-gradient(to top, rgba(0,0,0,0.65), rgba(0,0,0,0));
  touch-action: none;
}
#vidScrub[hidden] { display: none; }
#vidScrubTime {
  font-size: 12px; color: #ddd; font-variant-numeric: tabular-nums;
  white-space: nowrap; text-shadow: 0 1px 2px rgba(0,0,0,0.9);
}
#vidScrubRange {
  flex: 1 1 auto; min-width: 0; margin: 0;
  -webkit-appearance: none; appearance: none; background: none;
  /* A wider hit area than the 3px track it draws — the bar is thin by design, the target isn't. */
  height: 28px; touch-action: none; cursor: pointer;
}
#vidScrubRange::-webkit-slider-runnable-track {
  height: 3px; border-radius: 2px; background: rgba(255,255,255,0.35);
}
#vidScrubRange::-moz-range-track {
  height: 3px; border-radius: 2px; background: rgba(255,255,255,0.35);
}
#vidScrubRange::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 14px; height: 14px; margin-top: -5.5px;
  border-radius: 50%; background: #fff; border: none;
  box-shadow: 0 1px 3px rgba(0,0,0,0.8);
}
#vidScrubRange::-moz-range-thumb {
  width: 14px; height: 14px;
  border-radius: 50%; background: #fff; border: none;
  box-shadow: 0 1px 3px rgba(0,0,0,0.8);
}
