/* ============================================================================
   OPTICS STUDIO — Desktop Refresh
   Figma: XaZAvutjF1mcTwpFgbCCUn · section 431:1322 "Desktop Refresh"

   Architecture: Readymag-style scaled canvas.
   Every slide is designed on a fixed 1728×1117 canvas. One CSS unit
   `--u = 100vw / 1728` maps a design pixel to a viewport-proportional
   length, so the whole deck scales with viewport WIDTH at any desktop size.

   Vertical behavior is NOT scaled-to-fit: each slide is a real 100svh tall
   and lays out as  grid-template-rows: auto 1fr auto 1fr auto
   (top-pinned / flex / middle-pinned / flex / bottom-pinned), so on shorter
   or taller viewports the flex rows absorb the difference while pinned
   content stays anchored.

   Step 1 of build order: shell + navbar + scaled canvas + Hero slide.
   Snap-to-slide / transitions land in step 3 (script.js).
   ============================================================================ */

/* ----------------------------------------------------------------------------
   1. Licensed fonts (Pangram Pangram). Serrif (Light) is the headline face;
   Saans is UI/body. Self-hosted woff2 with woff fallback.
   ---------------------------------------------------------------------------- */
@font-face {
  font-family: 'Serrif';
  src: url('assets/fonts/Serrif/Serrif-Light.woff2') format('woff2'),
       url('assets/fonts/Serrif/Serrif-Light.woff') format('woff');
  font-weight: 300; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Saans';
  src: url('assets/fonts/Saans/Saans-Light.woff2') format('woff2'),
       url('assets/fonts/Saans/Saans-Light.woff') format('woff');
  font-weight: 300; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Saans';
  src: url('assets/fonts/Saans/Saans-Regular.woff2') format('woff2'),
       url('assets/fonts/Saans/Saans-Regular.woff') format('woff');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Saans';
  src: url('assets/fonts/Saans/Saans-Medium.woff2') format('woff2'),
       url('assets/fonts/Saans/Saans-Medium.woff') format('woff');
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Saans';
  src: url('assets/fonts/Saans/Saans-SemiBold.woff2') format('woff2'),
       url('assets/fonts/Saans/Saans-SemiBold.woff') format('woff');
  font-weight: 600; font-style: normal; font-display: swap;
}

/* ----------------------------------------------------------------------------
   2. Tokens + scaled-canvas unit
   ---------------------------------------------------------------------------- */
:root {
  /* Two design-pixel units off the 1728×1117 canvas:

     --u  (size unit)   min() of the width- and height-scale. Drives
          typography, element heights, vertical rhythm, and text measures.
          On viewports shorter than the design ratio, content scales DOWN
          to keep every vertical relationship Figma-proportional instead
          of squashing the flexible gaps.

     --uw (width unit)  pure width-scale. Drives the horizontal layout —
          page margins and column gutters — so the canvas always fills the
          viewport width edge-to-edge like the design, no side gutters.

     At design-ratio viewports the two are equal; on taller-than-design
     screens width governs both and the pinned-zone rows flex as before. */
  --u: min(calc(100vw / 1728), calc(100svh / 1117));
  --uw: calc(100vw / 1728);

  --color-ink: #101010;
  --color-line: #e8e8e8;
  --color-bg: #ffffff;

  --font-serif: 'Serrif', 'Times New Roman', serif;
  --font-sans: 'Saans', 'Helvetica Neue', Arial, sans-serif;

  --nav-h: calc(var(--u) * 56);
  --margin: calc(var(--uw) * 40);  /* horizontal canvas edge margin (x=40) */

  /* Slides drawer (right). Drives both the drawer's slide-in distance and
     the page's opposite-direction push. Canvas-scale, so the drawer (and
     its 56-unit head, matching the nav) scales in lockstep with the site. */
  --drawer-width: calc(var(--u) * 426);
  --drawer-anim-ms: 500ms;
  --drawer-ease: cubic-bezier(0.32, 0.72, 0, 1);
}

/* ----------------------------------------------------------------------------
   3. Base
   ---------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html { background: var(--color-bg); }

body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-ink);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* ----------------------------------------------------------------------------
   3.5  Anim gate — every element the GSAP intro animates is held hidden
   before first paint via html.anim-pending (set by the inline head script).
   script.js replaces this with JS-driven initial states then drops the class.
   The reduced-motion block re-shows everything so those users never see a
   blank frame even if the gate is briefly present.
   ---------------------------------------------------------------------------- */
.anim-pending .slide--hero .slide__title,
.anim-pending .hero__booking,
.anim-pending .approach__lead,
.anim-pending .approach__row,
.anim-pending .slide--services .slide__title,
.anim-pending .scard,
.anim-pending .slide--samples .samples__col img,
.anim-pending .slide--process .slide__title,
.anim-pending .process__num,
.anim-pending .process__rule,
.anim-pending .process__step,
.anim-pending .slide--pricing .slide__title,
.anim-pending .pcard__credit,
.anim-pending .pcard,
.anim-pending .slide--contact .slide__title,
.anim-pending .contact__links,
.anim-pending .optics-bookend path {
  visibility: hidden;
}

@media (prefers-reduced-motion: reduce) {
  .anim-pending .slide--hero .slide__title,
  .anim-pending .hero__booking,
  .anim-pending .approach__lead,
  .anim-pending .approach__row,
  .anim-pending .slide--services .slide__title,
  .anim-pending .scard,
  .anim-pending .slide--samples .samples__video,
  .anim-pending .slide--process .slide__title,
  .anim-pending .process__num,
  .anim-pending .process__rule,
  .anim-pending .process__step,
  .anim-pending .slide--pricing .slide__title,
  .anim-pending .pcard__credit,
  .anim-pending .pcard,
  .anim-pending .slide--contact .slide__title,
  .anim-pending .contact__links,
  .anim-pending .optics-bookend path {
    visibility: visible;
  }
}

