/* /work gallery.
 *
 * The layout is a CSS-only justified grid, and that choice is load bearing for
 * the CLS budget. Every tile knows its aspect ratio from work.manifest.json
 * before a single byte of media arrives, so each row's height is settled at
 * first paint and nothing moves when posters land.
 *
 * How the justification works: a tile's flex-basis is (aspect x row height), so
 * within a row the final widths stay proportional to aspect, which means
 * width/aspect — the height — is identical for every tile in that row. Rows
 * therefore fill the container exactly, with no ragged right edge, and no
 * JavaScript measures anything. Resize is free.
 */

.wg { --wg-gap: 10px; --wg-row: 300px; --wg-radius: 14px; }
@media (min-width: 900px) { .wg { --wg-row: 340px; --wg-gap: 12px; } }
@media (max-width: 560px) { .wg { --wg-row: 260px; --wg-gap: 8px; } }
/* Between 561 and 899 a widescreen tile beside a vertical one overran the
   container by a few pixels and wrapped, leaving a hole two-thirds of a row
   wide; a shorter row keeps the pairs together. */
@media (min-width: 561px) and (max-width: 767px) { .wg { --wg-row: 240px; } }
@media (min-width: 768px) and (max-width: 899px) { .wg { --wg-row: 270px; } }

/* ---------------------------------------------------------------- reel */

