/* ==========================================================================
   Poseidon Pump Service — "Trident Deep" · Our Work
   Layered ON TOP of styles.css. Nothing here redefines a brand token; every
   colour, radius, shadow and easing below is the one the index already uses.
   The page's own vocabulary is three things the index has no need for: the
   wave-wipe comparator, the masonry gallery and the lightbox.
   ========================================================================== */

/* Reserving the scrollbar gutter for the whole page means the lightbox can
   lock scrolling (overflow:hidden on the scroll container) without the layout
   sliding sideways by a scrollbar width as it opens. */
html[data-page="work"] { scrollbar-gutter: stable; }

/* The current page keeps the underline the other links only get on hover —
   same rule, drawn open instead of animating. */
.nav-desktop a[aria-current="page"] { color: var(--foam); }
.nav-desktop a[aria-current="page"]::after { right: 0; }
.nav-panel nav a[aria-current="page"] { color: var(--sky-bright); }

/* ================================================================ HERO BAND */
/* Compact by design: no intro, so the water only has to establish itself.
   The top padding is derived from --waterline exactly the way the index hero
   derives its own, which puts the first line of copy just under the horizon
   at every viewport height instead of at a guessed offset. */
/* WHERE THE TEXT SITS, and why it is not measured off --waterline any more.
   The index hangs its copy under the horizon because it has a trident hanging
   ABOVE the horizon holding that space up. This page has no intro cast, so the
   same offset left the top third of the window as empty sky and the block read
   as sunk — and pushed everything after it through the fold.

   The block is optically centred on 42% of the window instead: its own centre,
   not its top, is the thing being placed, which is why the constant below is
   half the block's height. It straddles the horizon rather than hanging off
   it, the empty sky above it is gone, and the ~120px it gives back is what
   lets the case card crest the fold underneath.

   --hero-half is that half-height: eyebrow + one line of display type + two
   lines of sub, which is stable across the desktop range because the sub is
   capped at 58ch and the title clamps out at 4.4rem. The phone block runs two
   lines of title, so it re-declares it. */
.work-hero {
  position: relative;
  --hero-half: 98px;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  padding-top: max(76px, calc(42vh - var(--hero-half)));
  padding-top: max(76px, calc(42svh - var(--hero-half)));
  padding-bottom: clamp(18px, 2.6vh, 38px);
  text-align: center;
}
.work-hero-inner { width: 100%; }
/* legibility over bare moving water is carried by the type, not by a scrim —
   same call, and same values, as the index hero */
.work-hero-eyebrow,
.work-hero-sub { text-shadow: 0 1px 2px rgba(2, 6, 12, .55), 0 6px 22px rgba(2, 6, 12, .62); }
.work-hero-eyebrow { justify-content: center; margin-bottom: clamp(12px, 1.8vh, 18px); }
.work-hero-title {
  font-size: clamp(2.3rem, 7vw, 4.4rem);
  letter-spacing: .045em;
  text-transform: uppercase;
  color: #fff;
  text-shadow: 0 6px 34px rgba(0, 0, 0, .75), 0 0 60px rgba(46, 127, 196, .30);
}
.work-hero-sub {
  margin: clamp(14px, 2.2vh, 22px) auto 0;
  max-width: 58ch;
  /* --text for the same reason as .hero-copy on the index: bare animating
     water behind it, no frost — the muted token dipped under 4.5:1 on crests. */
  color: var(--text);
  font-size: clamp(.96rem, 1.5vw, 1.08rem);
}

/* Clears the waterline fade before the first band of copy, the same way
   .band-services does on the index — but on a shorter lead-in, because this
   page has no intro to recover from and the whole point of the first screen
   is that the case study has already started by the time it ends. The fade
   itself (--wl-h) is still cleared; what came off is the section rhythm on
   top of it. */
.band-case { padding-top: calc(var(--sec-y) * .28 + var(--wl-h) * .82); }

/* "WET WELL REHABILITATION" is a two-word title and it should read as two
   words, not as "WET WELL / REHABILITATION" cut by the fold. 880px holds it
   on one line from about 1180px up; below that, balance splits it evenly
   instead of leaving one word stranded. */
.band-case .sec-head { max-width: 880px; }
.band-case .sec-head h2 { text-wrap: balance; }
/* The head sits on its own card rather than on a wall of tiles, and the card
   has to reach the fold: the standard 54px under a section head is 20px of
   the peek. */