/* ----------------------------------------------------------------------------
   3.6  Preloader — full-bleed white overlay above all chrome (nav 100,
   drawer 200). Holds a centered OPTICS lockup at 400 canvas-units (300 on
   tablet). script.js staggers the letters in/out then swipes the overlay up;
   reduced-motion users skip it entirely.
   ---------------------------------------------------------------------------- */
.preloader {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: var(--color-bg);
  display: flex;
  align-items: center;
  justify-content: center;
  will-change: transform;
}

/* Soft shadow just past the overlay's bottom edge — invisible at rest (sits
   below the viewport), it travels up the revealed page as the overlay lifts,
   giving the white-on-white swipe a visible moving edge (same trick as the
   snap-to-slide top-edge shadow). */
.preloader::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  height: calc(var(--u) * 120);
  background: linear-gradient(to bottom, rgba(16, 16, 16, 0.12), rgba(16, 16, 16, 0));
  pointer-events: none;
}

.preloader__optics {
  display: block;
  width: calc(var(--u) * 400);
  height: auto;
  color: var(--color-ink);
}

.anim-pending .preloader__optics path { visibility: hidden; }

@media (prefers-reduced-motion: reduce) {
  .preloader { display: none; }
  .anim-pending .preloader__optics path { visibility: visible; }
}

/* ----------------------------------------------------------------------------
   4. Navbar — 56px scaled, fixed full-width. Brand left at x=40,
   slide indicator right (right edge at x=1688 → 40 from edge).
   No "Book a Call" link in this refresh yet.
   ---------------------------------------------------------------------------- */
.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-inline: var(--margin);
  background: var(--color-bg);
  border-bottom: 1px solid var(--color-line);
  transition:
    background-color 300ms ease,
    border-color 300ms ease,
    transform var(--drawer-anim-ms) var(--drawer-ease);
}

.nav__brand {
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: calc(var(--u) * 20);
  line-height: 1;
  text-transform: capitalize;
  color: var(--color-ink);
  text-decoration: none;
  transition: color 300ms ease;
}

/* dark variant while a data-theme="dark" slide (Contact) is active */
.nav--dark {
  background: #101010;
  border-bottom-color: #3c3c3c;
}
.nav--dark .nav__brand,
.nav--dark .nav__indicator { color: #fff; }

/* Samples (04) is dark too, but its stroke is one notch lighter than Contact's
   (data-slide is stamped by applyNavTheme). */
.nav--dark[data-slide="samples"] { border-bottom-color: #333333; }

.nav__indicator {
  /* button reset — the indicator opens the Slides drawer */
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  position: relative;
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: calc(var(--u) * 20);
  /* > 1 so the overflow clip box clears descenders (g/y/p) — at 1 the box
     equals the em square and Saans descenders get sheared off */
  line-height: 1.35;
  text-transform: capitalize;
  text-align: right;
  color: var(--color-ink);
  overflow: hidden; /* clips the slot-machine travel */
  transition: color 300ms ease;
}

.nav__indicator-label {
  display: inline-block;
  white-space: nowrap;
  transition: transform 400ms cubic-bezier(0.4, 0, 0.2, 1), opacity 400ms ease;
}

/* outgoing label leaves flow so the incoming one sizes the box */
.nav__indicator-label.is-exiting {
  position: absolute;
  top: 0;
  right: 0;
}

@media (prefers-reduced-motion: reduce) {
  .nav__indicator-label { transition: none; }
}

/* ----------------------------------------------------------------------------
   5. Slides — scaled canvas + pinned zones.

   Each slide = 100svh, padded by the nav height on top and the 40px canvas
   margin on the sides/bottom. Rows: top-pinned / flex / middle-pinned /
   flex / bottom-pinned. Empty zones collapse to 0 and the 1fr rows share
   whatever height the viewport adds or removes vs. the 1117 design canvas.
   ---------------------------------------------------------------------------- */
.deck {
  position: relative;
  /* pushed horizontally while a drawer is open — same easing + duration as
     the drawer itself so the two motions read as one fluid gesture */
  transition: transform var(--drawer-anim-ms) var(--drawer-ease);
}

/* Body class is the toggle surface: page (nav + deck) pushes left by the
   drawer width in the direction the drawer opens from. The transform goes
   on .nav/.deck, NOT body — a body transform would re-anchor the fixed nav
   and break the sticky slide ladder. */
body.drawer-slides-open .nav,
body.drawer-slides-open .deck {
  transform: translateX(calc(-1 * var(--drawer-width)));
}

.slide {
  /* sticky ladder: every slide pins at the top of the viewport, so the next
     slide in flow rises up OVER the pinned one during a transition.
     z-index ladder is assigned in script.js (DOM order → ascending). */
  position: sticky;
  top: 0;
  height: 100svh;
  padding: var(--nav-h) var(--margin) calc(var(--u) * 40);
  display: grid;
  grid-template-columns: 100%;
  grid-template-rows: auto 1fr auto 1fr auto;
  background: var(--color-bg);
}

/* subtle shadow cast upward from a rising slide's top edge — only while a
   snap transition is running (at rest the next slide's shadow would
   otherwise leak into the bottom of the viewport) */
.slide + .slide::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: calc(var(--u) * 120);
  background: linear-gradient(to top, rgba(16, 16, 16, 0.10), rgba(16, 16, 16, 0));
  pointer-events: none;
  opacity: 0;
  transition: opacity 200ms ease;
}

.deck.is-animating .slide + .slide::before { opacity: 1; }

.zone--top    { grid-row: 1; }
.zone--mid    { grid-row: 3; }
.zone--bottom { grid-row: 5; }

/* ----------------------------------------------------------------------------
   5.5  Shared slide title — Serrif Light 40/1.1, 533w at (40, 96).
   Used by Hero, Services, Process, Pricing, Contact.
   ---------------------------------------------------------------------------- */
.slide__title {
  margin: calc(var(--u) * 40) 0 0;   /* nav 56 + 40 → y=96 on the canvas */
  max-width: calc(var(--u) * 533);
  font-family: var(--font-serif);
  font-weight: 300;
  font-size: calc(var(--u) * 40);
  line-height: 1.1;
  letter-spacing: calc(var(--u) * -0.4);
  color: #000;
  font-feature-settings: 'salt' 1, 'sinf' 1, 'lnum' 1, 'pnum' 1;
}

/* ----------------------------------------------------------------------------
   5.6  Shared four-card row — cols at x 40/458/876/1294, 394w, 24 gutters
   ---------------------------------------------------------------------------- */
.cards {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: calc(var(--uw) * 24);
  /* Cards share a baseline; the Audit column's credit box extends it upward. */
  align-items: end;
}

/* ----------------------------------------------------------------------------
   6. Hero (01) — node 425:3
   Top-pinned: title (y=96, 533w, Serrif Light 40) + Book a call (y=252).
   Bottom-pinned: OPTICS bookend (1648×348, 40 off the bottom edge).
   ---------------------------------------------------------------------------- */

.hero__booking {
  display: inline-block;
  margin-top: calc(var(--u) * 24);   /* title bottom 228 + 24 → y=252 */
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: calc(var(--u) * 20);
  line-height: 1;
  color: var(--color-ink);
}

/* ----------------------------------------------------------------------------
   Text-link underline (ported from the previous build).
   A 2px underline is shown at rest; on hover it sweeps out to the right and
   back in from the left, looping for as long as the link is hovered. The
   loop starts and ends at full width so it joins the resting underline with
   no blink. currentColor so it works on light and dark slides.
   ---------------------------------------------------------------------------- */
.text-link {
  position: relative;
  display: inline-block;
  text-decoration: none;
}

.text-link::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(var(--u) * -3);
  height: 2px;
  background: currentColor;
  transform: scaleX(1);            /* resting underline */
  transform-origin: left center;
}

