@font-face{font-family:Archivo;font-style:normal;font-weight:400 800;font-stretch:62% 125%;font-display:swap;src:url("archivo-latin.e3a28ead.woff2") format("woff2");unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:Archivo;font-style:normal;font-weight:400 800;font-stretch:62% 125%;font-display:swap;src:url("archivo-latin-ext.5717f370.woff2") format("woff2");unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
/*
 * Showimo
 *
 * The wall is finite. That is the whole idea, and the layout says so: time is
 * the spine, the collection ends, and you can take it in.
 *
 * The spine is the ORDER ITSELF. It used to be four marks over four grids --
 * 24H / 7D / 30D / 30D+ -- and the marks went, because they were a second,
 * coarser rendering of an age every card already prints, and because they put
 * a heading over the slow, careful channels this wall exists for.
 *
 * Two rules govern everything here.
 *
 * 1. THE PALETTE IS LANGLESS'S, and it is the Apple system palette: pure
 *    black, the iOS elevation greys, and the iOS accent hues. Sampled from
 *    app.langless.com rather than eyeballed, so the two products read as
 *    siblings. Do not drift the values.
 *
 *    Within that palette the old discipline still holds: SATURATED COLOUR
 *    CARRIES MEANING, NEVER DECORATION. The greys and white do the work; a
 *    hue appears only when it says something.
 *      --play    this plays here, in place
 *      --brand   Langless, and only Langless
 *      --focus   where the keyboard is
 *      --signal  something is wrong
 *
 *    `--accent` used to sit where `--brand` is, and its documented meaning was
 *    "the section you are in". It was painting the Langless call to action six
 *    pixels from a blue Langless bubble, and every focus ring on the site. The
 *    token was lying about all three. Renaming it is the whole of the fix: no
 *    value changed, and now the comment is true.
 *
 *    THERE IS NO PER-SECTION HUE, and that was measured rather than assumed.
 *    A section hue would land on exactly one element -- the current tab -- on
 *    one of the two screens; the countdown ring and the switch live on the
 *    watch screen, where there is no tab bar to say what the colour refers to.
 *    Langless has three colours, green is taken and blue is theirs, so four
 *    sections would need three invented hues and the product would read LESS
 *    like Langless. And in the light theme the orange fails 1.4.11 at 1.97:1 as
 *    a pill fill, as its own glyph and as a ring -- so the one hue that was
 *    asked for is the one that cannot do the job. See 24.19.
 *
 * 2. LOGICAL PROPERTIES ONLY -- margin-inline-start, inset-inline-end,
 *    text-align: start. Never left/right. An Arabic or Hebrew interface
 *    mirrors the whole chrome, and in physical properties that is a rewrite.
 *    Enforced by test/lint-css.test.js.
 */

:root {
  color-scheme: dark;

  /* Langless tokens, sampled from the running app. */
  --ink: #000;
  --surface: #1c1c1e;
  --lift: #2c2c2e;
  --edge: rgb(255 255 255 / 0.08);

  --paper: #fff;
  --muted: #aeaeb2;
  --dim: #8e8e93;

  --play: #30d158;
  --brand: #0a84ff;
  /* The maximum contrast available in either theme, unconditionally: white on
     black and black on white, 21:1. A focus ring is safety equipment and must
     never be prettier than it is findable. */
  --focus: var(--paper);
  --signal: #ff9f0a;

  /*
   * The hairline carrying the time spine. It used --edge, a BORDER token at 8%,
   * which composites to about #141414 on black -- functionally invisible --
   * while the newest band used --dim and had roughly forty times the contrast.
   * A rule is a mark, not an edge.
   */
  --rule: rgb(255 255 255 / 0.22);

  --gap: clamp(0.85rem, 1.6vw, 1.35rem);
  --radius: 16px;
  --radius-s: 10px;

  /*
   * Langless's own stack, with Archivo standing in where SF Pro Rounded does
   * not exist. On an Apple device this is pixel-for-pixel the same type as
   * Langless; everywhere else it is Archivo rather than whatever the system
   * happens to ship, so the page stays consistent instead of falling back to
   * Segoe UI.
   */
  --display: -apple-system, BlinkMacSystemFont, "SF Pro Rounded", "SF Pro Display",
    Archivo, system-ui, sans-serif;
}

/*
 * Light, in the Apple light system colours -- the counterpart of the dark set
 * above, so the two themes are siblings rather than one being an inversion of
 * the other.
 *
 * Three states, not two. "System" is the default and has no attribute: it
 * follows the operating system, which is what someone who has never touched
 * the control should get. An explicit choice sets data-theme and overrules it
 * in both directions.
 */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    color-scheme: light;

    /*
     * Elevation, the way Apple does it in light: the GROUND is grey and the
     * things sitting on it are white. It was inverted -- a white page with grey
     * cards -- so every card receded BELOW the page, and the masthead buttons
     * became a grey disc on white defined only by a 10% hairline.
     */
    --ink: #f2f2f7;
    --surface: #fff;
    --lift: #f7f7fa;
    --edge: rgb(0 0 0 / 0.1);

    --paper: #000;
    --muted: #6c6c70;
    /*
     * The one token that was NOT re-mapped per theme. Byte-identical to dark,
     * #8e8e93 is 3.26:1 on a light ground -- under AA for the 0.64rem codes it
     * carries -- and a stale card multiplies it by 0.62, landing near 1.8:1.
     */
    --dim: #6e6e73;

    --play: #34c759;
    /*
     * NOT #007aff, AND ONLY IN THE LIGHT THEME. This is the one place in the
     * palette that deliberately drifts from what was sampled at Langless's own
     * app, and the owner made the call with both numbers in front of him.
     *
     * #007aff is 3.60:1 on this ground and 4.02:1 where the link actually sits,
     * on a white card. AA text needs 4.5:1. So the Langless link -- the
     * commercial point of this entire wall -- failed contrast on every page,
     * twice, in the theme half the visitors arrive in. Not at an edge case: on
     * every surface it touches.
     *
     * #0040dd is Apple's own high-contrast variant of the same system blue:
     * 7.56:1 on white, 6.78:1 on this ground. It stays inside "the Apple system
     * palette" while no longer matching Langless pixel for pixel.
     *
     * The dark theme keeps #0a84ff, which passes, so the two products still
     * read as siblings wherever the sampling was actually load-bearing.
     */
    --brand: #0040dd;
    --focus: var(--paper);
    --signal: #ff9500;

    --rule: rgb(0 0 0 / 0.16);
  }
}