.band-case .sec-head { margin-bottom: clamp(22px, 2.4vw, 34px); }

/* A crest is a card entering the frame. A 10px line of its top border is not
   a crest, it is a rendering artefact, and between roughly 700 and 850px of
   window height that is all there is room for. So below 820 the head takes
   the difference back as air and the card starts cleanly under the fold: on a
   short window the heading with room around it IS the first screen. Above it
   the natural peek is already 60px and climbing. */
@media (max-height: 820px) {
  .band-case .sec-head { margin-bottom: max(40px, calc(72vh - 425px)); }
}

/* ================================================================ CASE CARD */
/* The comparator is the argument; the copy is the footnote to it. It gets the
   larger track, and the card's own padding is the only inset on either side —
   the copy used to carry 18px of padding of its own, which put the photograph
   29px off the card edge and the prose 45px off it. */
.case {
  display: grid;
  grid-template-columns: minmax(0, 1.08fr) minmax(0, .92fr);
  gap: clamp(22px, 3vw, 46px);
  align-items: center;
  padding: clamp(16px, 2vw, 26px);
  background: var(--card-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-card);
}

/* ---- the comparator ---------------------------------------------------- */
.wipe {
  position: relative;
  margin: 0 auto;
  width: 100%;
  /* the pair is a matched 1200x1600, so the frame is the photograph's own
     shape and neither side is ever cropped differently from the other */
  aspect-ratio: 3 / 4;
  /* the cap rises with the column: at 900px of viewport it now clears the
     537px the wider track gives it, so the figure fills its side of the card
     instead of floating in the middle of it */
  max-width: min(100%, calc(86vh * 0.75));
  border-radius: var(--r-md);
  overflow: hidden;
  background: #060B12;
  border: 1px solid var(--line);
  cursor: ew-resize;
  /* horizontal drags belong to the comparator, vertical ones to the page */
  touch-action: pan-y;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.wipe-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  pointer-events: none;
  -webkit-user-drag: none;
}
/* the "before" plate, clipped to the living edge. clip-path is written per
   frame as a polygon in CSS pixels — polygon() rather than path() because it
   is supported everywhere and costs the compositor the same. */
.wipe-clip { position: absolute; inset: 0; will-change: clip-path; }

.wipe-edge {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
  filter: drop-shadow(0 0 6px rgba(0, 0, 0, .55));
}

.wipe-tag {
  position: absolute;
  top: 12px;
  padding: .45em .95em;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
  background: rgba(6, 11, 18, .78);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  font-size: .66rem;
  font-weight: 800;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--sky-bright);
  pointer-events: none;
  /* no transition: the opacity is written per frame from the divider's
     position, and a transition on top of that only adds lag */
}
.wipe-tag-before { left: 12px; }
.wipe-tag-after  { right: 12px; }

.wipe-handle {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 48px;
  /* JS writes translateX(edge x at mid-height); the negative margin is what
     centres the grip on that line rather than hanging it off the right */
  margin-left: -24px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: ew-resize;
  touch-action: pan-y;
}
.wipe-grip {
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid rgba(191, 219, 245, .55);
  background: linear-gradient(180deg, rgba(30, 118, 198, .92), rgba(0, 68, 127, .92));
  color: #fff;
  box-shadow: 0 10px 26px -12px rgba(0, 0, 0, .95), inset 0 1px 0 rgba(255, 255, 255, .3);
  transition: transform .2s var(--ease-out), background .2s ease;
}
.wipe:hover .wipe-grip,
.wipe-handle:focus-visible .wipe-grip { transform: scale(1.07); }
.wipe-handle:focus-visible { outline: none; }
.wipe-handle:focus-visible .wipe-grip {
  outline: 2px solid var(--sky-bright);
  outline-offset: 4px;
}
.wipe.is-dragging { cursor: grabbing; }
.wipe.is-dragging .wipe-grip { transform: scale(.95); }

/* ---- the way in --------------------------------------------------------
   Bottom left, opposite the AFTER pill, on the side the seam rests away from
   at 55%. It is inside the figure, so work.js takes its pointerdown out of
   the drag; the pointer-events line keeps the rest of the figure draggable. */
