/*
 * Design system.
 *
 * Dependency-free by choice: the audit measured the old stack's JS bundle at
 * 170 KB and CLS at effectively zero, and the point of this rewrite is not to
 * spend that headroom again. Everything below is hand-written CSS, no framework.
 *
 * Three rules here are load-bearing rather than decorative:
 *
 *   1. Every media slot declares an aspect-ratio. With the width/height
 *      attributes in the markup, that is what holds CLS at zero while
 *      thumbnails stream in.
 *   2. The watch page body is rendered ONCE and switches layout with CSS
 *      (task 28). There is no .desktop-only / .mobile-only pair anywhere in this
 *      file and there must not be.
 *   3. The shorts feed is built from real anchors (task 5). Styling never
 *      replaces an <a> with a click handler.
 */

:root {
  --bg:            #0b0b10;
  --bg-elevated:   #14141c;
  --surface:       #1a1a24;
  --surface-hover: #22222e;
  --border:        #26263340;
  --border-solid:  #262633;

  --text:          #f2f3f5;
  --text-dim:      #9b9ba7;
  --text-faint:    #6b6b78;

  --accent:        #ff2e63;
  --accent-hover:  #ff4b78;
  --accent-soft:   #ff2e6318;

  --radius:    12px;
  --radius-sm: 8px;
  --radius-lg: 18px;
  --max:       1600px;
  --header-h:  60px;

  --shadow:    0 4px 24px rgba(0, 0, 0, 0.4);
  --shadow-lg: 0 12px 48px rgba(0, 0, 0, 0.55);
}

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  /* A guard, not a fix. Nothing should overflow horizontally - the audit
     confirmed no page type had a horizontal scrollbar - but a full-bleed
     element with slightly wrong arithmetic should not be able to break the
     whole layout on a phone. */
  overflow-x: clip;
}

body {
  margin: 0;
  overflow-x: clip;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

img, video { display: block; max-width: 100%; }

/*
 * The hidden attribute does not hide inline SVG on its own.
 *
 * The UA stylesheet rule that implements `hidden` does not reach elements in the
 * SVG namespace, so <svg hidden> computes to display:block and renders. Every
 * icon-toggle button in the player and the shorts rail holds two <svg> children
 * - play/pause, muted/sound, enter/exit fullscreen - inside a display:grid
 * button. With hidden inert, BOTH rendered, so the grid made two rows and every
 * control appeared doubled and stacked.
 *
 * !important because the buttons are grid containers, which blockify their
 * children, and this must win over that.
 */
[hidden] { display: none !important; }
a { color: inherit; text-decoration: none; }

::selection { background: var(--accent); color: #fff; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ------------------------------------------------------------------ header */

.site-header {
  position: sticky; top: 0; z-index: 100;
  display: flex; align-items: center; gap: 20px;
  height: var(--header-h); padding: 0 20px;
  background: rgba(11, 11, 16, 0.85);
  backdrop-filter: saturate(180%) blur(16px);
  -webkit-backdrop-filter: saturate(180%) blur(16px);
  border-bottom: 1px solid var(--border);
}

/* flex: 0 0 auto on BOTH the link and the image.
   Without it the wordmark is a flex item that flexbox is free to compress, and
   at 375px - where the input and the "Search" button wanted more room than the
   bar had - it was squeezed narrower than its own 400x125 ratio and rendered
   visibly distorted. Height sets the size; nothing is allowed to shrink it. */
.site-header .brand {
  display: flex; align-items: center;
  flex: 0 0 auto;
  font-size: 17px; font-weight: 800; letter-spacing: -0.02em;
  color: #fff;
  white-space: nowrap;
}

/* The wordmark is sized by height so it always fits the 60px header, with the
   width left to follow the aspect ratio - the file is 400x125 and squashing it
   to a fixed width would distort it. It shrinks on narrow screens, where the
   search field needs the room more than the logo does. */
.site-header .brand img {
  display: block;
  flex: 0 0 auto;
  height: 34px; width: auto;
}
@media (max-width: 560px) {
  .site-header .brand img { height: 26px; }
}

/* The mobile search toggle. Hidden on desktop, where the full field fits. */
.search-toggle { display: none; }

.site-header nav { display: flex; gap: 4px; }
.site-header nav a {
  padding: 7px 14px; border-radius: 999px;
  font-size: 14px; font-weight: 500; color: var(--text-dim);
  transition: background .15s, color .15s;
}
.site-header nav a:hover { background: var(--surface); color: var(--text); }

.site-header .search { margin-left: auto; display: flex; gap: 8px; }
.site-header input {
  width: clamp(140px, 26vw, 380px);
  padding: 9px 16px; border-radius: 999px;
  border: 1px solid var(--border-solid);
  background: var(--surface); color: var(--text);
  font-size: 14px; outline: none;
  transition: border-color .15s, background .15s;
}
.site-header input:focus { border-color: var(--accent); background: var(--bg-elevated); }
.site-header input::placeholder { color: var(--text-faint); }
.site-header button {
  padding: 9px 20px; border: 0; border-radius: 999px;
  background: var(--accent); color: #fff;
  font-size: 14px; font-weight: 600; cursor: pointer;
  transition: background .15s;
}
.site-header button:hover { background: var(--accent-hover); }

main { max-width: var(--max); margin: 0 auto; padding: 28px 20px 80px; }

/* ------------------------------------------------------------- typography */

h1 {
  font-size: clamp(22px, 3.2vw, 30px); font-weight: 750;
  letter-spacing: -0.025em; line-height: 1.2; margin: 0 0 8px;
}
h2 {
  font-size: 17px; font-weight: 650; letter-spacing: -0.01em;
  margin: 40px 0 16px;
}
h2:first-of-type { margin-top: 0; }

.lede, .hub-description { color: var(--text-dim); margin: 0 0 8px; max-width: 70ch; }
.hub-count { color: var(--text-faint); font-size: 13px; margin: 0 0 24px; }
.empty { color: var(--text-dim); padding: 48px 0; text-align: center; }

.breadcrumbs ol {
  list-style: none; display: flex; flex-wrap: wrap; gap: 8px;
  padding: 0; margin: 0 0 18px; font-size: 13px; color: var(--text-faint);
}
.breadcrumbs a { color: var(--text-dim); }
.breadcrumbs a:hover { color: var(--accent); }
.breadcrumbs li + li::before { content: "›"; margin-right: 8px; opacity: .5; }

/* ------------------------------------------------------------- video grid */

.video-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 22px 16px;
}

.video-card { position: relative; min-width: 0; }

.video-card .thumb {
  position: relative; display: block; overflow: hidden;
  border-radius: var(--radius); background: var(--surface);
  aspect-ratio: 16 / 9;
}
.video-card .thumb img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform .35s cubic-bezier(.2,.7,.3,1);
}
.video-card:hover .thumb img { transform: scale(1.06); }
.video-card:hover .thumb { box-shadow: var(--shadow); }