:root[data-theme="light"] {
  color-scheme: light;

  /*
   * Elevation, the way Apple does it in light: the GROUND is grey and the
   * things sitting on it are white. It was inverted -- a white page with grey
   * cards -- so every card receded BELOW the page, and the masthead buttons
   * became a grey disc on white defined only by a 10% hairline.
   */
  --ink: #f2f2f7;
  --surface: #fff;
  --lift: #f7f7fa;
  --edge: rgb(0 0 0 / 0.1);

  --paper: #000;
  --muted: #6c6c70;
  /*
   * The one token that was NOT re-mapped per theme. Byte-identical to dark,
   * #8e8e93 is 3.26:1 on a light ground -- under AA for the 0.64rem codes it
   * carries -- and a stale card multiplies it by 0.62, landing near 1.8:1.
   */
  --dim: #6e6e73;

  --play: #34c759;
  /* Apple's high-contrast blue, for the reason spelled out in the
     prefers-color-scheme block above: #007aff is 4.02:1 on a white card and
     the Langless link needs 4.5. Explicit light and system light must agree. */
  --brand: #0040dd;
  --focus: var(--paper);
  --signal: #ff9500;

  --rule: rgb(0 0 0 / 0.16);
}

/* --- the control cluster ------------------------------------------------ */

.controls {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.icon-btn {
  inline-size: 2.75rem;
  block-size: 2.75rem;
  flex: none;
  display: grid;
  place-items: center;
  border: 1px solid var(--edge);
  border-radius: 50%;
  background: var(--surface);
  color: var(--muted);
  cursor: pointer;
  padding: 0;
  font: inherit;
  transition:
    background 0.15s ease,
    color 0.15s ease;
}

.icon-btn:hover {
  background: var(--lift);
  color: var(--paper);
}

.icon-btn:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* --- density ------------------------------------------------------------ */

/*
 * Three profiles, and the last one is the point: at "minimal" the wall carries
 * no words at all. That is not a stripped-down fallback, it is the product's
 * own claim turned on itself -- if you can follow a video without knowing the
 * language, you should be able to browse without reading either.
 */
/*
 * ESSENTIAL KEEPS THE AGE, and it did not always.
 *
 * It used to drop it, on a reason written here in as many words: "the age is
 * still legible from the band heading above." When the band heads went, that
 * sentence stopped being true and essential became a wall with NO TIME
 * INFORMATION ANYWHERE -- on a collection whose only organising claim is
 * recency. The owner chose to restore it rather than keep a timeless density.
 *
 * So essential is now "full, minus the channel name and the stale note". That
 * is a thinner identity than it had, and it is the honest one: the alternative
 * was a profile that hid the single fact the wall is sorted by.
 */
/*
 * A density profile hides words FROM THE SCREEN, never from a screen reader.
 *
 * These rules used to be `display: none`, and that was the exact trap the
 * comment below describes having avoided for the tabs -- committed four lines
 * above the comment describing it. `display: none` removes a subtree from the
 * accessibility tree too, so:
 *
 *   - `.channel span` matched the AVATAR span as well as the name, leaving an
 *     anchor in the tab order at 0x0 with no accessible name at all. Measured,
 *     not deduced.
 *   - hiding `.body` at minimal took the title, the channel and the age's
 *     spoken sentence with it: every card announced as "article, EN, 14:03".
 *   - hiding `.stale` removed the REASON a card is dimmed while
 *     `.card--stale` kept dimming it -- and the comment on that rule argues
 *     the dimming is safe precisely because the reason survives for a reader.
 *     At two densities out of three it did not.
 *
 * Clipping instead of removing keeps every one of those in the accessibility
 * tree. It is the same technique as `.sr-only`, which is the point: at minimal
 * the wall carries no words, and a screen reader still hears all of them.
 */
/*
 * ESSENTIAL TAKES THE WHOLE CHANNEL ROW, AVATAR INCLUDED, and until it did it
 * was not a density at all.
 *
 * Measured at 1440px: full gave a 316x298 card and an 8919px grid over 109
 * cards, and essential gave a 316x298 card and an 8919px grid over 109 cards.
 * Byte-identical. It clipped the channel NAME and the stale note, and there
 * are zero stale channels on the live wall, so its entire visible effect was
 * removing a word and leaving a 20px avatar alone on its own full-height line
 * with nothing beside it -- which reads as an image that failed to load.
 *
 * On a wordless three-position control with no labels, that made the first
 * press look like a dead button, and the good position (minimal, -60%) sit two
 * presses away behind it.
 *
 * Clipping `.channel` rather than its children takes the row out of flow, so
 * the card is genuinely shorter and the middle position is genuinely in the
 * middle. The link stays in the accessibility tree, exactly as the whole
 * `.body` already does at minimal -- same technique, same reason.
 */
[data-density="essential"] .channel,
[data-density="essential"] .stale,
[data-density="minimal"] .body,
[data-density="minimal"] .stale {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/*
 * The rule that used to hide tab labels at minimal density has gone, and so has
 * its :has() guard against a section with no emoji. Tabs carry a mark and no
 * label at every density now, and src/icons.js is validated at build time, so
 * neither the exception nor the guard has anything left to protect.
 *
 * It was also actively dangerous once the labels moved into .sr-only spans:
 * `span:not(.tab-icon) { display: none }` would have hidden them from screen
 * readers too, leaving every tab nameless at exactly one density setting.
 */

[data-density="minimal"] .grid {
  grid-template-columns: repeat(auto-fill, minmax(min(230px, 100%), 1fr));
}

/*
 * Gated, like the full-density version. Ungated it spanned two columns at every
 * width, and at 390px the grid is a single column -- so a span-2 item forced an
 * implicit second track and the wall overflowed the phone.
 */
/*
 * The minimal-density lead rule has gone: `.card--lead { grid-column: span 2 }`
 * already sits in the same media query and applies at every density, so this
 * was a no-op that read as though minimal were different.
 */

* {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  padding-inline: clamp(1rem, 4vw, 3rem);
  /* NOTHING AT THE BOTTOM. The 4rem that used to sit here dated from when the
     page ended in a row of cards and needed air under them. The page ends in
     the colophon now, which brings its own, and the two stacked into about
     105px of black below the last line -- which is what the owner saw. */
  padding-block: clamp(1.25rem, 3vw, 2.25rem) 0;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--display);
  font-size: 16px;
  line-height: 1.45;
  font-synthesis: none;
}

#app {
  max-inline-size: 1440px;
  margin-inline: auto;
}

/* Utility voices, both cut from the one variable font's width axis. --------- */

/* Condensed uppercase: labels, codes, anything that is data rather than prose. */
.code {
  font-stretch: 72%;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-variant-numeric: tabular-nums;
}

/*
 * The two masthead clocks. Glyph plus value, not glyph alone: the mark says
 * which question is being answered and the code answers it.
 *
 * They sit a few centimetres from two round icon buttons of similar weight, so
 * they are deliberately squarer, dimmer and unpadded -- nothing here should
 * look like it can be pressed.
 */
.stamp {
  display: inline-flex;
  align-items: center;
  gap: 0.34rem;
  font-size: 0.74rem;
  color: var(--dim);
}