.text-link:hover::after { animation: textlink-sweep 1.1s ease-in-out infinite; }

/* full → collapse off to the right → regrow in from the left → full: one
   continuous left-to-right sweep that loops, beginning at the resting state. */
@keyframes textlink-sweep {
  0%   { transform: scaleX(1); transform-origin: right center; }
  45%  { transform: scaleX(0); transform-origin: right center; }
  55%  { transform: scaleX(0); transform-origin: left center; }
  100% { transform: scaleX(1); transform-origin: left center; }
}

@media (prefers-reduced-motion: reduce) {
  /* no looping motion — the static resting underline simply stays */
  .text-link:hover::after { animation: none; }
}

.hero__bookend,
.contact__bookend { line-height: 0; }

.optics-bookend {
  display: block;
  width: 100%;          /* 1648 design px = full content width */
  height: auto;
  color: var(--color-ink);
}

/* ----------------------------------------------------------------------------
   7. Philosophy (02) — node 425:61
   Top-pinned: intro pair, right-anchored (534 + 24 gap + 533 → left edge
   x=597, flush with the numerals below). Middle-pinned: three numbered
   rows — numeral col at x=597, body col at x=1155 (533w), 204px pitch.

   Flex-row weights: in the 1117 design the gap above the middle block is
   100px (272→372) and below it 219px (858→1077), so the two 1fr rows get
   100fr/219fr — exact Figma placement at design height, proportional
   absorption everywhere else.
   ---------------------------------------------------------------------------- */
.slide--philosophy {
  grid-template-rows: auto 100fr auto 219fr auto;
}

.approach__lead {
  margin-top: calc(var(--u) * 40);   /* nav 56 + 40 → y=96 */
  display: flex;
  gap: calc(var(--u) * 24);
  justify-content: flex-end;
  font-family: var(--font-serif);
  font-weight: 300;
  font-size: calc(var(--u) * 40);
  line-height: 1.1;
  letter-spacing: calc(var(--u) * -0.4);
  color: #000;
}

.approach__lead p { margin: 0; }
.approach__lead p:first-child { width: calc(var(--u) * 534); }
.approach__lead p:last-child  { width: calc(var(--u) * 533); }

.approach__row {
  /* right-anchored like the lead pair above: numeral col left edge lines up
     with the first lead paragraph (canvas x=597 = right margin − 1091) */
  width: calc(var(--u) * 1091);
  margin-left: auto;
  display: grid;
  grid-template-columns: calc(var(--u) * 558) calc(var(--u) * 533);
}

.approach__row:not(:last-child) { min-height: calc(var(--u) * 204); }

.approach__num {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: calc(var(--u) * 24);
  line-height: 1.1;
  letter-spacing: calc(var(--u) * -0.24);
  color: #000;
}

.approach__body {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: calc(var(--u) * 20);
  line-height: 1.3;
  color: #727272;
}

/* ----------------------------------------------------------------------------
   8. Services (03) — node 429:100
   Top-pinned: title. Bottom-anchored (Figma: bottom 40): four work-type
   cards, 394×614, gradient bg, 32px inner inset. Head 0–134, five 64px
   rows, description 40 below the last row.
   ---------------------------------------------------------------------------- */
.scard {
  display: flex;
  flex-direction: column;
  height: calc(var(--u) * 614);
  border: 1px solid var(--color-line);
  border-radius: calc(var(--u) * 2);
  background: linear-gradient(180deg, #ffffff 0%, #f9f9f9 100%);
  overflow: hidden;
}

.scard__head {
  /* title at 32 from card top, rows at 134 (less the card + list hairlines) */
  height: calc(var(--u) * 134 - 2px);
  padding: calc(var(--u) * 32) calc(var(--u) * 32) 0;
}

.scard__name {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: calc(var(--u) * 24);
  line-height: 1.1;
  color: var(--color-ink);
}

.scard__list {
  margin: 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--color-line);
}

