/* ==========================================================================
   videos.css - the video index and the per-video transcript page.

   Loaded by /videos and by every /videos/<slug>, and by nothing else. Page
   scoped for the same reason cheatsheet.css and compare.css are: this is two
   pages' furniture, and putting it in components.css would spend the bytes on
   every page. Everything here builds on tokens.css and on the shared .code,
   .btn and .section classes rather than restating them.

   REBUILT 2026-08-27 with the page. The old shape put a whole transcript inside
   a <details> on the index; the shape now is an index of videos, and a page each
   carrying what was said and the code that was on screen while it was said.
   ========================================================================== */

/* --------------------------------------------------------------------------
   The still image

   ONE RULE FOR BOTH PLACES. The card and the video page draw the same file, so
   they share the sizing: 640px is the widest either ever renders it, which is
   also half of the 1280px source, so it is pixel-exact on a 2x display and never
   upscaled. YouTube's own maxresdefault IS 1280x720 and there is no larger
   source; enlarging it past this would invent detail nothing here has.

   `aspect-ratio` beside the width/height attributes, not instead of them: the
   attributes reserve the box before CSS loads, the property keeps it correct
   when the width is constrained.
   -------------------------------------------------------------------------- */

.video-thumb {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  background: var(--bg-well);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}

/* The link is the image's box exactly, so the focus ring traces the picture
   rather than a line of text beside it. */
.video-thumb-link {
  display: block;
  border-radius: var(--r-md);
}

.video-thumb-link:hover .video-thumb {
  border-color: var(--border-strong);
}

/* --------------------------------------------------------------------------
   The index
   -------------------------------------------------------------------------- */

/* An ordered list, because the running order is the content. Markers off: the
   number is drawn in .episode-index, which is where it can be sized and placed;
   with the stylesheet off the real markers come back and read the same way. */
.episode-list {
  display: grid;
  gap: var(--sp-5);
  margin-top: var(--sp-6);
  padding: 0;
  list-style: none;
}

.episode-card {
  display: grid;
  gap: var(--sp-2) var(--sp-5);
  padding: var(--sp-6);
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
}

/* The running number, aria-hidden because the <ol> already carries the order for
   anything that reads the tree. It is a visual anchor down the left edge, not a
   second announcement of the same fact.

   --fg-dim, NOT --border-strong. It was written with the border token and measured
   1.94:1 light and 1.82:1 dark in a browser: a 32px number a sighted reader would
   try to read and could not. Being aria-hidden makes it decoration in the standard's
   terms and therefore exempt, which is an argument for shipping something unreadable
   rather than a reason to. --fg-dim clears 4.5:1 on both card surfaces
   in both themes, and tokens.css declares it for text. */
.episode-index {
  margin: 0;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--step-3);
  font-weight: 600;
  line-height: 1;
  color: var(--fg-dim);
}

.episode-body {
  min-width: 0;
}

.episode-title {
  margin: 0;
  font-size: var(--step-2);
  line-height: 1.25;
  letter-spacing: var(--track-sub);
  color: var(--fg-strong);
}

.episode-title a {
  color: inherit;
  text-decoration: none;
}

.episode-title a:hover,
.episode-title a:focus-visible {
  text-decoration: underline;
}

/* Date and duration read as one line of metadata, so they share a colour and a
   size and are separated by a middot rather than by punctuation that would look
   like part of the title. With neither known yet it carries one phrase instead. */
.episode-meta,
.video-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  margin: var(--sp-2) 0 0;
  font-size: var(--step--1);
  color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
}

.episode-dot {
  color: var(--border-strong);
}

.episode-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-top: var(--sp-5);
}

/* The still sits between the running number and the text, so the card reads
   number, picture, words down a phone and across a desktop. */
.episode-card .video-thumb-link,
.episode-card > .video-thumb {
  max-width: 320px;
}

@media (min-width: 560px) {
  .episode-card {
    grid-template-columns: auto minmax(0, 1fr);
  }

  /* The number keeps its own column; the still and the body share the second,
     which is what stops a 320px picture squeezing the title and meta to nothing. */
  .episode-card .video-thumb-link,
  .episode-card > .video-thumb {
    grid-column: 2;
  }
}

@media (min-width: 860px) {
  .episode-card {
    grid-template-columns: auto 280px minmax(0, 1fr);
    align-items: start;
  }

  .episode-card .video-thumb-link,
  .episode-card > .video-thumb {
    grid-column: 2;
    max-width: none;
  }

  .episode-body {
    grid-column: 3;
  }
}

/* --------------------------------------------------------------------------
   One video: what was said, and the code that was on screen
   -------------------------------------------------------------------------- */

.video-poster {
  max-width: 640px;
  margin: var(--sp-8) 0 0;
}

.video-actions {
  margin-top: var(--sp-6);
}

/* NOT a warning and NOT an apology: a statement of fact about a video that has
   not been posted. The site's warning vocabulary is ink on a well with an icon,
   and reaching for it here would dress a neutral fact as a problem. */
.video-pending {
  max-width: var(--measure);
  margin-top: var(--sp-6);
  padding-left: var(--sp-5);
  border-left: 3px solid var(--border-strong);
  color: var(--fg-muted);
}

.video-body {
  margin-top: var(--sp-10);
}

.video-section {
  margin: var(--sp-12) 0 var(--sp-5);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--border);
  font-size: var(--step-2);
  letter-spacing: var(--track-sub);
  color: var(--fg-strong);
}

.video-section:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

/* A figure's caption must sit closer to its own block than to the sentence after
   it, or it reads as a heading for that sentence. .code-figure carries a top
   margin and no bottom one, which is fine on a page where a figure is followed by
   a .section-body with its own; here it is followed by a transcript paragraph that
   starts flush, and the caption ended up glued to the wrong side. */
.video-body .code-figure {
  margin-bottom: var(--sp-10);
}

.transcript-para,
.transcript-list {
  max-width: var(--measure);
  margin: 0 0 var(--sp-5);
}

.transcript-list {
  padding-left: var(--sp-5);
}

.transcript-list li {
  margin-bottom: var(--sp-3);
}

/* A correction sits under the sentence it corrects. Since 2026-09-06 it is the
   owner's own words about his own transcript, written in the first person, and it
   may only exist with his explicit permission - gen_videos.py refuses one without a
   `permission:` line. The box is here so a reader can see it is not part of what was
   said. Clay, because the thing being corrected is a figure that does not hold. */
.transcript-note {
  max-width: var(--measure);
  margin: 0 0 var(--sp-5);
  padding: var(--sp-4) var(--sp-5);
  background: var(--bg-well);
  border-left: 3px solid var(--clay-solid);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  font-size: var(--step--1);
}

.transcript-note > p {
  margin: 0;
  color: var(--fg);
}

/* Child selector on both, so this outweighs the rule above on class count rather
   than on !important. `.transcript-note p` is (0,1,1) and a bare
   `.transcript-note-label` is (0,1,0), so the reset would have won and the label
   would have sat on the text. */
.transcript-note > .transcript-note-label {
  margin-bottom: var(--sp-1);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--track-eyebrow);
  color: var(--fg-strong);
}

.video-back {
  margin-top: var(--sp-12);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--border);
}