.wipe-full {
  position: absolute;
  left: 12px;
  bottom: 12px;
  z-index: 4;
  display: inline-flex;
  align-items: center;
  gap: .5em;
  padding: .5em .9em;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
  background: rgba(6, 11, 18, .78);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  font-family: inherit;
  font-size: .66rem;
  font-weight: 800;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--sky-bright);
  cursor: zoom-in;
  transition: background .2s ease, color .2s ease, border-color .2s ease;
}
.wipe-full:hover { background: var(--poseidon); color: #fff; border-color: var(--sky); }
.wipe-full:focus-visible { outline: 2px solid var(--sky-bright); outline-offset: 3px; }
.wipe-full svg { flex: none; }

/* ---- the copy beside it ------------------------------------------------ */
.case-copy { padding: 0; }
.case-kicker {
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--sky);
  margin-bottom: 12px;
}
.case-copy h3 {
  font-size: clamp(1.18rem, 2vw, 1.55rem);
  text-transform: uppercase;
  letter-spacing: .035em;
  color: #fff;
  margin-bottom: 16px;
}
.case-copy > p { color: var(--text-mute); font-size: .99rem; }
.case-copy > p + p { margin-top: 14px; }
.case-ticks { margin-top: 24px; }
.case-note {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
  font-size: .88rem;
  color: var(--text-dim);
  font-style: italic;
}

/* ================================================================= FILTERS */
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
}
/* .chip and its states now live in styles.css — shared with the FAQ page. */

.filter-status {
  margin-top: 16px;
  text-align: center;
  font-size: .78rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  /* --text-mute, not --text-dim: this line sits directly on the band glass
     with no card under it, and styles.css measures --text-dim at 3.9:1
     against the blurred sea's brightest patch there (which is why the footer
     lifts it locally). --text-mute is the 6:1 tone for that surface. */
  color: var(--text-mute);
}

/* ================================================================= GALLERY */
/* True masonry: a grid on a 4px row rhythm, each tile spanning the number of
   rows its own aspect ratio asks for. The span is computed in work.js from
   the manifest's w/h BEFORE a byte of image data arrives, so the layout is
   final from the first paint and no photograph can shift the page as it
   decodes. */
.gal {
  --gal-cols: 3;
  --feat-span: 2;
  margin-top: clamp(26px, 3.4vw, 40px);
  display: grid;
  grid-template-columns: repeat(var(--gal-cols), minmax(0, 1fr));
  gap: 18px;
  grid-auto-rows: 4px;
}
.tile {
  position: relative;
  grid-row-end: span var(--span, 100);
  border-radius: var(--r-md);
  overflow: hidden;
  border: 1px solid var(--line);
  background: #070D16;
  box-shadow: var(--sh-card);
  /* SURFACING: tiles rise out of the blur as they cross into view */
  opacity: 0;
  transform: translateY(14px);
  filter: blur(6px);
  transition: opacity .72s var(--ease-out), transform .72s var(--ease-out),
              filter .72s var(--ease-out), border-color .28s ease, box-shadow .28s ease;
}
.tile.is-in { opacity: 1; transform: translateY(0); filter: blur(0px); }
.tile[hidden] { display: none; }
/* Two tiles take two columns: the lead, and the one true landscape frame in
   the set. Both ride the same custom property, so both collapse to a single
   column at the same breakpoint. */
.tile.is-feature,
.tile.is-wide { grid-column: span var(--feat-span); }

.tile-btn {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
  overflow: hidden;
}
.tile-btn:focus-visible { outline: 2px solid var(--sky-bright); outline-offset: -3px; border-radius: 0; }

.tile-media { position: absolute; inset: 0; display: block; overflow: hidden; }
.tile-media img {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* the 1.06 headroom is what the depth parallax translates inside — without
     it a few pixels of travel would expose the frame at the top or bottom */
  transform: translate3d(0, 0, 0) scale(1.06);
  will-change: transform;
}
/* the per-photograph crop centre is written inline from the manifest's `pos`;
   this is only the default for frames that do not ask for one */
.tile-media img { object-position: 50% 50%; }