.stamp svg {
  opacity: 0.75;
}

.sr-only {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Masthead ---------------------------------------------------------------- */

.masthead {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.8rem;
  padding-block: 0 0.75rem;
  border-block-end: 1px solid var(--edge);
}

/*
 * `auto`, taking over from the clock that used to sit between the wordmark and
 * the buttons. Two grey time codes lived there -- a download mark and 3H, a
 * refresh mark and 3H -- and the owner cut them: the same number twice, at the
 * top of every page, on a wall whose whole claim is that it needs no chrome.
 *
 * A whole @media block went with them. It existed only because at 390px the
 * disc, the wordmark and the two clocks came to about 283px, so the buttons no
 * longer fitted and flex wrapped them onto a row nobody had placed. Without the
 * clocks the row fits, and the phone layout is the desktop one.
 */
.masthead .controls {
  margin-inline-start: auto;
}

/*
 * The same green disc that means "plays here" on every card, standing in
 * front of the name. The mark and the affordance are one shape, so the brand
 * is the thing the product does.
 */
/*
 * The disc is the link home. It was a bare span until the owner asked for it to
 * go somewhere, so it now needs a link's affordances: a cursor that admits it
 * is clickable, no underline, and a focus ring a D-pad can land on.
 */
.logomark {
  inline-size: 1.9rem;
  block-size: 1.9rem;
  flex: none;
  border-radius: 50%;
  background: var(--play);
  /* Black, for the same reason the card discs are: #fff on --play is 2.02:1
     and this mark ships on every page. It was the 54th instance of the
     failure and the only one not on a card. */
  color: #000;
  display: grid;
  place-items: center;
  padding-inline-start: 0.1rem;
  align-self: center;
  box-shadow: 0 0 0 5px color-mix(in oklab, var(--play) 13%, transparent);
  cursor: pointer;
  text-decoration: none;
}

.logomark:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
}

.wordmark {
  font-size: clamp(1.6rem, 3.4vw, 2.35rem);
  font-stretch: 118%;
  font-weight: 800;
  letter-spacing: -0.015em;
  line-height: 1;
  margin: 0;
}

/* Sections ---------------------------------------------------------------- */

.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  padding-block: 1rem 0;
  margin-block-end: 0.6rem;
}

/*
 * A tab is a mark and nothing else, so the box has to be built rather than
 * inherited from the text that used to fill it. Without a size of its own it
 * collapsed to a 16px target -- under half the 44px minimum, and on a remote
 * the focus ring would have outlined almost nothing.
 */
.tab {
  display: grid;
  place-items: center;
  /* 44px. The previous 2.6rem was 41.6px, under the minimum this rule's own
     earlier comment cited. */
  inline-size: 2.75rem;
  block-size: 2.75rem;
  border-radius: 999px;
  color: var(--dim);
  text-decoration: none;
  transition:
    color 0.15s ease,
    background 0.15s ease;
}

/*
 * The outline is separate from the hover state on purpose. .tab.current sets a
 * background at the same specificity and is declared later, so focusing the
 * current tab produced NO visual change at all -- and on a non-current tab the
 * indicator was a background shift of 1.2:1, well under the 3:1 a focus ring
 * needs. Measured, not assumed.
 */
.tab:focus-visible,
.back:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.tab:hover,
.tab:focus-visible {
  color: var(--paper);
  background: var(--surface);
}

/*
 * A filled pill, not the 2px underline this used to be. With the label gone the
 * underline had nothing to underline and shrank to a stub beneath a glyph.
 */
/*
 * THE PILL IS NEUTRAL, BECAUSE --brand HAS TO MEAN ONE THING.
 *
 * The comment at the top of this file says `--brand` is "Langless, and only
 * Langless". It was not true: five uses, and three of them -- this pill, the
 * countdown ring and the autoplay switch -- have nothing to do with Langless.
 * The rename that was supposed to fix this declared itself finished without
 * splitting anything.
 *
 * On this pill it was the expensive one. The current-section marker and the
 * link that sells the other product sat a few centimetres apart in the same
 * masthead wearing the same #007aff, so the one saturated hue in the chrome
 * said "you are here" and "this is a different product" in one glance.
 *
 * --paper instead: 21:1 in dark, 18.4:1 in light, and no hue at all. This is
 * the same subtraction the play disc just went through -- the answer to "more
 * colour" is fewer things competing with the photographs, not more paint on
 * the frame.
 */
.tab.current {
  color: var(--ink);
  background: var(--paper);
}

/*
 * No grayscale filter here any more. That hack existed because emoji ignore
 * `color`; an SVG path inherits currentColor, so dimming an inactive tab is now
 * an actual colour change rather than a desaturated picture.
 */
.tab-icon {
  display: grid;
  place-items: center;
  line-height: 1;
}

/* The wall. ------------------------------------------------------------- */

/*
 * One grid, newest first. The four band heads that used to break this up are
 * gone -- see the note at the top of this file. What remains is a named section
 * carrying one visually-hidden heading, which is a rotor stop for a screen
 * reader and nothing at all for anyone else.
 */
.wall {
  padding-block-start: 1.1rem;
}

/*
 * The lead card: the newest video on the page runs double width, always.
 *
 * The page's whole claim is "here is what just landed", and a uniform grid
 * states that only through position, which is the weakest way to say anything.
 *
 * ALWAYS, not "only when it is fresh" -- the owner's decision, taken against a
 * version that withheld the lead from a section with nothing published today.
 * The cost is that a quiet section presents a fortnight-old video double-width.
 * What keeps that honest is the age printed on the card itself, which is why
 * `essential` had its age restored in the same change: the lead may be old, but
 * it is never silent about being old.
 */