.scard__list li {
  display: flex;
  align-items: center;
  height: calc(var(--u) * 64);
  padding-inline: calc(var(--u) * 32);
  background: #fff;
  border-bottom: 1px solid var(--color-line);
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: calc(var(--u) * 16);
  line-height: 1;
  color: var(--color-ink);
}

.scard__desc {
  margin: calc(var(--u) * 40) 0 0;
  padding-inline: calc(var(--u) * 32);
  max-width: calc(var(--u) * 330);
  box-sizing: content-box;
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: calc(var(--u) * 16);
  line-height: 1.3;
  color: #727272;
}

/* ----------------------------------------------------------------------------
   9. Process (05) — node 431:809
   Top-pinned: title (2 lines → bottom 184). Middle-pinned block 407–725:
   ghost numerals (112, #e8e8e8, baseline 519) · rule at canvas center 559 ·
   step names (599) · step lists (647). Gaps 223 above / 352 below.
   ---------------------------------------------------------------------------- */
.slide--process {
  grid-template-rows: auto 223fr auto 352fr auto;
}

.process__nums,
.process__steps {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: calc(var(--uw) * 24);
}

.process__num {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: 300;
  font-size: calc(var(--u) * 112);
  line-height: 1;
  letter-spacing: calc(var(--u) * -1.12);
  color: #e8e8e8;
}

.process__rule {
  margin-top: calc(var(--u) * 40);     /* numeral bottom 519 → rule 559 */
  border-top: 1px solid var(--color-line);
}

.process__steps {
  margin-top: calc(var(--u) * 41);     /* rule 560 → names ~601 */
}

.process__name {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: calc(var(--u) * 20);
  line-height: 1;
  letter-spacing: calc(var(--u) * -0.2);
  color: var(--color-ink);
}

.process__items {
  margin: calc(var(--u) * 26) 0 0;     /* names bottom 621 → lists 647 */
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: calc(var(--u) * 20);
  line-height: 1.3;
  letter-spacing: calc(var(--u) * -0.2);
  text-transform: capitalize;
  color: #3c3c3c;
}

/* ----------------------------------------------------------------------------
   10. Pricing (06) — node 431:1076
   Top-pinned: title. Bottom-anchored (cards bottom 40): four pricing cards
   394×634 with 24px inner inset: head 0–130 · qualifier row 64 · price block
   128 · use-case row 64 · description · CTA 40 off the card bottom. The Audit
   column (468:102) carries an 88-unit credit box stacked above its card.
   ---------------------------------------------------------------------------- */
/* Audit column: credit box (88) sits flush atop the 634 card → 722 tall. */
.pcard-col {
  display: flex;
  flex-direction: column;
}

.pcard__credit {
  display: flex;
  align-items: center;
  flex: 0 0 auto;   /* hold height inside the column; never compress on tablet */
  height: calc(var(--u) * 88);
  margin: 0;
  padding: 0 calc(var(--u) * 24);
  background: #fbfbfb;
  border: 1px solid var(--color-line);
  border-bottom: 0;   /* the card's top border serves as the divider */
  border-radius: calc(var(--u) * 2) calc(var(--u) * 2) 0 0;
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: calc(var(--u) * 16);
  line-height: 1.3;
  color: #3c3c3c;
}