/* a hair of sea light across the top edge, the same gradient .card uses */
.tile::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  background: radial-gradient(120% 80% at 50% 0%, rgba(127, 182, 232, .12), transparent 58%);
  opacity: 0;
  transition: opacity .28s ease;
  z-index: 2;
}
.tile:hover { border-color: var(--line-strong); box-shadow: 0 34px 60px -34px rgba(0, 0, 0, .95), 0 0 0 1px rgba(127, 182, 232, .07); }
.tile:hover::after { opacity: 1; }

.tile-cap {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3;
  display: block;
  padding: 34px 15px 14px;
  text-align: left;
  background: linear-gradient(180deg, rgba(4, 8, 14, 0), rgba(4, 8, 14, .78) 42%, rgba(4, 8, 14, .95));
  opacity: 0;
  transform: translateY(10px);
  transition: opacity .3s var(--ease-out), transform .3s var(--ease-out);
}
.tile-btn:hover .tile-cap,
.tile-btn:focus-visible .tile-cap { opacity: 1; transform: translateY(0); }
/* Three captions are not decoration. The lead frame says what is happening in
   it, and the two location pills — GREENE TOWNSHIP, I-70 CORRIDOR — are the
   only place on the page that says these jobs happened HERE. A fact that only
   exists under a mouse pointer does not exist on a phone, on a tablet, or to
   anyone reading rather than pointing. */
.tile.is-capped .tile-cap { opacity: 1; transform: translateY(0); }
.tile-cap-text {
  display: block;
  font-size: .88rem;
  line-height: 1.45;
  color: var(--text);
  text-shadow: 0 1px 3px rgba(0, 0, 0, .8);
}
.tile-loc {
  display: inline-flex;
  align-items: center;
  gap: .45em;
  margin-top: 10px;
  padding: .35em .8em;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
  background: rgba(6, 11, 18, .8);
  font-size: .64rem;
  font-weight: 800;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--sky-bright);
}
.tile-loc svg { flex: none; }

/* Touch: there is no hover to reveal anything with, so the caption is simply
   part of the tile. The pointer test is there for the hybrids that report a
   coarse primary pointer — showing the caption anyway is the safe way to be
   wrong. */
@media (hover: none), (pointer: coarse) {
  .tile-cap { opacity: 1; transform: translateY(0); }
  .tile-btn { cursor: pointer; }
}

.gal-noscript {
  margin-top: 26px;
  text-align: center;
  color: var(--text-mute);
}
.gal-noscript a { color: var(--sky-bright); border-bottom: 1px solid var(--line-strong); }

/* ===================================================================== CTA */
/* padding-top: 0 made this the only band on either page with no air above it,
   which read as the CTA having been dropped on the end of the gallery rather
   than following it. It gets the same --sec-y every other band gets; the
   gallery's own flush bottom edge is what stops that reading as a gap. */
.workcta {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, .75fr);
  gap: clamp(22px, 3vw, 46px);
  /* the copy column is the taller one; the actions sit on its baseline
     instead of floating in the middle of a column of empty glass */
  align-items: end;
  padding: clamp(26px, 3.2vw, 44px);
  background: var(--card-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-card);
}
.workcta-copy h2 {
  font-size: clamp(1.45rem, 3vw, 2.2rem);
  text-transform: uppercase;
  letter-spacing: .025em;
  color: #fff;
  text-align: left;
}
/* 46ch, not 52ch: at 52 the last line of this paragraph is the single word
   "pump." */
.workcta-copy > p { margin-top: 16px; color: var(--text-mute); max-width: 46ch; }
.workcta-actions { display: grid; gap: 12px; justify-items: stretch; align-content: end; }
.workcta-quiet {
  margin-top: 4px;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 14px;
  font-size: .82rem;
  color: var(--text-dim);
}
.workcta-quiet a { color: var(--text-mute); border-bottom: 1px solid var(--line); }
.workcta-quiet a:hover { color: var(--sky-bright); border-bottom-color: var(--line-strong); }

/* ================================================================ LIGHTBOX */
.lb {
  position: fixed;
  inset: 0;
  z-index: 300;
}
.lb[hidden] { display: none; }
.lb-backdrop {
  position: absolute;
  inset: 0;
  /* dark enough that the page's own display type stops reading through it,
     transparent enough that the ocean is still down there */
  background: rgba(3, 6, 11, .93);
  backdrop-filter: blur(20px) saturate(.8);
  -webkit-backdrop-filter: blur(20px) saturate(.8);
  opacity: 0;
  transition: opacity .26s ease;
}
.lb.is-open .lb-backdrop { opacity: 1; }