@media (min-width: 690px) {
  /*
   * THE BLOCK IS THE GRID ITEM, not the lead card. Two columns because a wall's
   * newest video should be twice the size of the rest; two rows because two
   * ordinary cards stack beside it. The lead alone never filled those two rows
   * -- a 16:9 image twice as wide is not twice as tall -- so its channel's other
   * videos fill the remainder as a strip beneath it.
   */
  .lead-block {
    grid-column: span 2;
    grid-row: span 2;
    display: flex;
    flex-direction: column;
    gap: var(--gap);
  }

  /*
   * The lead keeps the span too, and the duplication is deliberate. When there
   * is a strip the lead sits inside `.lead-block`, which is a flex container,
   * so these two declarations are simply ignored on it. When there is NO strip
   * -- a channel with a single video holding the newest slot, six of 120 -- the
   * lead is a grid item again and needs them. One rule covers both without a
   * modifier class that both renderers would have to remember to emit.
   */
  .card--lead {
    grid-column: span 2;
    grid-row: span 2;
  }

  /*
   * Two columns, always, whatever the strip holds. The scan keeps three videos
   * per channel, so a lead has at most TWO siblings -- 110 channels of 120 have
   * exactly two, four have one and six have none. With one, half the strip is
   * empty; with none there is no block at all, because a lead with nothing to
   * fill its second row is better off as an ordinary card.
   *
   * THE THUMBNAILS KEEP 16:9. Sizing them to the leftover height would crop
   * them, and cropping is precisely what this exercise refused to do to the
   * hero; doing it to the strip for tidiness would be the same mistake one size
   * down. The block ends up slightly TALLER than two ordinary rows, so the rows
   * grow and the leftover lands as small even gaps under the ordinary cards
   * rather than as one hole under the lead.
   */
  .lead-strip {
    display: grid;
    /*
     * THREE COLUMNS FOR AT MOST TWO THUMBNAILS, and the odd-looking number is
     * arithmetic rather than taste.
     *
     * The block has to come out the same height as the two ordinary rows it
     * spans, and the budget left under the lead is only about 91px: the lead's
     * image is twice as wide as an ordinary card's and therefore twice as tall
     * (387px), but its body does not double, so the lead lands ~117px short of
     * two stacked cards. Measured at 1512px, four widths:
     *
     *     2 columns  thumb 332x187   block overshoots by 97px
     *     3 columns  thumb 214x120   overshoots by 30px
     *     4 columns  thumb 155x87    overshoots by -3px
     *     5 columns  thumb 119x67    overshoots by -23px
     *
     * Four is the exact fit and it was tried and rejected by looking: at 155px
     * the thumbnails are too small to recognise and two of the four slots stand
     * empty. Three leaves 30px, which lands as about 15px under each ordinary
     * card -- the difference between the two gaps the owner circled at 48px and
     * something nobody notices.
     *
     * THE THUMBNAILS KEEP 16:9. Filling the width with two of them would need a
     * 3.6:1 letterbox, half the image gone, and cropping is what this whole
     * exercise refused to do to the hero. One empty slot is the price of not
     * doing it to the strip either.
     */
    grid-template-columns: repeat(3, 1fr);
    gap: var(--gap);
  }

  /* The disc is sized for a 340px card. On a 214px thumbnail it swallows the
     image, so it comes down with everything else. */
  .card--strip .play,
  .card--strip .offsite {
    inline-size: 2.2rem;
    block-size: 2.2rem;
  }

  .card--strip .play svg,
  .card--strip .offsite svg {
    width: 16px;
    height: 16px;
  }

  .card--strip .dur,
  .card--strip .lang {
    font-size: 0.6rem;
  }

  /*
   * A strip card is a card with its words clipped -- the same technique
   * `[data-density="minimal"]` uses on the whole wall, and for the reason given
   * there: clipping keeps the title, the age sentence and the disc's name in
   * the accessibility tree where `display: none` would remove them. On screen a
   * bare thumbnail; to a screen reader a whole card.
   */
  .card--strip .body {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .card--lead .title {
    font-size: 1.15rem;
    -webkit-line-clamp: 3;
  }

  /* Padding scales with the card. 13.6px on a 660px lead read mean. */
  .card--lead .body {
    padding-block: 0.95rem 1.1rem;
    padding-inline: 1.15rem;
  }

  .card--lead .channel {
    font-size: 0.72rem;
  }

  .card--lead .avatar {
    inline-size: 1.7rem;
    block-size: 1.7rem;
  }

  .card--lead .avatar--mono {
    font-size: 0.68rem;
  }

  .card--lead .lang {
    font-size: 0.82rem;
    padding-block: 0.4rem 0.36rem;
    padding-inline: 0.7rem 0.75rem;
  }

  .card--lead .ai {
    font-size: 0.78rem;
    padding-block: 0.36rem 0.32rem;
    padding-inline: 0.5rem;
  }

  .card--lead .play {
    inline-size: 4.4rem;
    block-size: 4.4rem;
  }
}

/* Grid -------------------------------------------------------------------- */

.grid {
  display: grid;
  /*
 * min() on the floor, not a bare px value. At 320px the content box is 289px,
 * so a hard 310px track overflowed the viewport by 6px and the whole wall
 * scrolled sideways. It also fires at 200% browser font size on a wider phone.
 */
  grid-template-columns: repeat(auto-fill, minmax(min(310px, 100%), 1fr));
  gap: var(--gap);
  /* Each card is its own height. Stretching them to match the lead card left
     a dead panel under every short title. */
  align-items: start;
}

.card {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--edge);
  border-radius: var(--radius);
  overflow: hidden;
  cursor: pointer;
  transition:
    background 0.16s ease,
    border-color 0.16s ease;
}

.card:hover {
  background: var(--lift);
  /* color-mix off --paper, not a hardcoded brown. #3a322b was in neither the
     Apple palette this file forbids drifting from nor any theme. */
  border-color: color-mix(in oklab, var(--paper) 22%, transparent);
}

.card:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.media {
  position: relative;
  aspect-ratio: 16 / 9;
  background: #000;
  overflow: hidden;
}

.thumb {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.4s cubic-bezier(0.2, 0.7, 0.3, 1);
}

.card:hover .thumb {
  transform: scale(1.03);
}

/*
 * THE SIGNATURE. The language code, stencilled on the crate.
 *
 * This collection spans 25 languages. The interface needs no language; the
 * CONTENT does, and the stamp is where the page says so. So the language is not
 * a warning tucked in a corner; it is the motif, stamped on every card, and the
 * wall reads as a departures board from everywhere.
 *
 * The premise written here used to be "you do not need any of them -- you watch
 * someone's hands", which was the withdrawn tagline (§21.1) standing as the
 * STATED JUSTIFICATION for the stamp. So the one element that most directly
 * points at what Langless sells was, in the code, argued for on the grounds
 * that nobody needs Langless. The second sentence was always right and is kept
 * word for word; only the premise under it was wrong.
 */
.stamps {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  display: flex;
  align-items: stretch;
}

.lang {
  padding-block: 0.32rem 0.3rem;
  padding-inline: 0.55rem 0.6rem;
  font-stretch: 66%;
  font-weight: 800;
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  color: var(--ink);
  background: var(--paper);
  border-end-end-radius: 8px;
}

/*
 * The AI stamp. One-way signal: it appears only when the uploader declared
 * altered or synthetic content. There is deliberately no counterpart saying a
 * video is human-made -- YouTube never tells us that, and undisclosed
 * synthetic content would end up wearing the badge of authenticity.
 *
 * Outlined rather than filled, and not in --signal: a disclosure is
 * information, not an alarm.
 */
