/* /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; } }

/* ---------------------------------------------------------------- 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: 0; 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: 11px; 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: ""; flex-grow: 1e9; }

.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: 11px; color: rgba(255,255,255,.72); margin-top: 2px; }
.wg-tile__badge {
  position: absolute; z-index: 4; top: 9px; left: 9px;
  font-size: 10px; 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; }
.wg-lb__video { max-width: min(100%, 1400px); max-height: 100%; 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: 10.5px; 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: 11px; 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: 11px;
  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 (max-width: 899px) { .wc-row { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 639px) {
  .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; }
}

.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 {
  display: block; overflow: hidden; border-radius: 6px;
  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); }
.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: 11px;
  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: 10px;
  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 {
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: clamp(10px, 1.4vw, 16px); margin: 0; padding: 0; list-style: none;
}
@media (max-width: 1099px) { .wc-gates { grid-template-columns: repeat(3, 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: 8px;
  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: 10px;
  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: 11px;
  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);
}