.lb-dialog {
  position: relative;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(14px, 2vh, 22px);
  padding: clamp(58px, 8vh, 84px) clamp(56px, 8vw, 110px) clamp(20px, 4vh, 34px);
  opacity: 0;
  transform: scale(.985) translateY(8px);
  transition: opacity .28s var(--ease-out), transform .28s var(--ease-out);
}
.lb.is-open .lb-dialog { opacity: 1; transform: none; }

.lb-figure {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(14px, 2vh, 20px);
  min-height: 0;
  max-width: 100%;
}
.lb-stage {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  touch-action: pan-y;
}
.lb-img {
  max-width: min(100%, 1040px);
  max-height: 72vh;
  width: auto;
  height: auto;
  object-fit: contain;
  display: block;
  /* Same guard the comparator's plates carry. Without it a pointer drag
     across the photograph starts the browser's own image drag instead: the
     ghost thumbnail lifts off, the gesture ends in pointercancel, and the
     swipe that should have stepped to the next frame is swallowed. */
  -webkit-user-drag: none;
  -webkit-user-select: none;
  user-select: none;
  border-radius: var(--r-md);
  border: 1px solid rgba(127, 182, 232, .18);
  box-shadow: var(--sh-pop);
  background: #060B12;
}
.lb-cap { max-width: 68ch; text-align: center; }
.lb-caption {
  color: var(--text);
  font-size: clamp(.92rem, 1.3vw, 1.02rem);
  line-height: 1.55;
}
.lb-meta {
  margin-top: 10px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 14px;
  align-items: center;
  justify-content: center;
  font-size: .68rem;
  font-weight: 800;
  letter-spacing: .18em;
  text-transform: uppercase;
}
.lb-loc {
  padding: .38em .85em;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
  background: rgba(0, 90, 168, .22);
  color: var(--sky-bright);
}
.lb-loc[hidden] { display: none; }
.lb-count { color: var(--text-dim); letter-spacing: .2em; }