.ai {
  margin-inline-start: 0.3rem;
  align-self: start;
  padding-block: 0.28rem 0.26rem;
  padding-inline: 0.42rem;
  font-stretch: 66%;
  font-weight: 800;
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  /*
   * Pinned white for the same reason as .dur: the scrim behind it is a fixed
   * dark panel over the thumbnail, not a themed surface, so a themed foreground
   * turns black-on-black in the light theme. Found by looking at a light-theme
   * screenshot, which is the only way either of these two ever shows up.
   */
  color: #fff;
  background: rgb(16 14 12 / 0.82);
  border: 1px solid rgb(255 255 255 / 0.9);
  border-radius: 4px;
}

/*
 * The same chip the card carries. It used to be a sentence here and a badge
 * there -- one disclosure spelled two ways, and the sentence was the half that
 * had to go.
 */
.ai-note {
  display: flex;
  margin-block: 0.7rem 0;
}

.ai-note .ai {
  margin-inline-start: 0;
}

.dur {
  position: absolute;
  inset-block-end: 0.45rem;
  inset-inline-end: 0.45rem;
  padding-block: 0.1rem;
  padding-inline: 0.35rem;
  border-radius: 3px;
  background: rgb(0 0 0 / 0.78);
  /*
   * Not var(--paper). The background here is a fixed black scrim over the
   * thumbnail rather than a themed surface, so the text has to be fixed too --
   * in the light theme --paper is #000, and the badge was black on black. Same
   * reason .play and .logomark pin #fff a few rules up; this one was missed.
   */
  color: #fff;
  font-size: 0.7rem;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

/*
 * Centred, because it is the card's verb. In the corner it read as metadata
 * alongside the duration; in the middle it reads as the thing to do.
 */
.play,
.offsite {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  /*
   * transform, not the individual `translate` property -- those two COMPOSE per
   * Transforms L2, which is how an earlier version moved the disc a further
   * -50%/-50% on hover.
   *
   * THE TRAP THIS LEAVES, AND IT BIT: `transform` is one property, so any rule
   * that sets it MUST RE-STATE THIS TRANSLATE or the disc loses its centring
   * and jumps by half its own size. The .offsite hover does; the .play hover
   * did not, for as long as this comment claimed both did. The owner reported
   * it as "the green play jumps a lot when I hover". test/lint-css.test.js now
   * holds every rule that touches these two to it.
   */
  transform: translate(-50%, -50%);
  display: grid;
  place-items: center;
  transition:
    transform 0.18s cubic-bezier(0.2, 0.7, 0.3, 1),
    box-shadow 0.18s ease;
}

/*
 * ONE GREEN DISC ON THE PAGE, AND IT FOLLOWS THE POINTER.
 *
 * This rule used to paint every play disc `--play` with a white glyph. Two
 * measurements ended that:
 *
 *   1. #fff on --play is 2.02:1 in dark and 2.22:1 in light. The floor for a
 *      non-text UI component is 3:1 (WCAG 1.4.11). A BLACK glyph on the same
 *      green is 10.39:1 dark and 9.46:1 light. Nothing rescued it -- no
 *      stroke, no shadow, no paint-order anywhere near this rule. It was the
 *      most-repeated control on the site and the only one that failed.
 *   2. Fifty-three of them covered 6.06% of the visible thumbnail area, mostly
 *      landing on the face or the object the thumbnail is about. Measured on
 *      /craft/ at minimal density, 1440x900: 22 discs, 52,702px of green over
 *      870,249px of photograph.
 *
 * The second number is why the answer is subtraction rather than a recolour.
 * The owner asked for MORE COLOUR; the largest saturated field on his site was
 * fifty-three copies of a fact true of every card, printed over the only real
 * colour the page has. Removing fifty-two of them puts more colour on screen.
 *
 * It also makes the file's own rule true again. "Saturated colour carries
 * meaning, never decoration" -- `--play` means "this plays here", and asserted
 * identically everywhere it had stopped being a signal and become wallpaper.
 * Now exactly one disc is green: the one that will play if you click.
 *
 * The scrim is a LITERAL dark, not `--ink`. The backdrop is a thumbnail, not a
 * themed surface -- the same reason .dur, .ai and .logomark pin #fff. A themed
 * value here goes light-on-light the moment the theme flips.
 *
 * 62%, and the number is a floor rather than a taste. Worst case is a pure
 * white thumbnail underneath: #fff on the resulting #616161 is 6.21:1, which
 * clears the 3:1 component floor and the 4.5:1 text floor as well. Anything
 * darker behind it only improves. At 50% it would be 3.95 and would miss AA.
 */
.play {
  inline-size: 3.4rem;
  block-size: 3.4rem;
  border-radius: 50%;
  background: rgb(0 0 0 / 0.62);
  /* No backdrop-filter. It was here for a glass feel and it is the one thing
     in this rule that buys nothing measurable: the 6.21:1 floor comes from the
     62% alone. Fifty-three blurred backdrops is a real cost on the weakest
     device this site is meant to run on, which is a TV box. */
  color: #fff;
  padding-inline-start: 0.16rem;
  /* No resting halo. The old one lit every disc "so it reads as lit even when
     the pointer is elsewhere" -- fifty-three green auras, the same decoration
     argument at lower opacity. The halo is now the tell that the green has
     arrived, which is the only moment it says anything. */
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.5);
}

/*
 * At rest the green sits on the lead: the newest thing, and the answer to
 * "where do I start". `:not(:has(...))` rather than a release rule on the
 * lead, so the plain hover rule below wins on the lead itself without a
 * specificity fight -- when anything is under the pointer this simply stops
 * matching.
 */
.grid:not(:has(.card:hover, .card:focus-visible)) .card--lead .play {
  background: var(--play);
  color: #000;
  box-shadow:
    0 0 0 7px color-mix(in oklab, var(--play) 14%, transparent),
    0 8px 24px rgb(0 0 0 / 0.5);
}

/*
 * And it travels. :focus-visible is not decoration here -- without it a
 * keyboard visitor would watch the green sit on the lead for the whole
 * session and never learn that it means anything.
 */
.card:hover .play,
.card:focus-visible .play {
  background: var(--play);
  color: #000;
  /* translate FIRST, then scale: transform replaces, it does not merge, and
     without the translate the disc drops half its height and half its width. */
  transform: translate(-50%, -50%) scale(1.08);
  box-shadow:
    0 0 0 11px color-mix(in oklab, var(--play) 18%, transparent),
    0 8px 28px rgb(0 0 0 / 0.55);
}

/*
 * Same place as the play button, deliberately: one spot answers "what happens
 * if I click this". Different shape and colour, so the answer is legible
 * before the click rather than after it.
 */