.wg-reel {
  position: relative;
  overflow: hidden;
  background: #000;
  /* Reserved before the video exists, so the fold never jumps. */
  height: clamp(420px, 78vh, 820px);
  border-bottom: 1px solid var(--border-subtle, #222);
}
.wg-reel__media, .wg-reel__poster {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
/* Two pixels shorter than its container, and that is deliberate.
   The poster and the video fill the same box, so their largest-contentful-paint
   sizes are within 0.05% of each other — measured 256230 against 256351, a
   121px difference produced purely by sub-pixel rounding of a 78vh container.
   Whichever wins that coin toss becomes the LCP element, and they are not
   equivalent: the poster is 43 KB and paints at ~1.0s, the video is 1.1 MB and
   cannot paint before ~2.3s on a 4 Mbps link. Losing the toss moved the
   measured LCP from 1.6s to 2.3s with nothing about the page having changed.
   So the video is made unambiguously the smaller paint. The two pixels sit at
   the bottom of an overflow-hidden black container under a scrim that is 92%
   black there, behind a poster that is on top until playback starts. */
.wg-reel__media { height: calc(100% - 2px); }
.wg-reel__poster { z-index: 1; transition: opacity .6s ease; }

/* Portrait masters in a landscape band.
 *
 * Three of the five collections are vertical libraries with no widescreen clip
 * in them at all, and object-fit: cover on a 9:16 master in a 21:9 band keeps
 * the middle third — which on this library is a torso or a bottle cap. The
 * band mattes instead: the whole frame at full height, centred, on a field of
 * the same image blurred and darkened, so it still reads as one image rather
 * than as a video between two black bars.
 *
 * Both layers are contained, not just the poster: the reel video sits under
 * the poster and fades in behind it, so matting one and cropping the other
 * would change the framing the moment playback started. */
.wg-reel--matte .wg-reel__media,
.wg-reel--matte .wg-reel__poster {
  width: auto; max-width: 100%;
  left: 0; right: 0; margin-inline: auto;
  object-fit: contain;
}
.wg-reel--matte .mt-matte__fill {
  position: absolute; inset: -6%;
  width: 112%; height: 112%; object-fit: cover;
  filter: blur(38px) saturate(.75) brightness(.42);
  /* Explicitly the bottom layer. initReel() inserts the reel video with
     insertBefore(v, host.firstChild), so the video is the first child and the
     fill — emitted after it in the markup — painted straight over it: the band
     rendered as blur and nothing else. Stacking here is by z-index, not by
     DOM order, precisely because one of these three layers is inserted at
     runtime and its position in the tree is not this file's to rely on. */
  z-index: 0;
}
/* Above the fill, below the poster (which is z-index 1 and later in the tree)
   and below the scrim (z-index 2). */
.wg-reel--matte .wg-reel__media { z-index: 1; }
/* At phone widths the frame nearly fills the band, so the blurred field is a
   repaint-heavy layer earning a sliver either side. */
@media (max-width: 639px) {
  .wg-reel--matte .mt-matte__fill { display: none; }
  .wg-reel--matte .wg-reel__media,
  .wg-reel--matte .wg-reel__poster { object-fit: cover; width: 100%; }
}
.wg-reel.is-playing .wg-reel__poster { opacity: 0; }
.wg-reel__scrim {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: linear-gradient(180deg, rgba(0,0,0,.72), rgba(0,0,0,.30) 42%, rgba(0,0,0,.92));
}
.wg-reel__inner {
  position: relative; z-index: 3;
  height: 100%; display: flex; flex-direction: column; justify-content: flex-end;
  padding-bottom: clamp(28px, 6vh, 64px);
}
/* The caption area is a fixed box; what is inside it is taken out of flow.
 *
 * This is the whole CLS story for the page. During the design system runtime's
 * mount the headline rewraps — measured four lines to one and back at 820, and
 * two to three at 390 — and because the caption sits at the bottom of a fixed
 * height hero, every step moved the eyebrow above it and the buttons below it.
 *
 * A fixed height on the headline itself was the first attempt and was wrong
 * twice: in em it moved with the font size it was meant to pin, and small
 * enough to hold the shift it clipped the third line of real copy. Pinning the
 * AREA instead, with the text anchored to its top and the actions to its
 * bottom, means the headline can rewrap to any number of lines and move
 * nothing at all — and it is never clipped. */
.wg-reel__caption { position: relative; height: clamp(210px, 34vh, 290px); }
/* Collection pages add an intro paragraph to the reel caption, and the fixed
   height above cannot hold one: the text block grew to 250px inside a 290px
   box whose actions are pinned to the bottom, so the last line of the intro
   ran under the buttons (measured: 8px of overlap at 1440, worse as the
   viewport narrows and the paragraph wraps further). With an intro the caption
   becomes an ordinary flow column with a floor rather than a fixed height, so
   it grows with its own copy. The hub and /work/all keep the fixed box. */
.wg-reel__caption--tall {
  height: auto; min-height: clamp(210px, 34vh, 290px);
  display: flex; flex-direction: column; justify-content: flex-end;
  gap: clamp(18px, 2.4vh, 28px);
}
.wg-reel__caption--tall .wg-reel__text,
.wg-reel__caption--tall .wg-reel__actions { position: static; inset: auto; }
.wg-reel__text { position: absolute; inset: 0 0 auto 0; }
.wg-reel__line { margin: 14px 0 0; max-width: 24ch; text-wrap: balance; }
.wg-reel__actions { position: absolute; inset: auto auto 0 0; display: flex; flex-wrap: wrap; gap: 12px; }

/* ---------------------------------------------------------- filter rail */

.wg-rail {
  position: sticky; top: 68px; z-index: 30;
  background: color-mix(in srgb, var(--surface-canvas, #0A0A0B) 88%, transparent);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--border-subtle, #222);
}
/* Each facet is one non-wrapping, horizontally scrollable line.
 *
 * Wrapping chips cost 0.06 CLS at 820px and nothing at 390 or 1440: at that one
 * width the rows reflowed when the webfont swapped in and changed every chip's
 * text width, so the rail's height changed after first paint. A fixed height
 * line that scrolls sideways cannot do that at any width, and it reads better
 * on a narrow screen than three ragged wrapped rows. */
.wg-rail__row {
  display: flex; align-items: center; gap: 12px;
  /* padding-block, not the `padding` shorthand: these rows also carry
     .u-container, and a shorthand resets its padding-inline to zero — which is
     what pushed the "INDUSTRY" legend under the viewport edge and clipped it.
     Same failure as .wc-sec below; both are the shorthand overwriting a
     utility class's inline padding. */
  padding-block: 10px;
  flex-wrap: nowrap; min-height: 52px;
}
.wg-rail__group {
  display: flex; align-items: center; gap: 6px;
  /* flex:1 1 auto with min-width:0 is what keeps the row inside the container.
     Without it the group refused to shrink, the row grew wider than the page,
     and both ends clipped — the legend on the left, the clip count on the
     right. */
  flex: 1 1 auto;
  flex-wrap: nowrap; overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: none; -ms-overflow-style: none;
  padding-bottom: 2px; min-width: 0;
}
.wg-rail__group::-webkit-scrollbar { display: none; }
.wg-chip { flex: 0 0 auto; }
.wg-rail__legend { flex: 0 0 auto; }
.wg-rail__legend {
  font-size: var(--fs-eyebrow); letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-quiet, #888); margin-right: 2px;
}
/* 44px tall, and the height comes from the target rather than from the type.
   The device sweep measured every chip at 30px: over the 24px WCAG 2.2 AA
   floor, but under the 44px this site holds standalone controls to everywhere
   else (tests/e2e/target-sizes.spec.ts), and these are the controls a phone
   uses to drive the whole gallery. Padding does the work so the label, the
   pill radius and the rail's scroll behaviour are all unchanged. */
.wg-chip {
  appearance: none; cursor: pointer;
  display: inline-flex; align-items: center; min-height: 44px;
  border: 1px solid var(--border-default, #333);
  background: transparent; color: var(--text-secondary, #bbb);
  border-radius: 999px; padding: 6px 15px;
  font: inherit; font-size: 13px; line-height: 1.2;
  transition: background .16s ease, color .16s ease, border-color .16s ease;
}
.wg-chip:hover { border-color: var(--border-strong, #555); color: var(--text-primary, #fff); }
.wg-chip[aria-pressed="true"] {
  background: var(--text-primary, #fff); color: var(--surface-canvas, #0A0A0B);
  border-color: var(--text-primary, #fff);
}
.wg-chip:focus-visible, .wg-tile:focus-visible, .wg-lb button:focus-visible {
  outline: 2px solid var(--border-focus, #7dd3fc); outline-offset: 2px;
}
.wg-rail__meta { margin-left: auto; display: flex; align-items: center; gap: 14px; flex: 0 0 auto; }
.wg-count { font-variant-numeric: tabular-nums; color: var(--text-secondary, #bbb); font-size: 13px; }
.wg-count b { color: var(--text-primary, #fff); font-weight: 600; }

/* ------------------------------------------------------------- the grid */

.wg-grid { display: flex; flex-wrap: wrap; gap: var(--wg-gap); margin: var(--wg-gap) 0 0; padding: 0; list-style: none; }
/* Absorbs the last row's slack so its tiles keep the target row height
   instead of stretching to fill the line. */
.wg-grid::after { content: ""; }

.wg-tile {
  position: relative; display: block; padding: 0; margin: 0;
  border: 0; overflow: hidden; cursor: pointer;
  border-radius: var(--wg-radius);
  background-color: #111;
  flex-grow: var(--a); flex-shrink: 1;
  flex-basis: calc(var(--a) * var(--wg-row));
  aspect-ratio: var(--a);
  contain: layout paint;
}
/* No content-visibility here, deliberately.
 *
 * It looked like free virtualisation and cost 0.14-0.38 CLS: a skipped flex
 * item sizes as though empty, so aspect-ratio had nothing to work from and
 * every tile resized the moment it scrolled in. Supplying
 * contain-intrinsic-size only traded one wrong height for another.
 *
 * The tiles are cheap — a div, an img and two spans each — and 127 of them is
 * not what costs anything. What costs is media, and that is virtualised
 * properly by the IntersectionObserver: nothing loads a poster, a filmstrip or
 * a video until it is within 1.5 viewports, and everything past that holds its
 * exact box with flex-basis and aspect-ratio while owning no media at all. */

.wg-tile__lqip { position: absolute; inset: 0; z-index: 0; }
.wg-tile__poster {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity .35s ease;
}
.wg-tile__poster.is-in { opacity: 1; }
.wg-tile__sprite {
  position: absolute; inset: 0; z-index: 2;
  background-repeat: no-repeat; background-size: 600% 100%;
  opacity: 0; transition: opacity .18s ease;
}
.wg-tile.is-scrub .wg-tile__sprite { opacity: 1; }
.wg-tile__video {
  position: absolute; inset: 0; z-index: 3;
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity .3s ease;
}
.wg-tile.is-playing .wg-tile__video { opacity: 1; }
.wg-tile__label {
  position: absolute; z-index: 4; left: 0; right: 0; bottom: 0;
  padding: 30px 12px 11px;
  background: linear-gradient(180deg, transparent, rgba(0,0,0,.78));
  color: #fff; text-align: left;
  opacity: 0; transition: opacity .2s ease;
}
.wg-tile:hover .wg-tile__label, .wg-tile:focus-visible .wg-tile__label, .wg-tile.is-playing .wg-tile__label { opacity: 1; }
.wg-tile__title { display: block; font-size: 13px; font-weight: 600; line-height: 1.25; }
.wg-tile__sub { display: block; font-size: var(--fs-eyebrow); color: rgba(255,255,255,.72); margin-top: 2px; }
.wg-tile__badge {
  position: absolute; z-index: 4; top: 9px; right: 9px;
  font-size: var(--fs-eyebrow); letter-spacing: .1em; text-transform: uppercase;
  padding: 4px 7px; border-radius: 5px;
  background: rgba(0,0,0,.62); color: #fff; backdrop-filter: blur(4px);
  opacity: 0; transition: opacity .2s ease;
}
.wg-tile:hover .wg-tile__badge, .wg-tile:focus-visible .wg-tile__badge { opacity: 1; }

.wg-empty { padding: 64px 0; text-align: center; color: var(--text-secondary, #bbb); }

/* -------------------------------------------------------------- lightbox */

.wg-lb {
  position: fixed; inset: 0; z-index: 200;
  display: none; background: rgba(0,0,0,.94);
  backdrop-filter: blur(8px);
}
.wg-lb.is-open { display: grid; grid-template-rows: auto 1fr auto; }
.wg-lb__bar { display: flex; align-items: center; gap: 10px; padding: 14px 18px; color: #fff; }
.wg-lb__stage { position: relative; display: grid; place-items: center; min-height: 0; padding: 0 12px; overflow: hidden; }
.wg-lb__video { max-width: min(100%, 1400px); max-height: calc(100dvh - 210px); width: auto; height: auto; background: #000; border-radius: 10px; }
.wg-lb__foot { padding: 14px 18px 20px; color: #fff; }
.wg-lb__title { font-size: 17px; font-weight: 600; margin: 0; }
.wg-lb__facts { margin: 6px 0 0; font-size: 12.5px; color: rgba(255,255,255,.66); }
.wg-lb__alt { margin: 8px 0 0; font-size: 13px; color: rgba(255,255,255,.8); max-width: 76ch; }
.wg-lb__breakdown { display: grid; gap: 8px; margin: 14px 0 0; max-width: 78ch; }
.wg-lb__breakdown div { font-size: 13px; color: rgba(255,255,255,.82); }
.wg-lb__breakdown dt { font-size: var(--fs-eyebrow); letter-spacing: .12em; text-transform: uppercase; color: rgba(255,255,255,.5); }
/* 44, not 40. Every standalone control on this site is held to a 44px box
   (tests/e2e/target-sizes.spec.ts) and the lightbox's own close, sound and
   next/prev buttons were the set that missed it — the controls a phone uses
   one-handed over a playing video. The glyph is unchanged. */
.wg-icon {
  appearance: none; cursor: pointer; color: #fff;
  background: rgba(255,255,255,.09); border: 1px solid rgba(255,255,255,.16);
  border-radius: 9px; width: 44px; height: 44px; display: grid; place-items: center;
  font: inherit; font-size: 15px; line-height: 1;
}
.wg-icon:hover { background: rgba(255,255,255,.16); }
.wg-lb__nav { position: absolute; top: 50%; transform: translateY(-50%); }
.wg-lb__nav--prev { left: 10px; }
.wg-lb__nav--next { right: 10px; }

/* ---------------------------------------------------------- channel mode */

.wg-ch { position: fixed; left: 0; right: 0; bottom: 0; z-index: 210; display: none; }
.wg-ch.is-on { display: block; }
.wg-ch__bar {
  display: flex; align-items: center; gap: 14px;
  padding: 12px 18px calc(12px + env(safe-area-inset-bottom, 0px));
  background: rgba(8,8,10,.94); border-top: 1px solid rgba(255,255,255,.12); color: #fff;
}
.wg-ch__now { min-width: 0; flex: 1; }
.wg-ch__title { font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wg-ch__sub { font-size: var(--fs-eyebrow); color: rgba(255,255,255,.6); }
.wg-ch__track { position: absolute; top: 0; left: 0; right: 0; height: 3px; background: rgba(255,255,255,.14); }
.wg-ch__fill { height: 100%; width: 0; background: #fff; transition: width .2s linear; }
.wg-ch__dots { display: flex; gap: 3px; }
.wg-ch__dot { width: 14px; height: 3px; border-radius: 2px; background: rgba(255,255,255,.25); }
.wg-ch__dot.is-done { background: rgba(255,255,255,.62); }
.wg-ch__dot.is-now { background: #fff; }

body.wg-locked { overflow: hidden; }

/* --------------------------------------------------------- reduced motion */

@media (prefers-reduced-motion: reduce) {
  .wg-reel__poster { opacity: 1 !important; }
  .wg-reel__media { display: none; }
  .wg-tile__poster, .wg-tile__sprite, .wg-tile__video, .wg-tile__label, .wg-ch__fill { transition: none; }
}


/* ==========================================================================
   BROWSE SHEET — the mobile face of the filter rail (2026-08-28)
   The rail itself is untouched above 900px. Below it, the same markup and
   the same chip state are presented as a bottom sheet: one entry control,
   the active filters visible outside it, a live result count, reset and
   apply. No second copy of the filters exists, so nothing can drift.
   ========================================================================== */

.wg-browse { display: none; }
.wg-sheet__head, .wg-sheet__foot { display: none; }
.wg-active { display: none; }
.wg-sheet-scrim { display: none; }

/* Chips summarising what is on, shown on every width once something is on. */
.wg-active {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  padding-top: 14px;
}
.wg-active[hidden] { display: none; }
.wg-active__label {
  font-family: var(--font-mono, ui-monospace, monospace); font-size: var(--fs-eyebrow);
  letter-spacing: .14em; text-transform: uppercase; color: var(--text-quiet, #888);
  margin-right: 2px;
}
.wg-active__pill {
  appearance: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: 8px; min-height: 36px;
  padding: 4px 8px 4px 13px; border-radius: 999px;
  border: 1px solid var(--border-strong, #555);
  background: transparent; color: var(--text-primary, #fff);
  font: inherit; font-size: 12.5px; line-height: 1;
}
.wg-active__pill:hover { background: rgba(255,255,255,.08); }
.wg-active__pill:focus-visible { outline: 2px solid var(--border-focus, #7dd3fc); outline-offset: 2px; }
.wg-active__x { font-size: 14px; opacity: .7; }

@media (max-width: 899px) {
  .wg-active__pill { min-height: 44px; }

  .wg-browse {
    display: flex; align-items: center; justify-content: space-between; gap: 14px;
    padding-top: 16px; padding-bottom: 4px;
  }
  .wg-browse__btn {
    appearance: none; cursor: pointer;
    display: inline-flex; align-items: center; gap: 10px; min-height: 48px;
    padding: 10px 18px; border-radius: 999px;
    border: 1px solid var(--border-strong, #555);
    background: transparent; color: var(--text-primary, #fff);
    font: inherit; font-size: 14px; font-weight: 600; line-height: 1;
  }
  .wg-browse__btn:focus-visible { outline: 2px solid var(--border-focus, #7dd3fc); outline-offset: 2px; }
  .wg-browse__ico { display: inline-flex; }
  .wg-browse__badge {
    display: inline-grid; place-items: center; min-width: 22px; height: 22px; padding: 0 6px;
    border-radius: 999px; background: var(--text-primary, #fff); color: var(--surface-canvas, #0A0A0B);
    font-size: 12px; font-weight: 700;
  }
  .wg-browse__badge[hidden] { display: none; }
  .wg-browse__count { font-size: 13px; }

  /* The rail's own sticky belongs to the desktop layout. */
  .wg-rail { position: static; }

  /* Closed state: out of the layout AND out of the tab order, so the 32
     chips are not 32 stops a keyboard has to walk past to reach the grid. */
  .wg-rail {
    position: fixed; left: 0; right: 0; bottom: 0; top: auto; z-index: 200;
    max-height: min(86dvh, 86vh);
    display: flex; flex-direction: column;
    background: var(--surface-canvas, #0A0A0B);
    border-top: 1px solid var(--border-strong, #555);
    border-bottom: 0;
    border-radius: 18px 18px 0 0;
    box-shadow: 0 -24px 60px -20px rgba(0,0,0,.8);
    transform: translateY(101%);
    visibility: hidden;
    transition: transform .26s cubic-bezier(.22,.61,.36,1), visibility 0s linear .26s;
  }
  body[data-wg-sheet="1"] .wg-rail {
    transform: translateY(0); visibility: visible;
    transition: transform .26s cubic-bezier(.22,.61,.36,1), visibility 0s;
  }

  /* Driven by the body flag, not by a `hidden` attribute. The design-system
     runtime re-renders this subtree from the template and does not carry a
     bare boolean attribute through, so the scrim mounted without `hidden` and
     sat over the whole page intercepting every click — the nav burger
     included. One flag, set by the same code that moves the sheet, cannot
     come apart from it. */
  body[data-wg-sheet="1"] .wg-sheet-scrim {
    display: block; position: fixed; inset: 0; z-index: 190;
    background: rgba(0,0,0,.62); backdrop-filter: blur(2px);
  }

  .wg-sheet__head {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 14px 16px 12px; border-bottom: 1px solid var(--border-subtle, #222);
    flex: none;
  }
  /* The grab handle reads as "this pulls down" without being a control. */
  .wg-sheet__head::before {
    content: ""; position: absolute; top: 7px; left: 50%; transform: translateX(-50%);
    width: 38px; height: 4px; border-radius: 999px; background: rgba(255,255,255,.22);
  }
  .wg-sheet__head { position: relative; padding-top: 20px; }
  .wg-sheet__title { font-size: 16px; font-weight: 600; color: var(--text-primary, #fff); }
  /* .wg-icon is tuned to sit over playing video, where a near-white fill is
     what keeps it legible. On the sheet's own dark header that same fill read
     as a filled primary button and out-weighed the Show-N-clips control at the
     other end of the panel. Same 44px box, panel-native weight. */
  #wg-sheet-close {
    background: transparent; border-color: var(--border-default, #333);
    color: var(--text-secondary, #bbb); border-radius: 999px;
  }
  #wg-sheet-close:hover { background: rgba(255,255,255,.08); color: var(--text-primary, #fff); }

  /* Inside the sheet the chips wrap and the sheet scrolls: a reader can see
     every option in a facet instead of six of fourteen. */
  .wg-rail .wg-rail__row {
    flex-wrap: wrap; align-items: flex-start; padding: 14px 16px; min-height: 0;
    overflow: visible;
  }
  .wg-rail .u-container.wg-rail__row { max-width: none; }
  .wg-rail__group { flex-wrap: wrap; overflow: visible; gap: 8px; }
  .wg-rail__legend { flex: 1 1 100%; margin: 0 0 4px; }
  .wg-rail__meta { display: none; }
  /* One scroller, between the fixed head and the fixed foot; overscroll is
     contained so reaching the end of the list does not hand the scroll to the
     gallery behind the sheet. */
  .wg-rail { overflow: hidden; }
  .wg-rail__scroll {
    flex: 1 1 auto; min-height: 0;
    overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
  }
  .wg-rail__scroll > .wg-rail__row:first-child { padding-top: 6px; }

  .wg-sheet__foot {
    display: flex; align-items: center; gap: 12px; flex: none;
    padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid var(--border-subtle, #222);
    background: var(--surface-canvas, #0A0A0B);
  }
  .wg-sheet__reset {
    appearance: none; cursor: pointer; background: transparent;
    border: 1px solid var(--border-default, #333); border-radius: 999px;
    color: var(--text-secondary, #bbb); font: inherit; font-size: 13px;
    min-height: 48px; padding: 0 18px;
  }
  .wg-sheet__reset:disabled { opacity: .45; cursor: default; }
  .wg-sheet__apply { flex: 1 1 auto; justify-content: center; min-height: 48px; }
}

@media (prefers-reduced-motion: reduce) {
  .wg-rail { transition: none !important; }
}

/* ==========================================================================
   CURATED HUB — /work (2026-08-29)
   The hub is links and posters: rows, gateways and one hero. It shares the
   reel band and the card language with the gallery pages rather than
   inventing a second visual system for the same site.
   ========================================================================== */

/* padding-block-start, never the `padding` shorthand: these sections also
   carry .u-container, whose padding-inline is the site's gutter, and a
   shorthand here resets it to zero — work-gallery.css loads after site.css,
   so the shorthand wins and every row runs to the viewport edge. */
.wc-sec { padding-block-start: clamp(40px, 6vh, 72px); }
.wc-sec__head {
  display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between;
  gap: 14px clamp(24px, 4vw, 56px); margin-bottom: clamp(20px, 3vh, 30px);
}
.wc-sec__head > div { flex: 1 1 420px; min-width: 0; }
.wc-sec__title { margin: 14px 0 0; text-wrap: balance; }
.wc-sec__intro { margin: 12px 0 0; max-width: 62ch; color: var(--text-secondary, #bbb); }
.wc-sec__more { flex: 0 0 auto; }

/* --- curated rows ------------------------------------------------------- */
/* A row, not a grid: these are short editorial selections (four to eight),
   and a wrapping grid would make four items look like a page of results.
   It scrolls horizontally on narrow screens with the same affordance the
   homepage rails use. */
.wc-row {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(10px, 1.4vw, 16px); margin: 0; padding: 0; list-style: none;
}
/* Four across, two rows — not eight across. At 1440 an eight-column row gives
   each card 160px, which truncated every title in the selection ("Surreal
   Product Vi…") and made the best work the least legible row on the page. */
.wc-row--best { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (min-width: 768px) and (max-width: 899px) { .wc-row { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 767px) {
  .wc-row {
    display: flex; overflow-x: auto; scroll-snap-type: x mandatory;
    overscroll-behavior-x: contain; scrollbar-width: none;
    padding-bottom: 4px; margin-inline: calc(var(--u-gutter, 20px) * -1);
    padding-inline: var(--u-gutter, 20px);
  }
  .wc-row::-webkit-scrollbar { display: none; }
  .wc-row > .wc-card { flex: 0 0 62%; scroll-snap-align: start; }
}
/* Between phone and tablet the rail stays, with a card narrow enough to show
   that it scrolls. */
@media (min-width: 640px) and (max-width: 767px) { .wc-row > .wc-card { flex: 0 0 44%; } }

.wc-card { position: relative; display: flex; flex-direction: column; min-width: 0; }
.wc-card__link { display: flex; flex-direction: column; gap: 10px; text-decoration: none; color: inherit; }
.wc-card__link:focus-visible { outline: 2px solid var(--border-focus, #7dd3fc); outline-offset: 3px; }
.wc-card__media {
  position: relative;
  display: block; overflow: hidden; border-radius: 0;
  background: var(--surface-card, #141416); aspect-ratio: 3 / 4;
}
.wc-card__media img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform .4s cubic-bezier(.22,.61,.36,1);
}
.wc-card__link:hover .wc-card__media img { transform: scale(1.03); }

/* Live previews on the curated cards (public/work-hub.js): the sprite scrubs
   under the pointer at once, then the muted 540 rendition fades in after the
   dwell. Both layers are created by the script, so a no-JS page carries only
   the poster. */
.wc-card__sprite {
  position: absolute; inset: 0;
  background-size: 600% 100%; background-position: 0 0;
  opacity: 0; transition: opacity .18s ease-out; pointer-events: none;
}
.wc-card__media.is-scrub .wc-card__sprite { opacity: 1; }
.wc-card__media.is-live .wc-card__sprite { opacity: 0; }
.wc-card__vid {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: 0; transition: opacity .25s ease-out;
  pointer-events: none;
}
.wc-card__media.is-live .wc-card__vid { opacity: 1; }
/* The card's own sound control (rendered only where the delivered file has a
   track). It sits over the media's top-right corner, outside the link so it is
   its own control; the wave and the cross swap with aria-pressed. */
.wc-card__sound {
  position: absolute; top: 8px; right: 8px; z-index: 2;
  width: 36px; height: 36px; padding: 0; border-radius: 999px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(10,10,11,.72); color: #fff;
  border: 1px solid rgba(255,255,255,.28); backdrop-filter: blur(6px);
  cursor: pointer; transition: background .2s ease, border-color .2s ease;
}
.wc-card__sound:hover, .wc-card__sound:focus-visible { background: rgba(10,10,11,.92); border-color: rgba(255,255,255,.6); }
.wc-card__sound:focus-visible { outline: 2px solid var(--border-focus, #7dd3fc); outline-offset: 2px; }
.wc-card__sound .wc-card__sound-wave { display: none; }
.wc-card__sound[aria-pressed="true"] { background: #fff; color: #0A0A0B; border-color: #fff; }
.wc-card__sound[aria-pressed="true"] .wc-card__sound-wave { display: block; }
.wc-card__sound[aria-pressed="true"] .wc-card__sound-x { display: none; }
@media (pointer: coarse) { .wc-card__sound { width: 40px; height: 40px; } }
.wc-card__body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.wc-card__title {
  font-size: 14px; font-weight: 600; color: var(--text-primary, #fff);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.wc-card__meta {
  font-family: var(--font-mono, ui-monospace, monospace); font-size: var(--fs-eyebrow);
  letter-spacing: .1em; text-transform: uppercase; color: var(--text-tertiary, #999);
}

/* Concept and sound labels. The policy in work.collections.yaml requires the
   concept label on every non-client piece, so it is part of the card rather
   than an optional decoration. */
.wc-card__tags { position: absolute; top: 8px; left: 8px; display: flex; gap: 6px; }
.wc-tag {
  font-family: var(--font-mono, ui-monospace, monospace); font-size: var(--fs-eyebrow);
  letter-spacing: .12em; text-transform: uppercase; line-height: 1;
  padding: 5px 8px; border-radius: 3px; backdrop-filter: blur(6px);
}
.wc-tag--concept { background: rgba(10,10,11,.72); color: rgba(255,255,255,.82); border: 1px solid rgba(255,255,255,.18); }
.wc-tag--sound { background: rgba(255,255,255,.9); color: #0A0A0B; }

/* --- category gateways -------------------------------------------------- */
.wc-gates {
  /* Four doors now — Cinematic, UGC, Product, Image Ads — so four columns:
     each gateway gains a quarter of the row the fifth used to take. */
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(10px, 1.4vw, 16px); margin: 0; padding: 0; list-style: none;
}
@media (max-width: 767px) { .wc-gates { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 639px) { .wc-gates { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.wc-gate__link {
  position: relative; display: block; overflow: hidden; border-radius: 0;
  aspect-ratio: 4 / 5; text-decoration: none; background: var(--surface-card, #141416);
}
.wc-gate__link:focus-visible { outline: 2px solid var(--border-focus, #7dd3fc); outline-offset: 3px; }
.wc-gate__img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  transition: transform .5s cubic-bezier(.22,.61,.36,1);
}
.wc-gate__link:hover .wc-gate__img { transform: scale(1.04); }
/* The label sits on whatever frame the gateway drew, and two of the five are
   pale (folded towels, skin). The scrim has to be dark enough under a
   two-line name at the bottom of the card, not just at its last 10%. */
.wc-gate__scrim {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(0,0,0,.12) 0%, rgba(0,0,0,.18) 38%, rgba(0,0,0,.72) 72%, rgba(0,0,0,.94) 100%);
}
.wc-gate__text { position: absolute; inset: auto 0 0 0; padding: 14px; display: block; }
/* Balanced so "Product & E-commerce Films" breaks into two even lines rather
   than one long line and one orphan. */
.wc-gate__name {
  display: block; font-size: clamp(15px, 1.3vw, 18px); font-weight: 600;
  color: #fff; text-wrap: balance; line-height: 1.2;
}
.wc-gate__count { display: block; margin-top: 5px; color: rgba(255,255,255,.66); }

/* --- the hero's featured commercial ------------------------------------- */
/* The video sits over the reel poster in the same band and only becomes
   visible once the visitor starts it, so the page has one hero and one
   deliberate playback rather than a poster and a second player below it. */
.wg-reel__video {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  opacity: 0; pointer-events: none; transition: opacity .3s ease; z-index: 1;
}
.wg-reel__video.is-playing { opacity: 1; pointer-events: auto; }
@media (prefers-reduced-motion: reduce) { .wg-reel__video { transition: none; } }
.wg-reel__intro {
  margin: 12px 0 0; max-width: 54ch; color: rgba(255,255,255,.78);
}
.wc-feature__error { margin: 12px 0 0; color: var(--text-tertiary, #999); }
.wc-feature__error a { color: inherit; }

/* --- browse all --------------------------------------------------------- */
.wc-browse {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 16px 32px; margin-top: clamp(48px, 7vh, 84px);
  padding-block-start: clamp(24px, 3.5vh, 36px);
  border-top: 1px solid var(--border-default, #333);
}

/* The hero names the film it is a frame of. */
.wg-reel__feature {
  margin: 16px 0 0; display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 4px 14px;
}
.wg-reel__feature-label { color: rgba(255,255,255,.6); }
.wg-reel__feature-title { font-size: 15px; font-weight: 600; color: #fff; }
.wg-reel__feature-facts { color: rgba(255,255,255,.6); }

/* Tile labels the curation policy requires. Concept sits opposite the aspect
   badge so the two never stack; sound only appears where the delivered file
   has a track (measured, see scripts/media/inspect.mjs). */
.wg-tile__concept, .wg-tile__sound {
  position: absolute; top: 8px; z-index: 2;
  font-family: var(--font-mono, ui-monospace, monospace); font-size: var(--fs-eyebrow);
  letter-spacing: .12em; text-transform: uppercase; line-height: 1;
  padding: 5px 7px; border-radius: 3px; pointer-events: none;
}
.wg-tile__concept {
  left: 8px; background: rgba(10,10,11,.72); color: rgba(255,255,255,.82);
  border: 1px solid rgba(255,255,255,.18); backdrop-filter: blur(6px);
}
.wg-tile__sound { left: 8px; top: 34px; background: rgba(255,255,255,.9); color: #0A0A0B; }
/* A clip with no concept label puts its sound badge in the top slot. */
.wg-tile__concept + .wg-tile__sound { top: 34px; }
.wg-tile > .wg-tile__sound:first-of-type { top: 8px; }

/* Related collections — the end of a collection page, not a site-wide footer. */
.wc-related { padding-block: clamp(40px, 6vh, 68px) 0; }
.wc-related > h2 { margin: 0 0 clamp(16px, 2.4vh, 24px); }
.wc-related__list {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(10px, 1.4vw, 16px); margin: 0; padding: 0; list-style: none;
}
@media (max-width: 899px) { .wc-related__list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 479px) { .wc-related__list { grid-template-columns: minmax(0, 1fr); } }
.wc-related__item {
  display: flex; flex-direction: column; gap: 6px; min-height: 76px;
  justify-content: center; padding: 16px 18px;
  border: 1px solid var(--border-default, #333); border-radius: 6px;
  text-decoration: none; color: inherit;
  transition: border-color .16s ease, background .16s ease;
}
.wc-related__item:hover { border-color: var(--border-strong, #555); background: rgba(255,255,255,.04); }
.wc-related__item:focus-visible { outline: 2px solid var(--border-focus, #7dd3fc); outline-offset: 2px; }
.wc-related__item--all { border-color: var(--border-strong, #555); }
.wc-related__name { font-size: 14px; font-weight: 600; color: var(--text-primary, #fff); text-wrap: balance; }
.wc-related__count { color: var(--text-tertiary, #999); }

/* ------------------------------- 11 · THE CAPABILITY PANEL (2026-08-29) */
/* What buying this collection gets you. A definition list, not cards: these
   are four answers to four questions, and a grid of panels would make them
   look like features. */
.wk-spec { padding-block-start: clamp(40px, 6vh, 68px); }
.wk-spec > h2 { margin: 0 0 clamp(16px, 2.4vh, 24px); }
.wk-spec__rows { margin: 0; padding: 0; border-top: 1px solid var(--border-default, #333); }
.wk-spec__row {
  display: grid; grid-template-columns: minmax(140px, 12rem) minmax(0, 1fr);
  gap: 6px clamp(20px, 3vw, 44px);
  padding: clamp(14px, 2vh, 20px) 0;
  border-bottom: 1px solid var(--border-subtle, #222);
}
@media (max-width: 639px) { .wk-spec__row { grid-template-columns: minmax(0, 1fr); } }
.wk-spec__row dt {
  font-family: var(--font-mono, ui-monospace, monospace); font-size: var(--fs-eyebrow);
  letter-spacing: .12em; text-transform: uppercase; color: var(--text-quiet, #888);
}
.wk-spec__row dd { margin: 0; color: var(--text-secondary, #bbb); line-height: 1.6; }
.wk-spec__note {
  margin: clamp(18px, 2.4vh, 24px) 0 0; max-width: 72ch;
  color: var(--text-tertiary, #999);
}

/* --- curated collection grids (31 Aug 2026 restructure) ------------------ */
/* A collection page is the whole collection, so its rows wrap at every
   width — the 639px snap rail above stays a summary device for the hub. */
.wc-row--grid { grid-template-columns: repeat(4, minmax(0, 1fr)); row-gap: clamp(18px, 2.4vh, 28px); }
@media (min-width: 768px) and (max-width: 899px) { .wc-row--grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 767px) {
  .wc-row--grid {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    overflow-x: visible; margin-inline: 0; padding-inline: 0;
  }
  .wc-row--grid > .wc-card { flex: none; scroll-snap-align: none; }
}
@media (min-width: 640px) and (max-width: 767px) { .wc-row--grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* --- the stills grid (/work/image-ads) ----------------------------------- */
/* Five images presented at size, as two bricked columns. Until 1 Sep 2026
   this was a two-column grid, which sized every row by its taller frame and
   left a hole the height of a portrait under the one landscape still; CSS
   columns stack each frame under the previous one instead, and the browser
   balances the two columns. The stills are ordered in work.stills.yaml so
   the balance comes out close. Captions sit under the frame, not over it —
   these are photographs, and a scrim would grade them twice. */
.st-grid {
  columns: 2; column-gap: clamp(14px, 2vw, 24px);
  margin: 0; padding: 0; list-style: none;
}
.st-card { position: relative; min-width: 0; break-inside: avoid; margin-bottom: clamp(14px, 2vw, 24px); }
.st-fig { margin: 0; }
.st-fig img {
  width: 100%; height: auto; display: block; border-radius: 8px;
  background: var(--surface-card, #141416);
}
.st-cap { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px 16px; padding: 10px 2px 0; }
.st-cap__title { font-weight: 600; color: var(--text-primary, #fafafb); }
.st-cap__meta { color: var(--text-tertiary, #999); }
.st-card .wc-card__tags { position: absolute; top: 10px; left: 10px; }
@media (max-width: 639px) { .st-grid { columns: 1; } }

/* =============================================================
   DETAILED AUDIT PASS (1 Sep 2026) — library, lightbox, channel, cards
   ============================================================= */

/* The tiles, the Clear chip, the empty-state note and the sound controls are
   toggled with the hidden attribute, and every one of them carried a display
   rule that beat the user-agent [hidden] rule, so filtering hid nothing and a
   dead mute button sat in every lightbox. */
.wg-tile[hidden],
.wg-grid > [hidden],
#wg-clear[hidden],
#wg-empty[hidden],
#wg-lb-sound[hidden],
#wg-ch-sound[hidden],
.wg-rail [hidden] {
  display: none !important;
}

/* Touch devices have no hover: the title and category are always on, or the
   library is a wall of unlabelled posters. */
@media (hover: none) {
  .wg-tile__label {
    opacity: 1;
  }
}

/* At desktop widths the facet rows wrap instead of clipping their last chips
   behind a hidden scrollbar (Luxury and Home were unreachable at 1440). */
@media (min-width: 900px) {
  .wg-rail__group {
    flex-wrap: wrap;
    overflow: visible;
  }
  .wg-rail__meta {
    margin-left: 12px;
  }
}

/* Phone channel bar: the dots ran 473px past the edge and pushed the
   previous, next and close controls off-screen. */
@media (max-width: 899px) {
  .wg-ch__dots {
    display: none;
  }
  .wg-ch__bar {
    gap: 8px;
    padding-inline: 12px;
  }
  .wg-ch__now {
    flex: 1 1 auto;
    min-width: 0;
  }
  .wg-ch__sub {
    white-space: nowrap;
  }
}

/* Phone hub rails keep their gutter: snap-align start used to scroll the
   20px padding away and slice the second card at the viewport edge. */
@media (max-width: 767px) {
  .wc-row {
    scroll-padding-inline: 20px;
  }
}

/* Card meta stays one line; titles get two lines, then an ellipsis. */
.wc-card__meta {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.wc-card__title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The matted portrait hero: the frame takes the right half so the headline
   stays on the blurred field instead of running across the subject. */
@media (min-width: 1024px) {
  .wg-reel--matte .wg-reel__media,
  .wg-reel--matte .wg-reel__poster {
    /* The frame's right edge sits on the content margin: the shell
       container's side padding plus, past 90rem, half the excess. At 1920 a
       fixed 6vw left it 180px past the margin, aligned to nothing. */
    margin-inline: auto calc(max(0px, (100vw - 90rem) / 2) + clamp(20px, 4vw, 56px));
  }
}

/* No cap on a tile's growth: capped at 1.35 rows, a short middle row stopped
   up to 240px short of the right margin at 1440 and left a hole beside a lone
   widescreen piece below 1280. Every full row now fills the width. The
   trailing filler behaves like one extra widescreen tile with a heavy grow,
   so the last row's real tiles grow a little, like their neighbours, instead
   of staying at the base row height while the filler takes all the slack. */
.wg-grid::after {
  flex-grow: 4;
  flex-basis: calc(var(--wg-row) * 1.78);
}

/* Below 900px the labels are on whatever the pointer reports: a tablet with a
   trackpad still cannot hover a tile it has to scroll past. */
@media (max-width: 899px) {
  .wg-tile__label {
    opacity: 1;
  }
}

/* Phones: a two-column grid instead of the justified flex rows, which sized
   the same 9:16 clip at 146, 173 and 263px depending on its neighbours.
   Widescreen clips take the full width. */
@media (max-width: 560px) {
  .wg-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .wg-grid::after {
    display: none;
  }
  .wg-tile {
    max-width: none;
    flex-basis: auto;
  }
  .wg-tile[style*="--a:1.7"] {
    grid-column: span 2;
  }
}

/* Between 640 and 1023 the matted 9:16 master was centred in a band 78vh
   tall, so at 768 it stood 450px wide under the headline, intro and buttons.
   A shorter band keeps the frame at about 40% of the width, sits it to the
   right, and reserves the left of the caption for the copy. */
@media (min-width: 640px) and (max-width: 1023px) {
  .wg-reel--matte { height: clamp(420px, 70vw, 620px); }
  .wg-reel--matte .wg-reel__media,
  .wg-reel--matte .wg-reel__poster { margin-inline: auto 5vw; }
  .wg-reel--matte .wg-reel__inner { padding-right: calc(clamp(420px, 70vw, 620px) * 0.5625 + 8vw); }
}

/* On a short landscape tablet or small laptop the four chip rows plus the
   header held 300px of an 820px viewport while browsing; the rail scrolls
   with the page there and stays pinned on taller screens. */
@media (min-width: 900px) and (max-height: 920px) {
  .wg-rail { position: static; }
}

/* A four-column collection grid leaves six pieces as 4 + 2 and nine as
   4 + 4 + 1; three columns square those counts. */
@media (min-width: 900px) {
  .wc-row--grid:has(> li:nth-child(6):last-child),
  .wc-row--grid:has(> li:nth-child(9):last-child) {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* The "Buying this" answers ran to 118 characters a line at 1180. */
.wk-spec__row dd { max-width: 70ch; }


/* Card titles: the two-line clamp above was defeated by the nowrap it was
   written to replace, so phones showed "Activewear Creator Sp…"; the meta
   line may wrap on a phone rather than lose the duration. */
.wc-card__title { white-space: normal; }
@media (max-width: 899px) {
  .wc-card__meta { white-space: normal; }
}

/* The phone grid spans widescreen tiles across both columns; without dense
   packing a vertical tile before one sat beside an empty cell. */
@media (max-width: 560px) {
  .wg-grid { grid-auto-flow: dense; }
}

/* The image-ads page: two columns as a grid rather than CSS columns, which
   balanced by height and left one column 225px short. The stills are ordered
   so each row pairs two frames of the same aspect, and the one landscape
   frame spans both columns to close the grid on an even edge. */
.st-grid {
  columns: auto;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(14px, 2vw, 24px);
}
.st-card { margin-bottom: 0; }
.st-card--wide { grid-column: 1 / -1; }

/* Refinement pass (2 Sep 2026): the concept disclosure is stated once per grid, not on every tile. */
.wc-card__tags { display: none; }