.video-card .duration {
  position: absolute; right: 8px; bottom: 8px;
  padding: 2px 6px; border-radius: 5px;
  background: rgba(0,0,0,.8); backdrop-filter: blur(4px);
  color: #fff; font-size: 12px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.video-card .badge {
  position: absolute; left: 8px; top: 8px;
  padding: 3px 8px; border-radius: 999px;
  background: var(--accent); color: #fff;
  font-size: 11px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase;
}

.video-card h3 {
  font-size: 14px; font-weight: 550; line-height: 1.4;
  margin: 10px 0 4px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.video-card:hover h3 { color: var(--accent); }
.video-card .card-meta {
  font-size: 12.5px; color: var(--text-faint);
  display: flex; gap: 6px; flex-wrap: wrap; align-items: center;
}
.video-card .card-meta a:hover { color: var(--text-dim); }

/* Portrait cards, used wherever shorts appear inside a normal grid. */
.video-grid--portrait { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }
.video-grid--portrait .thumb { aspect-ratio: 9 / 16; }

/* ------------------------------------------------------------------ pills */

.pill-row {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-wrap: wrap; gap: 8px;
}
.pill-row a, .pill {
  display: inline-block; padding: 7px 14px;
  border: 1px solid var(--border-solid); border-radius: 999px;
  background: var(--surface); color: var(--text-dim);
  font-size: 13px; font-weight: 500;
  transition: background .15s, color .15s, border-color .15s;
}
.pill-row a:hover {
  background: var(--accent-soft); color: var(--accent);
  border-color: var(--accent);
}
.pill-row .count { color: var(--text-faint); font-size: 11px; margin-left: 4px; }

/* The homepage category strip.
 *
 * Named explicitly, NOT ".rail". A bare .rail selector also matched the player's
 * seek track - both were called .rail - so this margin-bottom was applied to the
 * scrub bar, pushing it 36px out of alignment with its own knob and opening a
 * large gap above the control row. */
.category-rail { margin-bottom: 36px; }

/* --------------------------------------------------------- shorts feed */
/*
 * TikTok-style vertical snap feed.
 *
 * Every slide is wrapped in a real <a href="/shorts/{slug}">. That is not a
 * detail: task 5 exists because the old feed rendered one video at a time in a
 * swipe player and emitted ZERO anchors, before or after JavaScript, so the
 * entire shorts corpus was unreachable by crawl. A click handler is not a link.
 *
 * The scroll-snap behaviour is pure CSS, so the feed still works with JS off -
 * it just does not autoplay.
 */
/* =========================================================== shorts mode */
/*
 * A shorts URL is a full-screen surface, not a page with a video on it.
 *
 * The previous version put the feed inside the normal <main>, which has
 * max-width and padding, then used negative margins to break out. Those margins
 * did not match the padding at every breakpoint, so on mobile the feed was ~40px
 * wider than the viewport - which is the content-cut-off-at-the-left in the
 * screenshots. Full-bleed by layout rather than by negative margin removes the
 * whole class of bug.
 */
body.shorts-mode { overflow: hidden; }

body.shorts-mode main {
  max-width: none;
  padding: 0;
  margin: 0;
}

/* The header floats over the video instead of stealing height from it. */
body.shorts-mode .site-header {
  position: fixed; left: 0; right: 0; top: 0;
  background: linear-gradient(rgba(0,0,0,.62), rgba(0,0,0,0));
  backdrop-filter: none; -webkit-backdrop-filter: none;
  border-bottom: 0;
  pointer-events: none;
}
body.shorts-mode .site-header > * { pointer-events: auto; }
body.shorts-mode .site-footer { display: none; }
body.shorts-mode .breadcrumbs { display: none; }

/*
 * The feed itself.
 *
 * WITHOUT JAVASCRIPT this is an ordinary scroll container with CSS snap - every
 * slide reachable, every anchor followable, which is what task 5 requires.
 * WITH JAVASCRIPT the script adds .is-enhanced and drives .shorts-track with a
 * transform, so the drag follows the finger 1:1.
 *
 * 100dvh rather than 100vh: on mobile the browser chrome shrinks the visual
 * viewport as you scroll, and 100vh leaves a strip of the next video showing.
 */
.shorts-feed {
  position: relative;
  width: 100%;
  height: 100vh;
  height: 100dvh;
  overflow-y: auto;
  scroll-snap-type: y mandatory;
  scrollbar-width: none;
  overscroll-behavior: none;
  background: #000;
  touch-action: pan-y;
}
.shorts-feed::-webkit-scrollbar { display: none; }

.shorts-feed.is-enhanced {
  overflow: hidden;
  touch-action: pan-x;
}

.shorts-track { will-change: transform; }

/*
 * The settle animation. transition-duration is set per gesture from the release
 * velocity: a fast flick lands in 150ms, a slow drag eases over 360ms.
 */
.shorts-feed.is-enhanced .shorts-track {
  transition: transform var(--settle, 260ms) cubic-bezier(.22, .61, .36, 1);
}

/*
 * No transition while a finger is down, or the track lags behind it.
 *
 * Driven by a DOM attribute rather than framework state on purpose: production
 * found that clearing a state flag and setting the new transform batched into
 * one commit, so `transition: none` was still in effect when the transform
 * changed and the settle never ran. The attribute is removed BEFORE the
 * transform is written.
 */
.shorts-feed[data-dragging] .shorts-track { transition: none !important; }

.short-slide {
  position: relative;
  width: 100%;
  height: 100vh;
  height: 100dvh;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
  /* Clear the fixed header on desktop so the stage is not tucked under it. */
  padding: 0;
}

/*
 * The blurred backdrop.
 *
 * On a wide screen the portrait stage cannot fill the viewport, and bare black
 * bars read as broken. A blurred, dimmed copy of the poster fills the rest -
 * the same treatment Instagram uses for non-conforming aspect ratios.
 */
.short-slide::before {
  content: "";
  position: absolute; inset: 0;
  background: var(--poster) center / cover no-repeat;
  filter: blur(44px) brightness(.34) saturate(1.2);
  transform: scale(1.2);
  z-index: 0;
}

/*
 * THE STAGE — the portrait frame the video actually occupies.
 *
 * This is the fix for desktop. Previously the <video> was stretched to the whole
 * slide with object-fit:cover, which is fine on a phone (portrait viewport,
 * portrait video) and badly wrong on a 1710px desktop, where covering a 9:16
 * clip into a 16:9 box shows a hugely zoomed fragment.
 *
 * YouTube Shorts and Instagram Reels both solve it the same way: a fixed
 * portrait stage, roughly viewport-height, centred, with the rest of the screen
 * as backdrop. aspect-ratio does the work - the stage is as tall as it can be
 * and exactly 9:16 wide.
 */
.short-stage {
  position: relative;
  height: 100%;
  aspect-ratio: 9 / 16;
  max-width: 100%;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
  background: #000;
  z-index: 1;
}

/*
 * Fit differs by breakpoint, because the right answer differs.
 *
 * ON A PHONE the stage IS the viewport, and a phone is taller than 9:16 - a
 * 440x956 screen is 0.46 against the video's 0.563. `contain` would letterbox
 * with black bars top and bottom on every clip; reels feeds fill edge to edge.
 * So: cover, accepting a small horizontal crop, which is what YouTube Shorts and
 * Instagram both do here.
 *
 * ON DESKTOP the stage is exactly 9:16, so a conforming clip fills it with no
 * crop at all, and `contain` protects the material that is NOT 9:16 - this
 * catalogue has square and landscape videos flagged portrait - by letterboxing
 * them inside the stage against the blurred backdrop rather than cropping them
 * into nonsense.
 */
.short-stage video {
  width: 100%; height: 100%;
  object-fit: cover;
  background: transparent;
}

@media (min-width: 900px) {
  .short-stage video { object-fit: contain; }
}

@media (min-width: 900px) {
  /* Below the fixed header, with breathing room, like the desktop Shorts page. */
  .short-slide { padding: calc(var(--header-h) + 12px) 0 16px; }
  .short-stage { border-radius: 14px; box-shadow: 0 12px 48px rgba(0,0,0,.6); }
}

/* ----------------------------------------------------------- shorts info */
/*
 * Title, channel, facts, description and tags.
 *
 * DESKTOP: a panel BESIDE the video. A 9:16 stage on a 1600px screen leaves wide
 * empty backdrop on both sides; putting the text there costs nothing and stops it
 * covering the bottom quarter of the clip. It also has room for the full set -
 * category, runtime, date, description and every tag - rather than a truncated
 * overlay.
 *
 * MOBILE: there is no room beside, so it reverts to an overlay on the video. The
 * tag list is clamped there, because covering the video on a phone is a real
 * cost and eleven pills is most of the screen.
 */
.short-info {
  position: absolute; inset: auto 0 0 0;
  z-index: 3;
  padding: 0 76px 26px 16px;   /* right gutter clears the overlaid rail */
  background: linear-gradient(transparent, rgba(0,0,0,.35) 34%, rgba(0,0,0,.88));
  pointer-events: none;
}
.short-info > * { pointer-events: auto; }

.short-info .short-title,
.short-info h1.short-title {
  display: -webkit-box; margin: 0 0 8px;
  font-size: 15px; font-weight: 600; line-height: 1.35;
  letter-spacing: 0; color: #fff;
  text-shadow: 0 1px 10px rgba(0,0,0,.55);
  -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.short-info .short-title:hover { color: var(--accent); }

.short-meta { margin-bottom: 8px; }
.short-meta .chan {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 13px; font-weight: 600; color: #fff;
}
.short-meta .chan img { width: 28px; height: 28px; border-radius: 50%; object-fit: cover; }
.short-meta .chan:hover { color: var(--accent); }

.short-facts {
  display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: center;
  font-size: 12.5px; color: rgba(255,255,255,.78); margin-bottom: 10px;
}
.short-facts .fact { white-space: nowrap; }
.short-facts a.fact { color: rgba(255,255,255,.92); text-decoration: underline;
                      text-decoration-color: rgba(255,255,255,.3); }
.short-facts a.fact:hover { color: var(--accent); }

.short-desc {
  margin: 0 0 12px; font-size: 13px; line-height: 1.5;
  color: rgba(255,255,255,.8);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

.short-tags a {
  background: rgba(255,255,255,.14); border-color: transparent;
  color: #fff; padding: 4px 10px; font-size: 12px;
  backdrop-filter: blur(6px);
}
.short-tags a:hover { background: var(--accent); }

/* On a phone, clamp the tag row so it cannot swallow the video. */
@media (max-width: 899px) {
  .short-tags { max-height: 62px; overflow: hidden; }
  .short-desc { display: none; }
}

/*
 * Desktop: out of the video entirely.
 */
@media (min-width: 900px) {
  .short-slide { gap: 28px; }

  .short-info {
    position: static;
    width: min(360px, 26vw);
    max-height: 812px;
    overflow-y: auto;
    padding: 0;
    background: none;
    align-self: center;
  }
  .short-info .short-title,
  .short-info h1.short-title {
    font-size: 19px; -webkit-line-clamp: 3;
    text-shadow: none; margin-bottom: 12px;
  }
  .short-desc { -webkit-line-clamp: 6; color: var(--text-dim); }
  .short-facts { color: var(--text-faint); }
  .short-facts a.fact { color: var(--text-dim); }
  .short-tags a { background: var(--surface); border: 1px solid var(--border-solid);
                  color: var(--text-dim); backdrop-filter: none; }
  .short-tags a:hover { background: var(--accent-soft); color: var(--accent);
                        border-color: var(--accent); }
}

/* ------------------------------------------------------- paused indicator */
/*
 * The centred play glyph, shown while a slide is paused.
 *
 * The markup and the is-paused class already existed; the CSS did not, so the
 * span rendered as an empty inline element and a paused slide looked frozen
 * rather than paused. There was no way to tell a stalled video from a stopped
 * one, which is exactly the complaint.
 *
 * Behaviour follows Reels and YouTube Shorts, which agree on this:
 *
 *   - Paused shows a large, centred, PERSISTENT play triangle. It is the state
 *     indicator, not a flash - it stays until playback resumes, so the surface
 *     always says which state it is in.
 *   - It is a hint, not a button. Tapping anywhere on the video toggles, which
 *     is why this is aria-hidden and pointer-events:none - it must never eat
 *     the tap it is inviting.
 *   - Playing shows nothing at all. A permanent control would sit on top of the
 *     content the whole surface exists to show.
 *   - It scales up and fades as it disappears, which reads as "resumed".
 */
.short-play {
  position: absolute;
  top: 50%; left: 50%;
  width: 78px; height: 78px;
  margin: -39px 0 0 -39px;
  border-radius: 50%;
  background: rgba(16, 16, 20, .55);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  display: grid;
  place-items: center;
  z-index: 7;

  /* Never intercept the tap that toggles playback. */
  pointer-events: none;

  /* Hidden by default: playing is the normal state and shows no chrome. */
  opacity: 0;
  transform: scale(1.35);
  transition: opacity .18s ease, transform .18s ease;
}

/* The triangle itself, drawn in CSS so the slide costs no extra request. */
.short-play::before {
  content: "";
  width: 0; height: 0;
  border-style: solid;
  border-width: 15px 0 15px 25px;
  border-color: transparent transparent transparent #fff;
  /* Nudged right: an optical centre, because a triangle's visual mass sits
     left of its bounding box and a mathematically centred one looks off. */
  margin-left: 6px;
}

.short-slide.is-active.is-paused .short-play {
  opacity: 1;
  transform: scale(1);
}

/*
 * Buffering is deliberately NOT shown here any more.
 *
 * A spinner in the centre covers the picture and, on clips this short, flashes
 * over the subject repeatedly - which is the complaint. Loading now reports on
 * the bottom bar, the way YouTube does it, and the centre means one thing only:
 * the viewer paused this.
 */

@media (prefers-reduced-motion: reduce) {
  .short-play { transition: opacity .18s ease; transform: none; }
  .short-slide.is-active.is-paused .short-play { transform: none; }
}

/* ------------------------------------------------------- progress / seek */
/*
 * The scrub bar, at the foot of the stage. It belongs to the video, so it is as
 * wide as the video - not as wide as the screen.
 *
 * It is also where LOADING is reported. A spinner in the middle of the frame
 * covers the one thing the surface exists to show, and on a short clip it
 * flashes in and out over the subject's face; YouTube puts the loading state on
 * the bottom bar for exactly that reason. The centre is now reserved for PAUSED
 * alone, which is a state the viewer chose and expects to see acknowledged.
 *
 * The element is 18px tall but only draws 3px. The rest is invisible touch
 * target: a 3px bar is far below the ~44px comfortable minimum, so without it
 * seeking on a phone means repeatedly missing the bar.
 */
.shorts-progress {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 18px;
  z-index: 6;
  cursor: pointer;
  /* The feed owns vertical drags; this element owns horizontal ones. Declaring
     none means the browser never starts a scroll gesture we then have to fight. */
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
}

/* The track. A pseudo-element so the 18px hit area stays invisible. */
.shorts-progress::before {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 3px;
  background: rgba(255,255,255,.22);
  transition: height .12s ease;
}

/* The elapsed fill. */
.shorts-progress span {
  position: absolute; left: 0; bottom: 0;
  display: block; height: 3px; width: 0;
  background: var(--accent);
  transition: width .15s linear, height .12s ease;
}

/* Thicken while the viewer is interacting, so the target they are aiming at is
   the target they can see. */
.shorts-progress:hover::before,
.shorts-progress:focus-visible::before,
.shorts-progress[data-scrubbing]::before,
.shorts-progress:hover span,
.shorts-progress:focus-visible span,
.shorts-progress[data-scrubbing] span { height: 6px; }

/* The knob, drawn at the head of the fill. Hidden until it is useful - a
   permanent handle is chrome sitting on top of the content. */
.shorts-progress span::after {
  content: "";
  position: absolute; right: -6px; bottom: -3px;
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--accent);
  opacity: 0; transform: scale(.4);
  transition: opacity .12s ease, transform .12s ease;
}
.shorts-progress:hover span::after,
.shorts-progress:focus-visible span::after,
.shorts-progress[data-scrubbing] span::after { opacity: 1; transform: scale(1); }

.shorts-progress:focus-visible { outline: none; }
.shorts-progress:focus-visible::before { background: rgba(255,255,255,.4); }

/* No easing while the value is being driven directly - by a scrub or by a feed
   drag - or the fill lags behind the finger. */
.shorts-feed[data-dragging] .shorts-progress span,
.shorts-progress[data-scrubbing] span { transition: height .12s ease; }

/*
 * Buffering, on the bar rather than over the picture.
 *
 * An indeterminate sweep along the track: it says "still loading" without
 * claiming a position, which a partially-filled bar would. Suppressed while
 * scrubbing, because seeking always triggers a rebuffer and the viewer already
 * knows why the video stopped - they did it.
 */
.short-slide.is-active.is-buffering .shorts-progress:not([data-scrubbing])::before {
  background-image: linear-gradient(90deg,
    transparent 0%, rgba(255,255,255,.65) 50%, transparent 100%);
  background-size: 45% 100%;
  background-repeat: no-repeat;
  animation: shorts-buffering 1.1s linear infinite;
}
@keyframes shorts-buffering {
  from { background-position: -45% 0; }
  to   { background-position: 145% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .shorts-progress span { transition: none; }
  .short-slide.is-active.is-buffering .shorts-progress:not([data-scrubbing])::before {
    animation: none;
    background: rgba(255,255,255,.45);
  }
}

/* ------------------------------------------------------------ action rail */
/*
 * The vertical control column on the right, as on every shorts surface. It sits
 * above the overlay gradient and is deliberately NOT in blocksFeedDrag: a drag
 * that starts on the rail should still move the feed.
 */
.short-rail {
  position: absolute; right: 8px; bottom: 96px;
  display: flex; flex-direction: column; gap: 18px;
  z-index: 4;
}

/*
 * On a wide screen the rail moves OUTSIDE the stage, to its right - the desktop
 * YouTube Shorts arrangement. Overlaying it on a 562px-wide stage wastes the
 * screen and covers the video; there is empty backdrop right next to it.
 */
@media (min-width: 900px) {
  /* Between the video and the details panel, in normal flow. */
  .short-rail {
    position: static;
    align-self: flex-end;
    margin-bottom: 8px;
    gap: 14px;
  }
  .short-rail .ico { background: var(--surface); border: 1px solid var(--border-solid); }
  .short-rail .ico svg { fill: var(--text); }
  .short-rail button, .short-rail a { color: var(--text-dim); text-shadow: none; }
}
.short-rail button,
.short-rail a {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  width: 48px; padding: 0; border: 0; cursor: pointer;
  background: none; color: #fff;
  font: inherit; font-size: 11px; font-weight: 600;
  text-shadow: 0 1px 6px rgba(0,0,0,.6);
}
.short-rail .ico {
  width: 42px; height: 42px; border-radius: 50%;
  background: rgba(255,255,255,.14); backdrop-filter: blur(8px);
  display: grid; place-items: center;
  transition: background .15s, transform .15s;
}
.short-rail button:active .ico { transform: scale(.9); }
.short-rail button:hover .ico, .short-rail a:hover .ico { background: rgba(255,255,255,.26); }
.short-rail .ico svg { width: 21px; height: 21px; fill: #fff; }
.short-rail .avatar-btn .ico {
  background: none; border: 2px solid #fff; overflow: hidden; padding: 0;
}
.short-rail .avatar-btn img { width: 100%; height: 100%; object-fit: cover; }

/* ---------------------------------------------------------------- drawer */
/*
 * Details live in a bottom sheet, not stacked under the feed as page content.
 *
 * Everything in it is in the served HTML, so the cast, tags and description are
 * crawlable exactly as before - this is progressive disclosure, which is what
 * YouTube Shorts does, not hidden text. The whole point of point 3 in the
 * feedback: a shorts URL must not look like a watch page.
 */
.short-drawer {
  position: fixed; left: 0; right: 0; bottom: 0;
  max-height: 78vh; z-index: 20;
  background: var(--bg-elevated);
  border-radius: 18px 18px 0 0;
  box-shadow: 0 -8px 40px rgba(0,0,0,.6);
  transform: translateY(100%);
  transition: transform .28s cubic-bezier(.22,.61,.36,1);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0 16px 32px;
}
.short-drawer[data-open] { transform: translateY(0); }

.short-drawer .grabber {
  position: sticky; top: 0; z-index: 1;
  margin: 0 -16px 8px; padding: 10px 16px 8px;
  background: var(--bg-elevated);
  display: flex; align-items: center; justify-content: space-between;
}
.short-drawer .grabber::before {
  content: ""; position: absolute; left: 50%; top: 6px;
  width: 36px; height: 4px; margin-left: -18px;
  border-radius: 2px; background: var(--border-solid);
}
.short-drawer .grabber h2 { margin: 6px 0 0; font-size: 15px; }
.short-drawer .grabber button {
  background: none; border: 0; color: var(--text-dim);
  font-size: 22px; line-height: 1; cursor: pointer; padding: 4px 6px;
}
.short-drawer .section { margin-bottom: 18px; }
.short-drawer .section h3 {
  font-size: 11px; text-transform: uppercase; letter-spacing: .07em;
  color: var(--text-faint); margin: 0 0 8px;
}

.short-scrim {
  position: fixed; inset: 0; z-index: 19;
  background: rgba(0,0,0,.5);
  opacity: 0; pointer-events: none;
  transition: opacity .28s;
}
.short-scrim[data-open] { opacity: 1; pointer-events: auto; }

/* ------------------------------------------------------------- watch page */

.watch-layout {
  display: grid; gap: 32px;
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 1100px) {
  .watch-layout { grid-template-columns: minmax(0, 1fr) 380px; }
}



.watch h1 { margin: 20px 0 12px; }

.meta-row {
  display: flex; flex-wrap: wrap; gap: 14px; align-items: center;
  padding-bottom: 18px; margin-bottom: 20px;
  border-bottom: 1px solid var(--border);
  font-size: 13.5px; color: var(--text-dim);
}
.meta-row .channel {
  display: inline-flex; align-items: center; gap: 8px;
  font-weight: 650; color: var(--text);
}
.meta-row .channel:hover { color: var(--accent); }
.meta-row .channel img { width: 32px; height: 32px; border-radius: 50%; }
.meta-row .dot { color: var(--text-faint); }

.section { margin-bottom: 28px; }
.section h2 { font-size: 14px; text-transform: uppercase; letter-spacing: .06em;
              color: var(--text-faint); margin: 0 0 12px; }

.description {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 18px 20px;
}
.description p { margin: 0 0 10px; }
.description p:last-child { margin: 0; }
.description .context { color: var(--text-dim); font-size: 13.5px; }
.description .context a { color: var(--text); font-weight: 550; }
.description .context a:hover { color: var(--accent); }

.related .video-grid { grid-template-columns: minmax(0, 1fr); gap: 14px; }
@media (max-width: 1099px) {
  .related .video-grid { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
}
@media (min-width: 1100px) {
  /* In the sidebar, cards go horizontal so more fit above the fold. */
  .related .video-card { display: grid; grid-template-columns: 168px 1fr; gap: 12px; }
  .related .video-card h3 { margin-top: 0; -webkit-line-clamp: 3; font-size: 13.5px; }
}

/* ------------------------------------------------------- channel / actor */

.hub-header {
  display: flex; gap: 20px; align-items: center; flex-wrap: wrap;
  padding: 24px; margin-bottom: 32px;
  background: linear-gradient(135deg, var(--surface), var(--bg-elevated));
  border: 1px solid var(--border); border-radius: var(--radius-lg);
}
.hub-header .avatar {
  width: 84px; height: 84px; border-radius: 50%;
  object-fit: cover; background: var(--surface-hover);
}
.hub-header h1 { margin: 0 0 6px; }

.stats { display: flex; flex-wrap: wrap; gap: 26px; margin: 14px 0 0; }
.stats div { display: flex; flex-direction: column; }
.stats dt { font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
            color: var(--text-faint); }
.stats dd { margin: 3px 0 0; font-size: 16px; font-weight: 650; }

.index-list {
  list-style: none; padding: 0; margin: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 8px;
}
.index-list a {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border-radius: var(--radius-sm);
  background: var(--surface); border: 1px solid transparent;
  transition: background .15s, border-color .15s;
}
.index-list a:hover { background: var(--surface-hover); border-color: var(--border-solid); }
.index-list img { width: 36px; height: 36px; border-radius: 50%; object-fit: cover; }
.index-list .name { font-size: 14px; font-weight: 500; }
.index-list .count { margin-left: auto; font-size: 12px; color: var(--text-faint); }

/* ------------------------------------------------------------- pagination */

.pagination {
  display: flex; align-items: center; justify-content: center; gap: 14px;
  margin-top: 44px; padding-top: 28px;
  border-top: 1px solid var(--border);
}
.pagination a {
  padding: 10px 20px; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--border-solid);
  font-size: 14px; font-weight: 550;
  transition: background .15s, border-color .15s;
}
.pagination a:hover { background: var(--accent); border-color: var(--accent); color: #fff; }
.page-status { color: var(--text-faint); font-size: 13px;
               font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------------ misc */

.site-footer {
  max-width: var(--max); margin: 0 auto;
  padding: 32px 20px 48px; border-top: 1px solid var(--border);
  color: var(--text-faint); font-size: 13px;
}
.site-footer nav { display: flex; gap: 20px; flex-wrap: wrap; margin-bottom: 14px; }
.site-footer nav a:hover { color: var(--accent); }

.error-page { max-width: 520px; margin: 60px auto; text-align: center; }
.error-page nav ul {
  list-style: none; padding: 0; margin: 24px 0 0;
  display: flex; gap: 10px; justify-content: center; flex-wrap: wrap;
}
.error-page nav a {
  padding: 8px 16px; border-radius: 999px; background: var(--surface);
  font-size: 14px;
}

body.embed { background: #000; }
.embed-player { position: relative; width: 100vw; height: 100vh; }
.embed-player video { width: 100%; height: 100%; object-fit: contain; }
.embed-title {
  position: absolute; left: 0; right: 0; top: 0; padding: 14px 18px;
  color: #fff; font-size: 14px; font-weight: 600;
  background: linear-gradient(rgba(0,0,0,.75), transparent);
}

@media (max-width: 640px) {
  /* MOBILE HEADER
     ----------------------------------------------------------------
     Layout is  [wordmark]  ....  [Shorts] [Tags]  [search icon]

     Three things were wrong before. The wordmark was compressed out of its
     aspect ratio by the search field competing for width. The field plus a
     "Search" button took most of a 375px bar to do something a 38px icon does.
     And nav was simply set to display:none, which left phones with no way at
     all to reach Shorts or Tags - the two things the header exists to link.

     Expanding the field needs no JavaScript: the icon is a <label for="q">, so
     tapping it focuses the input natively, and :focus-within below is what
     opens the row. Nothing on this page loads a script, and this should not be
     the reason it starts. */
  .site-header { gap: 8px; padding: 0 12px; }

  .site-header nav { display: flex; gap: 2px; margin-left: auto; }
  .site-header nav a { padding: 6px 10px; font-size: 13px; }

  /* Collapsed: the input stays in the DOM and focusable - it is what the label
     targets - but takes no space and shows no chrome. */
  .site-header .search { margin-left: 0; gap: 0; }
  .site-header .search input {
    width: 0; min-width: 0; padding: 0; border-width: 0; opacity: 0;
    transition: none;
  }
  .site-header .search button { display: none; }

  .search-toggle {
    display: grid; place-items: center;
    width: 38px; height: 38px; border-radius: 999px;
    color: var(--text-dim); cursor: pointer;
    transition: background .15s, color .15s;
  }
  .search-toggle:hover { background: var(--surface); color: var(--text); }

  /* Expanded: the form takes over the whole bar rather than fighting the
     wordmark for it. The header is sticky and therefore already a containing
     block, so this anchors to the header and not the page. */
  .site-header .search:focus-within {
    position: absolute; z-index: 3;
    left: 12px; right: 12px; top: 50%; transform: translateY(-50%);
    gap: 8px;
    background: var(--bg-elevated, #14141c);
    border-radius: 999px;
  }
  .site-header .search:focus-within .search-toggle { display: none; }
  .site-header .search:focus-within input {
    width: 100%; padding: 9px 16px; border-width: 1px; opacity: 1;
  }
  .site-header .search:focus-within button {
    display: block; padding: 9px 18px; flex: 0 0 auto;
  }

  main { padding: 20px 12px 60px; }
  .video-grid { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 18px 10px; }
  .hub-header { padding: 18px; }
  .hub-header .avatar { width: 64px; height: 64px; }
}

/* --------------------------------------------------- shorts chrome */


/* The swipe hint. Shown once, then remembered - a hint that reappears every
   visit stops being a hint and becomes furniture. */
.shorts-hint {
  position: absolute; left: 50%; bottom: 26px; transform: translateX(-50%);
  z-index: 6; padding: 8px 16px; border-radius: 999px;
  background: rgba(0,0,0,.55); backdrop-filter: blur(10px);
  color: rgba(255,255,255,.92); font-size: 13px; font-weight: 500;
  pointer-events: none; opacity: 0;
  transition: opacity .35s;
}
.shorts-feed.show-hint .shorts-hint {
  opacity: 1;
  animation: floaty 2.4s ease-in-out infinite;
}
@keyframes floaty {
  0%, 100% { transform: translateX(-50%) translateY(0); }
  50%      { transform: translateX(-50%) translateY(-7px); }
}
@media (prefers-reduced-motion: reduce) {
  .shorts-feed.show-hint .shorts-hint { animation: none; }
  .shorts-feed.is-enhanced .shorts-track { transition-duration: 1ms; }
}

/* ====================================================== custom player */
/*
 * The watch-page player.
 *
 * Default <video controls> is the browser's chrome: it does not match the site,
 * differs on every platform, and gives no hook for the things that matter here -
 * a quality menu driven by the HLS ladder, a buffered-range indicator, or
 * double-tap seeking. All of it is built here instead.
 */

.player {
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
  background: #000;
  box-shadow: var(--shadow-lg);
  aspect-ratio: 16 / 9;
  --ctl-h: 44px;
}
.player video {
  width: 100%; height: 100%;
  object-fit: contain;
  background: #000;
  display: block;
}

/* Centre affordance. Visible whenever playback is not running. */
.player .big-play {
  position: absolute; inset: 0; margin: auto;
  width: 72px; height: 72px; border: 0; border-radius: 50%;
  background: rgba(0,0,0,.55); backdrop-filter: blur(8px);
  color: #fff; cursor: pointer;
  display: grid; place-items: center;
  transition: opacity .2s, transform .2s;
  z-index: 2;
}
.player .big-play svg { width: 30px; height: 30px; fill: #fff; margin-left: 3px; }
.player .big-play:hover { transform: scale(1.06); }
.player[data-state="playing"] .big-play,
.player[data-state="loading"] .big-play { opacity: 0; pointer-events: none; }

/* Buffering spinner, shown only while genuinely stalled. */
.player .spinner {
  position: absolute; inset: 0; margin: auto;
  width: 42px; height: 42px; border-radius: 50%;
  border: 3px solid rgba(255,255,255,.25); border-top-color: #fff;
  opacity: 0; pointer-events: none; z-index: 2;
  animation: spin .8s linear infinite;
}
.player[data-state="loading"] .spinner { opacity: 1; }

/* Double-tap seek feedback. */
.player .seek-flash {
  position: absolute; top: 0; bottom: 0; width: 32%;
  display: grid; place-items: center; z-index: 2;
  color: #fff; font-size: 13px; font-weight: 700;
  background: rgba(255,255,255,.12);
  opacity: 0; pointer-events: none; transition: opacity .25s;
}
.player .seek-flash[data-side="back"] { left: 0; }
.player .seek-flash[data-side="fwd"] { right: 0; }
.player .seek-flash[data-show] { opacity: 1; }

/* ------------------------------------------------------ control bar */

.player .controls {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 3;
  padding: 28px 10px 6px;
  background: linear-gradient(transparent, rgba(0,0,0,.82));
  opacity: 1; transition: opacity .25s;
}
.player[data-idle] .controls { opacity: 0; pointer-events: none; }
.player[data-idle] { cursor: none; }

/* The scrub bar. A tall invisible hit area around a thin visible track, so it
   is grabbable on a phone without looking heavy. */
.player .scrub {
  position: relative; height: 16px; cursor: pointer;
  display: flex; align-items: center;
}
.player .scrub .rail {
  position: relative; width: 100%; height: 4px; border-radius: 2px;
  background: rgba(255,255,255,.28); overflow: hidden;
  transition: height .12s;
}
.player .scrub:hover .rail { height: 6px; }
.player .scrub .buffered {
  position: absolute; inset: 0 auto 0 0; width: 0;
  background: rgba(255,255,255,.34);
}
.player .scrub .played {
  position: absolute; inset: 0 auto 0 0; width: 0;
  background: var(--accent);
}
.player .scrub .knob {
  position: absolute; top: 50%; left: 0;
  width: 13px; height: 13px; margin: -6.5px 0 0 -6.5px;
  border-radius: 50%; background: var(--accent);
  transform: scale(0); transition: transform .12s;
}
.player .scrub:hover .knob,
.player[data-scrubbing] .knob { transform: scale(1); }

.player .row {
  display: flex; align-items: center; gap: 4px;
  height: var(--ctl-h); color: #fff;
}
.player .row button {
  width: 38px; height: 38px; padding: 0; border: 0; border-radius: 8px;
  background: none; color: #fff; cursor: pointer;
  display: grid; place-items: center;
  transition: background .15s;
}
.player .row button:hover { background: rgba(255,255,255,.16); }
.player .row button svg { width: 20px; height: 20px; fill: #fff; }
.player .time {
  font-size: 12.5px; font-variant-numeric: tabular-nums;
  color: rgba(255,255,255,.9); padding: 0 6px; white-space: nowrap;
}
.player .spacer { flex: 1; }

/* Volume expands on hover rather than always occupying the bar. */
.player .vol { display: flex; align-items: center; }
.player .vol input {
  width: 0; opacity: 0; margin: 0;
  transition: width .18s, opacity .18s, margin .18s;
  accent-color: var(--accent);
}
.player .vol:hover input, .player .vol:focus-within input {
  width: 72px; opacity: 1; margin: 0 8px 0 2px;
}

/* Quality menu, populated from the HLS ladder when there is one. */
.player .menu-wrap { position: relative; }
.player .menu {
  position: absolute; right: 0; bottom: 46px; min-width: 132px;
  background: rgba(18,18,24,.96); backdrop-filter: blur(12px);
  border: 1px solid var(--border-solid); border-radius: 10px;
  padding: 6px; z-index: 4;
}
.player .menu[hidden] { display: none; }
.player .menu button {
  width: 100%; height: 32px; padding: 0 10px; border-radius: 6px;
  justify-content: flex-start; font-size: 13px; text-align: left;
  display: flex; align-items: center; gap: 8px;
}
.player .menu button[aria-checked="true"]::before { content: "✓"; color: var(--accent); }
.player .menu button:not([aria-checked="true"])::before { content: ""; width: 9px; }

.player:fullscreen { border-radius: 0; aspect-ratio: auto; height: 100vh; }
.player:fullscreen video { object-fit: contain; }

@media (max-width: 640px) {
  .player .row button { width: 34px; height: 34px; }
  .player .time { font-size: 11.5px; padding: 0 3px; }
  .player .vol { display: none; }   /* phones use hardware volume */
}

/* ------------------------------------------------------------ chip strip */
/*
 * The homepage category strip.
 *
 * A single horizontally-scrolling row, not a wrapped block. Wrapped, the 63
 * non-empty categories filled the entire first screen on a phone and pushed
 * every video below the fold - the browse list became the page. One row keeps
 * the videos as the first thing you see, which is what the homepage is for.
 *
 * Same shape YouTube uses for its chip row.
 */
.chip-strip {
  margin: 0 0 24px;
  /* Bleed to the screen edges on mobile so the row can scroll off-screen
     rather than stopping short inside the page gutter. */
  margin-inline: -20px;
  padding-inline: 20px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.chip-strip::-webkit-scrollbar { display: none; }

.chip-strip ul {
  list-style: none; margin: 0; padding: 0;
  display: flex;
  flex-wrap: nowrap;          /* the whole point: one row, never stacked */
  gap: 8px;
  width: max-content;
}
.chip-strip a {
  display: inline-block; white-space: nowrap;
  padding: 8px 14px; border-radius: 999px;
  border: 1px solid var(--border-solid); background: var(--surface);
  color: var(--text-dim); font-size: 13.5px; font-weight: 500;
  transition: background .15s, color .15s, border-color .15s;
}
.chip-strip a:hover {
  background: var(--accent-soft); color: var(--accent); border-color: var(--accent);
}
.chip-strip .count { color: var(--text-faint); font-size: 11.5px; margin-left: 4px; }
.chip-strip a:hover .count { color: var(--accent); }

/* The escape hatch to the full list, visually distinct from a category. */
.chip-strip .chip-more {
  background: var(--accent-soft); color: var(--accent);
  border-color: var(--accent); font-weight: 600;
}

@media (max-width: 640px) {
  .chip-strip { margin-inline: -12px; padding-inline: 12px; }
}

/* ------------------------------------------------------ scroll loading */

/* The sentinel is an observation target, not a visual element. It needs real
   height so IntersectionObserver can see it cross the rootMargin. */
.infinite-sentinel { height: 1px; margin-top: 8px; }

.infinite-status {
  text-align: center; color: var(--text-faint); font-size: 13px;
  margin: 18px 0 0; min-height: 20px;
}
.infinite-status[data-loading]::after {
  content: ""; display: inline-block;
  width: 16px; height: 16px; vertical-align: -3px;
  border: 2px solid var(--border-solid); border-top-color: var(--accent);
  border-radius: 50%; animation: spin .7s linear infinite;
}

/* With scroll-loading active the pagination links are still in the DOM for
   crawlers and for no-JS visitors, but they would be confusing directly under an
   endlessly growing grid. The script hides them only once it has taken over. */
.js-infinite .pagination { display: none; }

.load-more {
  display: block; margin: 18px auto 0;
  padding: 11px 26px; border-radius: 999px;
  border: 1px solid var(--border-solid); background: var(--surface);
  color: var(--text); font: inherit; font-size: 14px; font-weight: 600;
  cursor: pointer; transition: background .15s, border-color .15s;
}
.load-more:hover { background: var(--accent); border-color: var(--accent); color: #fff; }
.load-more:disabled { opacity: .6; cursor: default; }