.offsite {
  inline-size: 3.4rem;
  block-size: 3.4rem;
  /*
   * A SQUIRCLE, NOT A CIRCLE, AND THE COMMENT ABOVE FINALLY MEANS IT.
   *
   * "Different shape and colour" was half true for as long as it stood: the
   * colour differed and the shape was the same 50% circle as .play. The colour
   * was carrying the whole distinction on its own.
   *
   * Then the play disc stopped being green. Measured on /kids/ in light theme,
   * where all six off-site cards live: .offsite is #000 with a #f2f2f7 glyph
   * and a resting .play is black at 62% with a #fff glyph -- two dark discs
   * with pale glyphs, near enough to be the same object. The tell that a card
   * leaves the site was about to be carried by the dashed border alone.
   *
   * So the shape takes over the job the colour used to do, which is the more
   * robust half anyway: it survives a theme flip, and it survives whatever the
   * thumbnail behind it happens to be.
   */
  border-radius: 32%;
  background: var(--paper);
  color: var(--ink);
  box-shadow:
    /* --ink, not white: in light theme .offsite is a black disc and a white
       halo was the inverse of the .dur and .ai reasoning three rules up. */
    0 0 0 7px color-mix(in oklab, var(--ink) 55%, transparent),
    0 8px 24px rgb(0 0 0 / 0.5);
}

.card:hover .offsite {
  transform: translate(-50%, -50%) scale(1.06);
}

/*
 * A card that leaves the site has to say so before the click, not after.
 * The dashed edge is the tell: everything else on this wall plays in place.
 */
.card:has(.offsite) {
  border-style: dashed;
  /* Same: the dashed edge is the only pre-click tell that a card leaves the
     site, and in light theme a dark brown dash read as a selection marquee. */
  border-color: color-mix(in oklab, var(--paper) 28%, transparent);
}

.body {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  padding-block: 0.75rem 0.85rem;
  padding-inline: 0.85rem;
  text-align: start;
  flex: 1;
}

/*
 * The channel's face. Before this the only way to tell two cards apart was to
 * read the name in 10px caps, which is not how anyone scans a wall.
 *
 * The monogram is not a placeholder waiting for a real avatar: an image URL
 * can always fail, so it is the permanent floor. Monochrome on purpose --
 * fifty tinted discs would fight the thumbnails, which are the actual content.
 */
.avatar {
  inline-size: 1.4rem;
  block-size: 1.4rem;
  flex: none;
  border-radius: 50%;
  overflow: hidden;
  background: var(--lift);
  border: 1px solid var(--edge);
  display: grid;
  place-items: center;
}

.avatar img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  display: block;
}

.avatar--mono {
  font-stretch: 76%;
  font-weight: 800;
  font-size: 0.58rem;
  letter-spacing: 0.04em;
  color: var(--muted);
}

.channel {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-stretch: 80%;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 0.66rem;
  color: var(--muted);
  text-decoration: none;
  align-self: start;
}

.channel:hover,
.channel:focus-visible {
  color: var(--paper);
}

.channel:hover span,
.channel:focus-visible span {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* A single unbroken 200-character token would otherwise be clipped mid-glyph by
   the card's overflow:hidden, and simply overflow on the watch screen. */
.title,
.qtitle,
.nowtitle {
  overflow-wrap: anywhere;
}

.title {
  font-size: 0.95rem;
  font-weight: 550;
  line-height: 1.32;
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/*
 * The age. A code -- 5D, 3H, 2MO -- with the sentence beside it in an .sr-only
 * span. It used to read "5 days ago", and across fifty cards plus the queue
 * that was the largest block of interface language on the site, and the last
 * thing still announcing a language over videos that each have their own.
 */
.when {
  display: block;
  margin-block-start: auto;
  padding-block-start: 0.4rem;
  font-size: 0.68rem;
  color: var(--dim);
}

/*
 * Staleness is carried by the card's condition, not by a warning sentence.
 *
 * A lone triangle with no tooltip -- and tooltips are banned here, because a
 * remote has no pointer -- hands someone an alarm they cannot act on. Dimming
 * says "this one is old" without asking to be decoded, and the mark is only
 * reinforcement. The reason still exists in full for a screen reader.
 */
/*
 * THE DIM IS AIMED NOW, AND IT USED TO SWALLOW THE WARNING.
 *
 * `.card--stale { opacity: 0.62 }` dimmed the whole card -- including the one
 * mark whose job is to say why the card is dimmed. The element reporting the
 * problem was being faded by the problem it reported. Measured, the warning
 * glyph: 4.15:1 in dark and 1.61:1 in light, against a 3:1 floor.
 *
 * And it cannot be undone from inside. `opacity` on an ancestor composites the
 * whole subtree as one group, so `.stale { opacity: 1 }` does nothing at all --
 * a child cannot be more opaque than the group it belongs to. The dim has to
 * stop being applied to the root and start being applied to the parts that
 * should recede, which is what this is.
 *
 * The thumbnail and the words fade; the warning does not. That matches what
 * this rule always claimed -- the comment above it says the mark is "only
 * reinforcement" and the dimming carries the meaning -- except that the mark
 * was fading with everything else. Now: 8.28:1 in dark, 2.20:1 in light.
 *
 * Light still fails 3:1 at 2.20, and no hue swap fixes it: red is WORSE
 * (2.30 light, and it breaks dark by falling from 4.15 to 2.75). What light
 * needs is a darker signal value, which is a palette decision and is 24.19's
 * open question rather than this rule's business.
 */
.card--stale .media,
.card--stale .body > *:not(.stale) {
  opacity: 0.62;
}

.card--stale .thumb {
  filter: saturate(0.45);
}

.stale {
  margin: 0;
  padding-block: 0.4rem 0;
  line-height: 1;
  color: var(--signal);
}

/* Banners, skeletons, empty states ---------------------------------------- */

/*
 * `partial` is a mark, `stopped` keeps its words. The dividing line is whether
 * there is content on screen: partial sits above a wall that is right there, so
 * a glyph has something to point at; stopped means the wall itself may be lying
 * to you, and a symbol cannot say that to a stranger.
 */
/*
 * The reachable retry control. `.error button` is styled and sits on a branch
 * that can no longer run -- the pre-rendered grid means #app is never empty --
 * while THIS button, the one a visitor actually meets when a refresh fails over
 * a good snapshot, was an unstyled browser default inside a designed banner.
 */
.banner button {
  margin-inline-start: 0.7rem;
  font: inherit;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--ink);
  background: var(--paper);
  border: 0;
  border-radius: 5px;
  padding-block: 0.3rem;
  padding-inline: 0.7rem;
  cursor: pointer;
}

.banner button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.banner.partial {
  display: inline-flex;
  align-items: center;
  padding-inline: 0.6rem;
  color: var(--signal);
}

.banner {
  margin-block: 1rem 0;
  padding-block: 0.65rem;
  padding-inline: 0.9rem;
  border: 1px solid color-mix(in oklab, var(--signal) 45%, var(--edge));
  border-inline-start-width: 3px;
  border-radius: 6px;
  background: color-mix(in oklab, var(--signal) 9%, var(--ink));
  color: var(--paper);
  font-size: 0.82rem;
}

.skeleton {
  cursor: default;
  pointer-events: none;
}

.skeleton .media {
  background: var(--lift);
}

/*
 * The card transitions, which nothing guarded.
 *
 * The skeleton's own animation is correctly opt-in below -- it only runs under
 * `no-preference`. But the thumbnail scales over 0.4s and the play disc scales
 * on FOCUS, which on a television fires on every single arrow press.
 */
@media (prefers-reduced-motion: reduce) {
  .card,
  .thumb,
  .play,
  .offsite,
  .tab,
  .icon-btn,
  /* .back arrived on the watch screen after this list was written and animated
     unguarded until a coverage lint went looking. Adding a selector here is not
     something to remember -- test/lint-css.test.js now requires it. */
  .back,
  .qitem {
    transition: none;
  }

  /*
   * Each restored to its OWN base, not a generic reset: .play and .offsite are
   * centred by translate(-50%, -50%) and a blanket `transform: none` would
   * throw both discs into the corner of the thumbnail.
   */
  .card:hover .thumb {
    transform: none;
  }

  .card:hover .play,
  .card:focus-visible .play,
  .card:hover .offsite {
    transform: translate(-50%, -50%);
  }
}

@media (prefers-reduced-motion: no-preference) {
  .skeleton .media {
    animation: breathe 1.6s ease-in-out infinite;
  }

  @keyframes breathe {
    50% {
      opacity: 0.5;
    }
  }
}

.empty,
.error {
  padding-block: 3rem;
  color: var(--muted);
  max-inline-size: 42ch;
}

.error button {
  margin-block-start: 0.75rem;
  font: inherit;
  font-stretch: 82%;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.72rem;
  color: var(--ink);
  background: var(--paper);
  border: 0;
  border-radius: 5px;
  padding-block: 0.5rem;
  padding-inline: 0.9rem;
  cursor: pointer;
}

/* Watch ------------------------------------------------------------------- */

.backbar {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding-block-end: 1rem;
}

/*
 * Sized like the masthead buttons rather than like the text it replaced: a
 * glyph with no label needs a box of its own, and this is the one control on
 * the watch screen a remote has to be able to land on.
 */
.back {
  display: grid;
  place-items: center;
  inline-size: 2.4rem;
  block-size: 2.4rem;
  background: none;
  border: 1px solid var(--edge);
  border-radius: 999px;
  color: var(--muted);
  cursor: pointer;
  padding: 0;
  transition:
    color 0.15s ease,
    background 0.15s ease;
}

.back:hover,
.back:focus-visible {
  color: var(--paper);
  background: var(--surface);
}

.watch {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
}

@media (min-width: 940px) {
  .watch {
    grid-template-columns: minmax(0, 2.3fr) minmax(300px, 1fr);
  }
}

.frame {
  /* The countdown overlay anchors to this. Everything else about the frame is
     unchanged; the overlay is a SIBLING of the iframe, because re-parenting an
     iframe reloads it. */
  position: relative;
  aspect-ratio: 16 / 9;
  background: #000;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--edge);
}

.player {
  inline-size: 100%;
  block-size: 100%;
  border: 0;
}

.nowtitle {
  font-size: clamp(1.05rem, 2vw, 1.35rem);
  font-weight: 650;
  line-height: 1.25;
  margin-block: 1rem 0.4rem;
  text-align: start;
}

/*
 * A row, not a run of inline spans. The badge and the age used to be built into
 * one template literal with " · " between them; splitting them apart left
 * three elements butted together with nothing to separate them, and the corner
 * radius the badge inherits from the card overlay made it worse.
 */
.nowmeta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin: 0;
  color: var(--muted);
  font-size: 0.8rem;
}