/* Square the Audit card's top corners so it joins the credit box above it. */
.pcard-col .pcard {
  flex: 0 0 auto;   /* keep the 634-unit card body from shrinking in the column */
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

.pcard {
  display: flex;
  flex-direction: column;
  height: calc(var(--u) * 634);
  border: 1px solid var(--color-line);
  border-radius: calc(var(--u) * 2);
  background: linear-gradient(180deg, #ffffff 0%, #f9f9f9 100%);
  overflow: hidden;
}

.pcard__head {
  height: calc(var(--u) * 130);
  padding: calc(var(--u) * 40) calc(var(--u) * 24) 0;
}

.pcard__name {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: calc(var(--u) * 24);
  line-height: 1.1;
  color: var(--color-ink);
}

.pcard__tag {
  margin: calc(var(--u) * 8) 0 0;
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: calc(var(--u) * 16);
  line-height: 1;
  color: #7c7c7c;
}

.pcard__row {
  display: flex;
  align-items: center;
  height: calc(var(--u) * 64);
  padding-inline: calc(var(--u) * 24);
  background: #fff;
  border-top: 1px solid var(--color-line);
  border-bottom: 1px solid var(--color-line);
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: calc(var(--u) * 16);
  line-height: 1;
  color: var(--color-ink);
}

.pcard__row--uses {
  border-top: 0;
  font-weight: 500;
  line-height: 1.3;
}

.pcard__price {
  height: calc(var(--u) * 128);
  padding: calc(var(--u) * 30) calc(var(--u) * 24) 0;
  background: #fff;
  border-bottom: 1px solid var(--color-line);
}

.pcard__amount {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: calc(var(--u) * 40);
  line-height: 1;
  letter-spacing: calc(var(--u) * -0.4);
  color: var(--color-ink);
}

.pcard__timeline {
  margin: calc(var(--u) * 12) 0 0;
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: calc(var(--u) * 16);
  line-height: 1;
  color: var(--color-ink);
}

.pcard__desc {
  margin: calc(var(--u) * 28) 0 0;
  padding-inline: calc(var(--u) * 24);
  max-width: calc(var(--u) * 346);   /* design text measure — keeps line breaks */
  box-sizing: content-box;
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: calc(var(--u) * 16);
  line-height: 1.3;
  color: #3c3c3c;
}

/* Figma colors the Large + Custom descriptions full ink */
.pcard:nth-child(n+3) .pcard__desc { color: var(--color-ink); }

.pcard__cta {
  margin: auto calc(var(--u) * 24) calc(var(--u) * 40);
  display: flex;
  align-items: center;
  justify-content: center;
  height: calc(var(--u) * 72);
  background: var(--color-ink);
  border-radius: calc(var(--u) * 4);
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: calc(var(--u) * 16);
  line-height: 1;
  color: #fff;
  text-decoration: none;
  font-feature-settings: 'ordn' 1, 'dlig' 1;
}

/* ----------------------------------------------------------------------------
   11. Contact (07) — node 431:1174 · dark slide
   Same composition as Hero on #101010: white title + two links at y=252
   (Book a call at x=40, email at x=177), dim #202020 OPTICS bookend.
   ---------------------------------------------------------------------------- */
.slide--contact { background: #101010; }

.slide--contact .slide__title { color: #fff; }

.contact__links {
  margin-top: calc(var(--u) * 24);   /* title bottom 228 + 24 → y=252 */
  display: flex;
  gap: calc(var(--u) * 19);          /* Book a call box ends x=158, email starts x=177 */
}

/* Figma gives the first link a 118px slot; pin it so the email lands at x=177.
   The width lives on a wrapper, not the link, so the link hugs its text and
   the hover underline matches the text rather than the wider box. */
.contact__slot { width: calc(var(--u) * 118); }

.contact__links a {
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: calc(var(--u) * 20);
  line-height: 1;
  color: #fff;
}

.optics-bookend--dim { color: #202020; }

/* ----------------------------------------------------------------------------
   11.5  Carousel pagination dots + Samples grid — tablet-only components,
   present in the desktop DOM but hidden until the tablet breakpoint.
   Dots: 8px circles, 24 center-to-center; active = filled ink, rest hollow.
   ---------------------------------------------------------------------------- */
.carousel-dots {
  display: none;
  gap: calc(var(--u) * 16);                /* 24 center-to-center − 8 dot */
  margin-top: calc(var(--u) * 40);         /* cards bottom 962 → dots 1002 */
}

.carousel-dots button {
  width: calc(var(--u) * 8);
  height: calc(var(--u) * 8);
  padding: 0;
  border: calc(var(--u) * 2) solid var(--color-ink);
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
}

.carousel-dots button[aria-current="true"] { background: var(--color-ink); }

/* ----------------------------------------------------------------------------
   11.6  Samples (04) — dark slide, full-bleed deck reel (Figma 431:729 · 605:2154).
   The slide drops its pinned-zone grid and side/bottom margins so the video
   bleeds to the edges below the nav. object-fit: contain keeps the 16:9 frame
   uncropped at any viewport aspect; the matching #101010 backdrop hides the
   letterbox — near-full on the wide desktop canvas, a centred band on portrait
   mobile. No per-breakpoint sizing needed: width/height 100% + contain scale it.
   ---------------------------------------------------------------------------- */
.slide--samples {
  background: #101010;
  padding: var(--nav-h) 0 0;   /* starts below the nav; bleeds to side + bottom edges */
  display: block;              /* override the five-row pinned grid */
}

.samples__video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: #101010;
}

/* ----------------------------------------------------------------------------
   12. Slides drawer (right) — ported from the previous build.
   Fixed 426px panel, dark #101010, slides in via translateX while the page
   pushes the opposite way (see .deck/.nav push rules above). Backdrop is a
   transparent click-catcher only — the design pushes rather than dims.
   Drawer chrome uses fixed px sizing, not canvas units.
   ---------------------------------------------------------------------------- */
.drawer {
  position: fixed;
  top: 0;
  width: var(--drawer-width);
  height: 100svh;
  background: #101010;
  color: #ffffff;
  z-index: 200;
  display: flex;
  flex-direction: column;
  visibility: hidden;
  /* visibility delay equals the duration so the drawer stays in the tree
     long enough to animate out */
  transition:
    transform var(--drawer-anim-ms) var(--drawer-ease),
    visibility 0s linear var(--drawer-anim-ms);
}

.drawer--right {
  right: 0;
  transform: translateX(100%);
  border-left: 1px solid #3c3c3c;
}

.drawer.is-open {
  visibility: visible;
  transform: translateX(0);
  transition:
    transform var(--drawer-anim-ms) var(--drawer-ease),
    visibility 0s linear 0s;
}

.drawer__head {
  /* Identical bar to the site nav — same --nav-h token, and the SAME flex
     centering the nav uses (display:flex + align-items:center + space-between).
     Was display:grid with a single auto row: iOS Safari parks that row at the
     top of the taller container (it doesn't stretch/center it like Chrome), so
     the text jammed against the top edge instead of centering. Flexbox centers
     reliably on every engine, exactly as the nav already does. */
  flex: none;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 calc(var(--u) * 24);
  border-bottom: 1px solid #3c3c3c;
}

.drawer__title {
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: calc(var(--u) * 16);
  /* line-height 1 (not normal) so the line box equals the font size and the
     text centers at the same y as the site nav brand (which is also lh 1) —
     matches the Figma nav text placement exactly. */
  line-height: 1;
  color: #fff;
  text-transform: capitalize;
}

.drawer__close {
  background: transparent;
  border: 0;
  padding: 0;
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: calc(var(--u) * 16);
  line-height: 1; /* see .drawer__title — matches the site nav metrics */
  color: #fff;
  text-transform: capitalize;
  cursor: pointer;
}

.drawer-backdrop {
  position: fixed;
  inset: 0;
  background: transparent;
  z-index: 150;
  pointer-events: none;
}

.drawer-backdrop.is-open { pointer-events: auto; }

/* --- thumbnail list: live CSS-scaled clones of each slide ----------------- */
.drawer__list {
  list-style: none;
  margin: 0;
  padding: calc(var(--u) * 24);
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: calc(var(--u) * 48);
  scrollbar-width: thin;
  scrollbar-color: #3c3c3c transparent;
}

.drawer__list::-webkit-scrollbar { width: 6px; }
.drawer__list::-webkit-scrollbar-track { background: transparent; }
.drawer__list::-webkit-scrollbar-thumb { background: #3c3c3c; border-radius: 3px; }

.slides-thumb-row {
  display: flex;
  flex-direction: column;
  gap: calc(var(--u) * 16);
}

.slides-thumb {
  position: relative;
  width: 100%;
  border-radius: calc(var(--u) * 2);
  overflow: hidden;
  background: #ffffff;
  cursor: pointer;
  border: 1px solid #3c3c3c;
  padding: 0;
  display: block;
  /* Buttons default to text-align: center — the slide clones inside must
     inherit the page's default (start) to mirror the live slides. */
  text-align: start;
}

.slides-thumb:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 2px;
}

.slides-thumb__frame {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: top left;
  pointer-events: none;
}

/* Clones must reset the sticky ladder so they render in their natural
   place inside the frame, not pinned to the viewport like live slides. */
.slides-thumb__frame .slide {
  position: relative !important;
  z-index: auto !important;
  width: 100%;
  height: 100%;
}

.slides-thumb-meta {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: calc(var(--u) * 16);
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: calc(var(--u) * 16);
  text-transform: capitalize;
  pointer-events: none;
}

.slides-thumb-meta__name { color: #fff; }
.slides-thumb-meta__active { color: #9c9c9c; }

.slides-thumb-row[data-active="false"] .slides-thumb-meta__active { display: none; }

/* Tablet/mobile list view + CTA (467:39) — desktop shows neither. */
.slides-thumb__field { display: none; }
.drawer__foot { display: none; }

@media (prefers-reduced-motion: reduce) {
  .drawer,
  .drawer.is-open,
  .deck,
  .nav { transition: none; }
}

/* ============================================================================
   13. TABLET REFRESH — Figma section 440:1323, 744×1050 canvas, ≤1024px.
   Same architecture as desktop: --u/--uw remap to the tablet canvas so every
   canvas-unit dimension rescales automatically; below are only the values
   and layouts that the tablet design actually changes.

   · Titles: 32/1.1 −0.32 at a 493 measure (Hero alone: 36, no tracking).
   · Philosophy: lead pair stacks full-width; numbered rows tighten to a
     171/1fr grid with a 142 pitch.
   · Services / Pricing / Process: edge-bleed horizontal snap carousels with
     pagination dots (cards keep their desktop dimensions; card 2 peeks).
   · Samples: two-column placeholder grid.
   Mobile (later) will carve its own range out of the bottom of this one.
   ============================================================================ */
@media (max-width: 1024px) {
  :root {
    --u: min(calc(100vw / 744), calc(100svh / 1050));
    --uw: calc(100vw / 744);
    /* Menu panel narrows to the tablet design's 382/744 (≈51vw). */
    --drawer-width: calc(var(--uw) * 382);
  }

  /* Samples reel (tablet + mobile): cover-fit so the portrait area under the
     nav is filled to max height; the sides crop rather than letterbox. The
     narrower reel is chosen in script.js. */
  .samples__video { object-fit: cover; }

  /* --- shared title -------------------------------------------------------- */
  .slide__title {
    max-width: calc(var(--u) * 493);
    font-size: calc(var(--u) * 32);
    letter-spacing: calc(var(--u) * -0.32);
  }

  .slide--hero .slide__title {
    font-size: calc(var(--u) * 36);
    letter-spacing: 0;
  }

  .preloader__optics { width: calc(var(--u) * 300); }

  /* --- Philosophy (440:1324) — lead stacks, rows tighten -------------------- */
  .slide--philosophy {
    /* design gaps: lead bottom 322 → rows 386 (64) · block bottom 748 →
       content edge 1010 (262) */
    grid-template-rows: auto 64fr auto 262fr auto;
  }

  .approach__lead {
    flex-direction: column;
    gap: calc(var(--u) * 16);
    font-size: calc(var(--u) * 32);
    letter-spacing: calc(var(--u) * -0.32);
  }

  .approach__lead p:first-child,
  .approach__lead p:last-child { width: 100%; }   /* 664 = full measure */

  .approach__row {
    width: 100%;
    /* numerals at x=40 (col start), bodies at x=211 → 171 numeral column */
    grid-template-columns: calc(var(--u) * 171) 1fr;
  }

  .approach__row:not(:last-child) { min-height: calc(var(--u) * 142); }

  .approach__num {
    font-size: calc(var(--u) * 20);
    letter-spacing: calc(var(--u) * -0.2);
  }

  /* --- carousels: cards rows + the process block --------------------------- */
  /* Edge-bleed scrollers: negative margins out to the viewport edge, padding
     back in so content rests on the 40-unit canvas margin at either end. */
  /* Philosophy is excluded — it only becomes a scroller at the mobile range. */
  [data-carousel]:not(.approach__rows) {
    overflow-x: auto;
    margin-inline: calc(-1 * var(--margin));
    padding-inline: var(--margin);
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--margin);
    touch-action: pan-x;          /* horizontal = native pan; vertical = snap */
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }

  [data-carousel]:not(.approach__rows)::-webkit-scrollbar { display: none; }

  [data-snap]:not(.approach__row) { scroll-snap-align: start; } /* off-range targets clamp to the end */

  /* Two steps per page here, so only 01 and 03 are snap points on tablet. */
  .slide--process .process__num:nth-child(2),
  .slide--process .process__num:nth-child(4) { scroll-snap-align: none; }

  .carousel-dots { display: flex; }
  .carousel-dots[data-carousel-dots="philosophy"] { display: none; }

  .cards {
    display: flex;
    /* gap in size units, not width units — inside a scroller the gutter
       belongs to the content, not the viewport frame */
    gap: calc(var(--u) * 24);
  }

  .scard, .pcard, .pcard-col { flex: 0 0 calc(var(--u) * 394); }

  /* --- Process (443:1990) — two steps per page ------------------------------ */
  .slide--process {
    /* title bottom 166 → block 407 (241) · block bottom 725 → dots 1002 (277) */
    grid-template-rows: auto 241fr auto 277fr auto;
  }

  .slide--process .carousel-dots { margin-top: 0; } /* gap lives in the fr row */

  .process__nums,
  .process__steps {
    grid-template-columns: repeat(4, calc(var(--u) * 394));
    gap: calc(var(--u) * 24);
    width: max-content;
  }

  .process__rule { width: calc(var(--u) * 1648); } /* spans all four columns */

  /* Samples reel scales by object-fit (see §11.6) — no tablet override needed. */

  /* --- Slides menu (467:39) — image thumbnails collapse to a tap list -------
     Each row becomes a 72-unit field: a hairline divider, the slide name
     right-aligned in white, and a dot marking the current slide. The
     thumbnail clones still build but stay hidden, so the active-state and
     navigation logic is shared with desktop. A pinned "Book a Call" CTA sits
     below the (scrollable) list. */
  .drawer__head { padding: 0 calc(var(--u) * 20); }
  .drawer__title,
  .drawer__close { font-size: calc(var(--u) * 20); }

  .drawer__list { padding: 0; gap: 0; }
  .slides-thumb-row { gap: 0; }

  .slides-thumb__frame,
  .slides-thumb-meta { display: none; }

  .slides-thumb {
    height: calc(var(--u) * 72);
    border: 0;
    border-bottom: 1px solid #3c3c3c;
    border-radius: 0;
    background: transparent;
    overflow: visible;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 calc(var(--u) * 20);
  }

  /* Active-slide dot (held in layout when inactive so names stay aligned). */
  .slides-thumb::before {
    content: '';
    flex: 0 0 auto;
    width: calc(var(--u) * 8);
    height: calc(var(--u) * 8);
    border-radius: 50%;
    background: #ffffff;
  }
  .slides-thumb-row[data-active="false"] .slides-thumb::before { visibility: hidden; }

  .slides-thumb__field {
    display: block;
    font-family: var(--font-sans);
    font-weight: 400;
    font-size: calc(var(--u) * 22);
    line-height: 1;
    color: #ffffff;
    text-transform: capitalize;
    text-align: right;
  }

  .drawer__foot {
    display: block;
    flex: 0 0 auto;
    border-top: 1px solid #3c3c3c;
    padding: calc(var(--u) * 24) calc(var(--u) * 20);
  }

  .drawer__cta {
    display: flex;
    align-items: center;
    justify-content: center;
    height: calc(var(--u) * 64);
    border: 1px solid #ffffff;
    border-radius: calc(var(--u) * 4);
    font-family: var(--font-sans);
    font-weight: 400;
    font-size: calc(var(--u) * 16);
    color: #ffffff;
    text-decoration: none;
    font-feature-settings: "ordn" 1, "dlig" 1;
  }
}

/* ============================================================================
   14. MOBILE — Figma section 452:2051 "Lite Mobile", 390×844 canvas, ≤600px.
   Carves the bottom of the tablet range: --u/--uw remap to the phone canvas so
   every canvas-unit dimension rescales, and below are only the values the
   mobile design changes. Carousels go one card per page; Philosophy joins the
   carousel set (leads pinned, the three insights swipe); the Process hairline
   runs continuously across its four step-pages; the Pricing Audit credit note
   is dropped; the slides menu fills the screen.
   ============================================================================ */
@media (max-width: 600px) {
  :root {
    --u: min(calc(100vw / 390), calc(100svh / 844));
    --uw: calc(100vw / 390);
    --margin: calc(var(--uw) * 24);          /* content margin (x=24) */
    --drawer-width: calc(var(--uw) * 390);   /* menu fills the screen */
  }

  /* --- nav: 18 type at a 20 margin ---------------------------------------- */
  .nav { padding-inline: calc(var(--uw) * 20); }
  .nav__brand,
  .nav__indicator { font-size: calc(var(--u) * 18); }

  /* --- shared title: 26/1.2 · Hero + Contact 28/1.15 ---------------------- */
  .slide__title {
    margin-top: calc(var(--u) * 24);          /* nav 56 + 24 → y=80 */
    max-width: calc(var(--u) * 342);
    font-size: calc(var(--u) * 26);
    line-height: 1.2;
    letter-spacing: calc(var(--u) * -0.26);
  }

  .slide--hero .slide__title,
  .slide--contact .slide__title {
    font-size: calc(var(--u) * 28);
    line-height: 1.15;
    letter-spacing: calc(var(--u) * -0.28);
  }

  /* Hero + Contact pin the OPTICS bookend 24 off the bottom (not 40). */
  .slide--hero,
  .slide--contact { padding-bottom: calc(var(--u) * 24); }

  /* --- Philosophy (452:2399) — leads pinned, insights become a carousel --- */
  .slide--philosophy {
    /* leads bottom 344 → insight 544 (200) · insight bottom 680 → dots 796 (116) */
    grid-template-rows: auto 200fr auto 116fr auto;
  }

  .approach__lead {
    margin-top: calc(var(--u) * 24);
    font-size: calc(var(--u) * 26);
    letter-spacing: calc(var(--u) * -0.26);   /* column + 16 gap stay from tablet */
  }

  .approach__rows {
    display: flex;
    overflow-x: auto;
    margin-inline: calc(-1 * var(--margin));
    padding-inline: var(--margin);
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--margin);
    touch-action: pan-x;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    gap: calc(var(--uw) * 24);
  }
  .approach__rows::-webkit-scrollbar { display: none; }

  .approach__row {
    flex: 0 0 100%;
    width: auto;
    margin-left: 0;
    display: flex;
    flex-direction: column;
    scroll-snap-align: start;
  }
  .approach__row:not(:last-child) { min-height: 0; }

  .approach__num {
    font-size: calc(var(--u) * 18);
    letter-spacing: calc(var(--u) * -0.18);
  }
  .approach__body {
    margin-top: calc(var(--u) * 24);
    font-size: calc(var(--u) * 18);
  }

  .slide--philosophy .carousel-dots { margin-top: 0; } /* dots pin to the bottom */
  .carousel-dots[data-carousel-dots="philosophy"] { display: flex; }

  /* --- carousels: one card per page --------------------------------------- */
  .scard, .pcard, .pcard-col { flex: 0 0 100%; }

  /* --- Services (452:2454) — 342×512 card, 24 inset ----------------------- */
  .scard { height: calc(var(--u) * 512); }
  .scard__head {
    height: calc(var(--u) * 126 - 2px);
    padding: calc(var(--u) * 24) calc(var(--u) * 24) 0;
  }
  .scard__list li { height: calc(var(--u) * 48); padding-inline: calc(var(--u) * 24); }
  .scard__desc {
    margin-top: calc(var(--u) * 24);
    padding-inline: calc(var(--u) * 24);
    max-width: calc(var(--u) * 294);
  }

  /* --- Pricing (474:373) — 342×568 card, Audit credit note dropped -------- */
  .pcard__credit { display: none; }
  .pcard-col .pcard { border-radius: calc(var(--u) * 2); }  /* re-round the top */

  .pcard { height: calc(var(--u) * 568); }
  .pcard__head { height: calc(var(--u) * 108); padding-top: calc(var(--u) * 32); }
  .pcard__tag { font-size: calc(var(--u) * 14); }
  .pcard__row { height: calc(var(--u) * 62); font-size: calc(var(--u) * 14); }
  .pcard__row--uses { height: calc(var(--u) * 64); }
  .pcard__price { height: calc(var(--u) * 102); padding-top: calc(var(--u) * 24); }
  .pcard__amount { font-size: calc(var(--u) * 32); letter-spacing: calc(var(--u) * -0.32); }
  .pcard__timeline { margin-top: calc(var(--u) * 8); font-size: calc(var(--u) * 14); }
  .pcard__desc { font-size: calc(var(--u) * 14); max-width: calc(var(--u) * 294); }
  .pcard__cta {
    height: calc(var(--u) * 72);
    margin: auto calc(var(--u) * 24) calc(var(--u) * 24);
    font-size: calc(var(--u) * 14);
  }
  .slide--pricing .carousel-dots { margin-top: calc(var(--u) * 24); }

  /* --- Process (473:192) — one step per page; the hairline runs the row ----
     A single rule spans all four step-columns, so it enters from the left
     margin on page 1 (x=24), bleeds both edges through 02/03, and ends at the
     right margin on page 4 (x=366) — exactly the Figma page framing. */
  .slide--process {
    /* title bottom 142 → numeral 256 (114) · block bottom 575 → dots 796 (221) */
    grid-template-rows: auto 114fr auto 221fr auto;
  }
  /* All four snap on mobile (one per page) — match the tablet nth-child
     specificity so this wins over the "02/04 don't snap" rule above. */
  .slide--process .process__num:nth-child(2),
  .slide--process .process__num:nth-child(4) { scroll-snap-align: start; }

  .process__nums,
  .process__steps {
    grid-template-columns: repeat(4, calc(var(--uw) * 342));
    gap: calc(var(--uw) * 24);
  }
  .process__rule { width: calc(var(--uw) * 1440); } /* 4×342 + 3×24: col0-left → col3-right */
  .process__steps { margin-top: calc(var(--u) * 52); }
  .process__items { margin-top: calc(var(--u) * 16); }

  /* Samples reel scales by object-fit (see §11.6) — no mobile override needed. */

  /* --- Slides menu (467:2) — full-screen panel, 16 nav type --------------- */
  .drawer__title,
  .drawer__close { font-size: calc(var(--u) * 16); }
}

/* ----------------------------------------------------------------------------
   15. TOUCH DEVICES — one long scroll.

   No snap and no sticky ladder: slides leave the pin and stack in normal
   flow, so the page reads as a single continuous scroll. Each section
   closes with the same hairline the navbar draws while that section is
   active (light sections --color-line; the dark slides their nav-stroke
   shades). script.js's scroll listener drives the indicator / nav theme /
   entry animations from flow offsets instead of snap indices.
   ---------------------------------------------------------------------------- */
@media (hover: none) and (pointer: coarse) {
  .slide {
    position: relative;   /* unpin the sticky ladder — pure flow */
    height: auto;
    min-height: 100svh;   /* each section still fills at least one screen;
                             the pinned-zone 1fr rows resolve against it */
    /* section separator — the nav's bottom stroke for this section */
    border-bottom: 1px solid var(--color-line);
  }
  .slide[data-theme='dark'] { border-bottom-color: #3c3c3c; }           /* Contact */
  .slide--samples[data-theme='dark'] { border-bottom-color: #333333; }  /* Samples, one notch lighter */
  .deck .slide:last-child { border-bottom: 0; }  /* page end — no trailing rule */

  /* Samples reel: the base height:100% depended on the slide's definite
     100dvh height — gone with the flow layout. Flex-stretch the reel to
     fill the whole section under the nav and cover-fit it (crop the 16:9
     sides), the treatment the tablet/mobile reel was authored for — a
     contain-fit 16:9 band reads as a sliver on a tall phone screen. */
  .slide--samples {
    display: flex;
    flex-direction: column;
    /* base pads the section top by nav-h — in the snap layout that strip
       always hid behind the fixed nav, but mid-scroll here it shows as a
       bare-background gap above the reel. The video hugs the section's
       top edge instead (it just slides under the opaque nav at rest). */
    padding-top: 0;
  }
  .samples__video {
    flex: 1;
    min-height: 0;
    height: auto;
    object-fit: cover;
  }

  /* full-height drawer reaches the real window bottom */
  .drawer {
    height: 100dvh;
  }

  /* Carousels keep horizontal panning, but a vertical swipe that starts on
     one must still scroll the page — pan-x alone (set for the desktop
     JS-snap path) would pin it. */
  [data-carousel]:not(.approach__rows),
  .approach__rows[data-carousel] {
    touch-action: pan-x pan-y;
  }
}