.lb-btn {
  position: absolute;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
  background: rgba(9, 16, 26, .72);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: var(--sky-bright);
  cursor: pointer;
  transition: background .2s ease, color .2s ease, border-color .2s ease, transform .18s var(--ease-out);
}
.lb-btn:hover { background: var(--poseidon); color: #fff; border-color: var(--sky); transform: scale(1.06); }
.lb-close { top: clamp(14px, 2.4vh, 26px); right: clamp(14px, 2.4vw, 30px); }
.lb-prev { left: clamp(10px, 2vw, 30px); top: 50%; margin-top: -24px; }
.lb-next { right: clamp(10px, 2vw, 30px); top: 50%; margin-top: -24px; }

/* ============================================================== RESPONSIVE */
/* No nav breakpoint here on purpose. This page used to fold the desktop nav
   one step earlier than the others, scoped to html[data-page="work"] — which
   meant the shared header behaved two different ways depending on the page
   you were on, and the override was invisible to anyone measuring the header
   anywhere else. The nav is four items now and the single hand-off lives in
   styles.css. A page stylesheet must not move a shared component's
   breakpoint. */

/* The tiles arrive from work.js; until they do this list is 0px tall and the
   footer sits 2400-4600px above where it will end up, so anyone scrolling in
   that window has the page yanked under their finger. Reserve roughly the
   built height per column count. work.js zeroes it once real tiles exist
   (filters may legitimately shrink the wall below these numbers), and the
   noscript style in work.html zeroes it when no script will ever come. */
.gal { min-height: 2450px; }

@media (max-width: 1000px) {
  /* The order is tuned against the three-column packer. Folded to two, the
     same sequence leaves the odd tile out and opens 400px holes, so the
     two-column wall is allowed to backfill them. Dense reordering is a
     visual-order change only, and on a wall of photographs — where every
     tile is its own subject and the reading order is "look at these" — that
     costs nothing the three-column layout was buying. */
  .gal { --gal-cols: 2; grid-auto-flow: row dense; min-height: 2800px; }
  .workcta { grid-template-columns: minmax(0, 1fr); }
  /* stacked, the actions are a row of buttons with the quiet line under them
     rather than a column, so flex says it more directly than the grid does */
  .workcta-actions { display: flex; flex-wrap: wrap; align-items: center; }
  .workcta-quiet { flex: 1 0 100%; margin-top: 0; }
}

@media (max-width: 900px) {
  .case { grid-template-columns: minmax(0, 1fr); }
  .wipe { max-width: min(100%, calc(66vh * 0.75)); }
}

@media (max-width: 700px) {
  .lb-dialog { padding: clamp(56px, 8vh, 76px) 14px 20px; }
  .lb-prev { left: 8px; }
  .lb-next { right: 8px; }
  .lb-btn { width: 44px; height: 44px; }
  .lb-img { max-height: 62vh; }
}

@media (max-width: 599px) {
  .gal { --gal-cols: 1; --feat-span: 1; gap: 14px; min-height: 4600px; }
  /* the title runs to two lines at this width, so the block is taller and
     its half-height — the thing the 42% centring is measured from — grows */
  .work-hero { --hero-half: 104px; }
  .workcta-actions { flex-direction: column; align-items: stretch; }
  .workcta-actions .btn { width: 100%; }
  .wipe-grip { width: 40px; height: 40px; }
  .wipe-tag { font-size: .6rem; letter-spacing: .16em; top: 10px; }
  .wipe-tag-before { left: 10px; }
  .wipe-tag-after { right: 10px; }
  .wipe-full { font-size: .6rem; letter-spacing: .12em; left: 10px; bottom: 10px; }

  /* ---- chips: one row you swipe, not three rows you read -------------
     Five chips wrapped into three ragged centred lines is the widest, least
     legible way to spend that space. One nowrap row, bled out through the
     wrap's gutters so it starts at the screen edge and is obviously longer
     than the screen, with the mask fading whatever is still to come. */
  .filters {
    flex-wrap: nowrap;
    justify-content: flex-start;
    gap: 8px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    /* Without this the snapport is the padding box, so "snap the first chip to
       the start" resolves to scrollLeft = one gutter — the row lands already
       scrolled and ALL sits half inside the mask's left fade. Insetting the
       snapport by the same gutter makes the resting position a real zero. */
    scroll-padding-inline: var(--gutter);
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 var(--gutter),
                        #000 calc(100% - var(--gutter)), transparent 100%);
            mask-image: linear-gradient(90deg, transparent 0, #000 var(--gutter),
                        #000 calc(100% - var(--gutter)), transparent 100%);
  }
  .filters::-webkit-scrollbar { display: none; }

  /* ---- captions: a strip under the photograph, not a lid on it -------
     At one column the permanent scrim covered the bottom third of every
     frame — on the tiles whose subject IS the bottom third, that is the
     photograph. Below the image the caption costs its own height and
     nothing else; work.js measures that height into the tile's span. */
  .tile { display: flex; }
  .tile-btn { position: static; display: flex; flex-direction: column; min-height: 0; }
  .tile-media { position: relative; inset: auto; flex: 1 1 auto; min-height: 0; }
  .tile-cap {
    position: static;
    flex: none;
    opacity: 1;
    transform: none;
    padding: 9px 13px 10px;
    background: rgba(6, 11, 18, .82);
    border-top: 1px solid var(--line);
  }
  .tile-cap-text { font-size: .78rem; line-height: 1.38; text-shadow: none; }
  .tile-loc { margin-top: 7px; padding: .3em .7em; }
}

/* A short window needs no special case any more: the hero's offset is a
   fraction of the window, so it shrinks with it. What a short window does not
   get is the card cresting the fold — there is only room for the heading, and
   cramming the card in on top of it would cost the heading its air. That
   happens by itself; nothing here forces it. */

/* ========================================================= REDUCED MOTION */
@media (prefers-reduced-motion: reduce) {
  /* the surfacing reveal is the animation, so there is nothing to reveal —
     the tiles are simply there */
  .tile { opacity: 1; transform: none; filter: none; transition: border-color .28s ease, box-shadow .28s ease; }
  .tile-cap { transition: none; }
  .lb-backdrop, .lb-dialog { transition: none; opacity: 1; transform: none; }
  .wipe-grip, .chip, .lb-btn, .wipe-full { transition: none; }
  .tile:hover, .chip:hover, .lb-btn:hover { transform: none; }
}