/* Away from the card corner it is a plain chip on all four sides. */
.lang--inline {
  border-radius: 4px;
  font-size: 0.64rem;
  padding-block: 0.22rem 0.2rem;
  padding-inline: 0.4rem;
}

.nowmeta a {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  vertical-align: middle;
  color: var(--paper);
  text-decoration: none;
  font-stretch: 82%;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 0.72rem;
}

/* The one reachable interactive element in this file with no focus ring. */
.nowmeta a:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.nowmeta a:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

.queue {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.qitem {
  display: flex;
  gap: 0.7rem;
  background: none;
  border: 1px solid transparent;
  border-radius: 7px;
  padding: 0.4rem;
  cursor: pointer;
  text-align: start;
  color: inherit;
  font: inherit;
  transition: background 0.14s ease;
}

.qitem:hover {
  background: var(--surface);
}

.qitem:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 1px;
}

.qthumb {
  position: relative;
  inline-size: 104px;
  flex: none;
  aspect-ratio: 16 / 9;
  border-radius: 4px;
  overflow: hidden;
  background: #000;
}

.qthumb img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  display: block;
}

.qlang {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  padding-inline: 0.28rem;
  font-stretch: 66%;
  font-weight: 800;
  font-size: 0.58rem;
  letter-spacing: 0.1em;
  color: var(--ink);
  background: var(--paper);
  border-end-end-radius: 4px;
}

.qbody {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-inline-size: 0;
}

/*
 * flex, like its card-side twin .channel. Without it the avatar -- a
 * display:grid block -- sat on its own line ABOVE the channel name, only in the
 * queue and nowhere else on the site.
 */
.qchannel {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-stretch: 80%;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  font-size: 0.62rem;
  color: var(--muted);
}

.qtitle {
  font-size: 0.82rem;
  line-height: 1.3;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.qwhen {
  display: block;
  font-size: 0.64rem;
  color: var(--dim);
}

.nowwhen {
  font-size: 0.7rem;
  color: var(--dim);
}

/* The colophon. ----------------------------------------------------------- */

/*
 * A COLOPHON, NOT A BANNER. No box, no fill, no gradient, no card: a hairline
 * and two lines of text under the wall.
 *
 * The restraint is commercial, not decorative. This page is made of other
 * people's thumbnails and the only original thing on it is the curation -- so a
 * promotional slab under the grid would spend the one asset the site has, to
 * advertise a product whose whole pitch depends on the site being worth
 * trusting.
 *
 * --brand is Langless's own blue, which is where the palette at the top of
 * this file came from. So the one hue down here says the same thing it says
 * everywhere else: a colour appears when it means something.
 */
.colophon {
  margin-block-start: 4rem;
  /* It owns the end of the page now, so this is the whole of the space under
     the last line rather than half of it. */
  padding-block: 1.4rem 2rem;
  border-block-start: 1px solid var(--rule);
  display: grid;
  gap: 0.9rem;
}

/* The claim and the link share a line; the link goes to the far end of it. */
.colophon-top {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.4rem 1.1rem;
}

.colophon-claim {
  margin: 0;
  font-size: 0.8rem;
  line-height: 1.5;
}

/* Centred, as a colophon line is: it belongs to the page, not to a column. */
.colophon-rights {
  margin: 0;
  font-size: 0.72rem;
  color: var(--dim);
  text-align: center;
}

.colophon-cta {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--brand);
  font-size: 0.8rem;
  font-weight: 650;
  text-decoration: none;
  flex: none;
}

.colophon-cta:hover,
.colophon-cta:focus-visible {
  text-decoration: underline;
}

/* -------------------------------------------------------------- countdown --
 *
 * The five seconds between one video ending and the next starting, and the
 * only place in the whole flow where anything can be stopped.
 *
 * THE TARGET IS THE WHOLE PLAYER. A six-year-old can hit a square and cannot
 * hit a 15px glyph, and this is the control that most needs to be hittable by
 * one. It sits INSIDE .frame as a sibling of the iframe -- appending a sibling
 * leaves the iframe alone, where re-parenting it would reload it.
 *
 * No words. A ring and a numeral: wordless.test.js already blesses digits as
 * notation that reads the same in every locale, and there is no "Up next"
 * label anywhere near it. The spoken name and the one-shot announcement carry
 * what the ring cannot.
 */
.countdown {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  border: 0;
  padding: 0;
  cursor: pointer;
  color: var(--paper);
  /* Dark enough to read a ring against, sheer enough that the last frame of
     the video someone just watched is still there behind it. */
  background: color-mix(in oklab, var(--ink) 72%, transparent);
  animation: countdown-in 180ms ease-out;
}

@keyframes countdown-in {
  from { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .countdown { animation: none; }
}

.countdown-ring {
  /* --left runs 1 -> 0 and is set from the clock, never from a tick count:
     timers throttle in a background tab and a counted ring would lie. */
  --left: 1;
  inline-size: 4.5rem;
  block-size: 4.5rem;
  border-radius: 50%;
  grid-area: 1 / 1;
  background: conic-gradient(
    var(--brand) calc(var(--left) * 360deg),
    color-mix(in oklab, var(--paper) 18%, transparent) 0
  );
  /* The hole. mask keeps it a ring without a second element to colour-match
     against a translucent backdrop. */
  mask: radial-gradient(circle, transparent 58%, #000 59%);
}

.countdown-num {
  grid-area: 1 / 1;
  font: 600 1.5rem / 1 var(--display);
  font-variant-numeric: tabular-nums;
}

/*
 * A queue row the chain has already played.
 *
 * Dimmed rather than removed: removing it would make the list jump under
 * whoever is reading it, and the section genuinely still contains that video.
 * Still clickable -- this says "the chain will not come back here", not "you
 * may not go here". The rule it makes readable: the next video is the first
 * row that is not greyed.
 */
.qitem--played {
  opacity: 0.45;
}

.qitem--played:hover,
.qitem--played:focus-visible {
  opacity: 1;
}

/* ----------------------------------------------------------------- switch --
 *
 * The autoplay control. A switch rather than an icon button, and the reason is
 * not taste: theme and density change the page the instant you press them, so a
 * glyph is a label for something you can already see. Autoplay changes nothing
 * until a video ends, which may be forty minutes away -- so the control has to
 * carry its own state, visibly, with nothing to compare it against.
 *
 * Wordless still holds. A knob left or right is not a word, it is a position,
 * and it reads at a glance in any language. The glyph beside it says what the
 * switch is about; the hidden name says both, in words, for a screen reader.
 */
.switch {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0.55rem 0.3rem 0.6rem;
  border: 1px solid var(--edge);
  border-radius: 999px;
  background: var(--surface);
  color: var(--dim);
  cursor: pointer;
  /* Same height as the round buttons it sits beside, so the bar does not
     develop a step in the middle of it. */
  block-size: 2.25rem;
}

.switch:hover { color: var(--paper); }

.switch:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.switch-track {
  inline-size: 2rem;
  block-size: 1.15rem;
  border-radius: 999px;
  background: var(--lift);
  border: 1px solid var(--edge);
  padding: 2px;
  display: flex;
  transition: background 140ms ease;
}

.switch-knob {
  inline-size: 0.85rem;
  block-size: 0.85rem;
  border-radius: 50%;
  background: var(--dim);
  /* margin-inline-start rather than transform: the knob is the only thing that
     moves, and a logical property keeps this correct if the chrome ever runs
     right-to-left. */
  margin-inline-start: 0;
  transition: margin-inline-start 140ms ease, background 140ms ease;
}

.switch--on { color: var(--paper); }
.switch--on .switch-track { background: color-mix(in oklab, var(--brand) 60%, transparent); }
.switch--on .switch-knob {
  margin-inline-start: calc(100% - 0.85rem);
  background: var(--paper);
}

@media (prefers-reduced-motion: reduce) {
  .switch-track,
  .switch-knob { transition: none; }
}

/*
 * The Langless shortcut in the masthead.
 *
 * The same link as the colophon's, at masthead scale: mark, brand, arrow. It is
 * a SHORTCUT and not a pitch -- the claim and the copyright stay at the foot of
 * the page, and repeating them up here would be the site asking twice.
 *
 * `auto` on the start side pushes it and the two controls to the far end
 * together, so the wordmark keeps the whole left of the bar to itself.
 */
.masthead-cta {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-inline-start: auto;
  color: var(--brand);
  font-size: 0.78rem;
  font-weight: 650;
  text-decoration: none;
  white-space: nowrap;
}

/* The controls no longer claim the auto margin; the shortcut sits before them
   and takes it, and they follow at a fixed gap. */
.masthead .masthead-cta + .controls {
  margin-inline-start: 0.7rem;
}

.masthead-cta:hover { text-decoration: underline; }

.masthead-cta:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
  border-radius: 4px;
}

/* On a phone the bar is disc, wordmark, shortcut and two buttons. The brand
   word is the one part that can go without losing the destination: the mark and
   the arrow still say where the link leads, and they are what a returning
   visitor recognises. */
@media (max-width: 560px) {
  .masthead-cta span { display: none; }
}

/*
 * Below 690px there is no lead, no span and therefore no hole, so the block
 * stops being a box and its lead becomes an ordinary card again.
 *
 * `display: none` on the strip is safe HERE and would not have been under the
 * design that truncated the flat list: the siblings keep their own cards
 * further down the wall, so hiding these two costs a phone nothing. Under a
 * truncation it would have cost two videos that existed on no surface at all.
 */
@media (max-width: 689px) {
  .lead-block { display: contents; }
  .lead-strip { display: none; }
}

/*
 * At minimal density every card body is clipped to 1px, so the lead is barely
 * taller than an ordinary card and the hole the strip fills is not there.
 * Showing it anyway would make the block taller than the two rows it reserves
 * -- the same misalignment, inverted.
 */
[data-density="minimal"] .lead-strip {
  display: none;
}

