/* ==========================================================
   Studio Name — Architectural Bureau
   Reference viewport: 1512px. Display type is fluid (vw).
   ========================================================== */

@font-face{
  font-family:"Nimbus Sans L";
  src:url("../fonts/nimbus-sans-l-regular.otf") format("opentype");
  font-weight:400; font-style:normal; font-display:swap;
}
@font-face{
  font-family:"Nimbus Sans L";
  src:url("../fonts/nimbus-sans-l-regular-italic.otf") format("opentype");
  font-weight:400; font-style:italic; font-display:swap;
}
@font-face{
  font-family:"Nimbus Sans L";
  src:url("../fonts/nimbus-sans-l-bold.otf") format("opentype");
  font-weight:700; font-style:normal; font-display:swap;
}
@font-face{
  font-family:"Nimbus Sans L";
  src:url("../fonts/nimbus-sans-l-bold-italic.otf") format("opentype");
  font-weight:700; font-style:italic; font-display:swap;
}

*,*::before,*::after{ box-sizing:border-box }

/* Visually hidden, still read aloud. */
.u-hidden{
  position:absolute; width:1px; height:1px; overflow:hidden;
  clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap;
}

:root{
  --pad: 80px;

  --paper:#ffffff;
  --ink:#0d0d0d;
  /* All three uses of --muted are caption text at --micro (11-13px), where
     #8c8c8c measured 3.36:1 on white — under AA. #767676 is 4.54:1 and still
     reads as a grey, not a second black. */
  --muted:#767676;

  --void:#000000;
  --ink-i:#ffffff;
  --muted-i:#6e6e6e;

  --ph:#8f8f8f;
  --ph-dark:#333333;

  --sans:"Nimbus Sans L","Helvetica Neue",Helvetica,Arial,sans-serif;
  /* Source Serif 4 is the current release of Source Serif Pro — same design,
     plus a variable optical-size axis, which matters here because the serif
     runs from 19px (ledes) up to 242px (display). Pro is named next in the
     stack so a locally installed copy still resolves. */
  /* Optical centring shim for pill buttons.

     Every pill sets line-height:1, so its line box IS the em box: the
     baseline sits at the font's ascent (12px at 16.56px here) and the
     descent (4px) hangs below it as dead space. Centre that box
     geometrically and the cap-height block — which is what the eye reads
     as "the text" — lands 2.32px ABOVE the middle, so the pill looks
     bottom-heavy. Measured on Nimbus Sans L: 2.29px at a 16.56px label,
     which is .138em. Applied as padding-top + shim / padding-bottom - shim,
     so the total height is unchanged and it scales with the label. */
  --pill-shim:.14em;
  /* Pill hover. A filled button changes its FILL on hover rather than
     emptying out to an outline — the old inversion made the button look
     like it was disappearing under the pointer, and on the dark sections a
     white pill going transparent left only a hairline. One step of grey in
     whichever direction the button already is: light pills go down, dark
     pills come up. The dark value is the tone the folder drawer already
     lights to, so the site has one hover grey rather than two. */
  --pill-hover-light:#e2e2e2;
  --pill-hover-dark:#363636;
  --serif:"Source Serif 4","Source Serif Pro","Times New Roman",Georgia,serif;

  --d-sans: clamp(40px, 9.1vw, 182px);
  --d-serif: clamp(42px, 12.1vw, 242px);
  --hero: 64px;
  --micro: max(11px, 0.76vw);
  --nav: max(11px, 0.79vw);

  --ease:cubic-bezier(.22,1,.36,1);
}

@media (max-width:820px){ :root{ --pad:18px; --d-sans:clamp(34px,10vw,64px); --d-serif:clamp(34px,9.6vw,64px) } }

html{
  min-height:100%; background:var(--void);
  color-scheme:dark;
  -webkit-text-size-adjust:100%;
  overscroll-behavior-y:none;
}
html.page-light{ background:var(--paper); color-scheme:light }

body{
  margin:0; background:var(--paper); color:var(--ink);
  min-height:100%; min-height:100dvh;
  font-family:var(--sans); font-weight:400;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
  overscroll-behavior-y:none;
}
/* Safari derives its toolbar/overscroll canvas from the root page paint, not
   from whichever full-bleed section happens to be under it. Keep that canvas
   dark on every dark-starting page; all light sections already paint their
   own backgrounds. */
body:not(.is-contact){ background:var(--void) }
h1,h2,h3,p,figure,blockquote{ margin:0 }
a{ color:inherit; text-decoration:none }
::selection{ background:#0d0d0d; color:#fff }

.serif{ font-family:var(--serif); font-weight:400 }

/* ── display type ───────────────────────────────────────── */
.d-sans{
  font-family:var(--sans); font-weight:400;
  font-size:var(--d-sans); line-height:.98;
  letter-spacing:-.035em;
}
.d-serif{
  font-family:var(--serif); font-weight:400;
  font-size:var(--d-serif); line-height:.99;
  letter-spacing:-.012em;
}

/* masked line/word reveal primitives.
   The overflow box needs room on BOTH edges — line-height is below 1 on the
   display faces (.98/.99), so cap-height (S, T...) clips at the top and
   descenders (g, y, Q...) clip at the bottom without this. Padding opens the
   box on each side and the matching negative margin keeps the flowed
   height/position unchanged. */
.ln,.msk{
  display:block; overflow:hidden;
  padding:.16em 0 .18em; margin:-.16em 0 -.18em;
}
.msk{ display:inline-block; vertical-align:top }
/* No will-change here on purpose. These reveals run ONCE, and there is one
   of these spans per word — 72 of them on the home page. Declaring
   will-change in the stylesheet promotes every one to its own compositor
   layer for the life of the page, which pushes Chrome past its layer budget;
   it then starts evicting and re-rasterising layers, and individual words
   flash blank while that happens. main.js promotes them for the length of
   the tween and hands them straight back. */
.ln__i,.msk__i{ display:inline-block }

/* ── placeholders ───────────────────────────────────────── */
.ph{ position:relative; display:block; overflow:hidden; background:var(--ph) }
.ph__fill{
  position:absolute; inset:-8% 0; display:block;
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(255,255,255,.13), rgba(255,255,255,0) 60%),
    repeating-linear-gradient(135deg, rgba(0,0,0,.016) 0 34px, rgba(0,0,0,0) 34px 68px),
    var(--ph);
  will-change:transform;
}
.ph::after{
  content:attr(data-ph);
  position:absolute; left:50%; top:50%; transform:translate(-50%,-50%);
  font-size:var(--micro); letter-spacing:.16em; text-transform:uppercase;
  color:rgba(255,255,255,.5); z-index:2; pointer-events:none;
}
.ph__fill--img{
  background:var(--ph) url("../img/team-photo.jpg") center/cover no-repeat;
}
.ph--shade::before{
  content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
  background:linear-gradient(to top, rgba(0,0,0,.42), rgba(0,0,0,0) 42%),
             linear-gradient(to bottom, rgba(0,0,0,.28), rgba(0,0,0,0) 26%);
}


/* ── header ───────────────────────────────────────────────
   Ported whole from ~/Downloads/moretasks-header. Two differences from the
   kit, both deliberate:

     · the kit sets --sans to Inter in its own :root. That token is not
       copied — the header uses the site's --sans (Nimbus Sans L) so the bar
       is set in the same face as everything under it, rather than the site
       carrying a third typeface for one component.
     · --pad, --nav and --ease are the site's already.

   Everything else, including the dropdown, the veil and the phone menu, is
   the kit's code verbatim. */
.hdr{
  position:fixed; inset:0 0 auto 0; z-index:40;
  display:flex; align-items:center; justify-content:space-between;
  /* 18px above and below a 36px wordmark: the 72px bar in the header design.
     The page gutter stays the horizontal padding. */
  padding:18px var(--pad);
  /* Always white + mix-blend-mode:difference (kononenkogroup.com's header
     trick): the browser inverts per-pixel against whatever is directly
     behind it — difference(white, black bg) = white, difference(white,
     white bg) = black — so the logo/nav stay legible over any background
     (photo, video, gradient, light or dark section) with no JS section
     tracking needed. Replaces the old per-section .is-light toggle, which
     only ever knew a section's nominal light/dark flag, not what was
     actually painted behind the header at that scroll position. */
  color:#fff; mix-blend-mode:difference;
  transition:opacity .45s var(--ease);
}
/* Always visible now — the header no longer hides near the footer or while
   scrolling down. JS still toggles .is-gone/.is-away at the same triggers as
   before; these are kept as harmless no-ops rather than ripping out that
   logic, so re-enabling the old behaviour later is a one-line revert. */
.hdr.is-gone,
.hdr.is-away{ opacity:1; pointer-events:auto }
/* Set by main.js §4a for the first frame on a page that has no hero, where
   the bar is white from the start. Without it the bar animates transparent
   -> white and white type -> dark on load, which on a light page reads as a
   flash of an invisible header. There is no state being left, so there is
   nothing to animate from. Dropped on the next frame, so every later change
   (scrolling, the Services panel) still transitions normally. */
.hdr.is-instant,
.hdr.is-instant *{ transition:none !important }
/* The scrim is a SIBLING of the bar, not a child, so the rule above does not
   reach it. Without this it fades from full to nothing over its own .3s on
   load and the first frames of a legal page carry a grey wash under the
   header. */
body:has(.hdr.is-instant) .hdr-scrim{ transition:none !important }

/* ── the header over the hero ─────────────────────────────────
   The bar used to be drawn with mix-blend-mode:difference, which inverts it
   per-pixel against whatever is behind it. That is excellent at the ends of
   the range — difference(white, black) is white, difference(white, white) is
   black — and useless in the middle: difference(white, mid-grey) is mid-grey
   on mid-grey. The homepage hero is video, and it drifts through that middle
   band constantly, which is what made the nav disappear into the picture.

   Darkening the backdrop does not rescue the blend, it breaks it further: it
   moves a bright frame *toward* the dead middle instead of away from it. So
   over the hero the blend goes, and the bar is painted outright — white type
   on a scrim dark enough to carry it. Contrast then depends on the scrim,
   which we control, rather than on the frame, which we do not.

   This only concerns the hero. main.js puts .is-white on the bar the moment
   the hero's bottom passes it, and from there down the bar is a solid white
   one with dark type; those rules are further down and are untouched. */
.hdr:not(.is-white):not(.is-menu-open){
  mix-blend-mode:normal;
  color:#fff;
  /* A short shadow under the type, for the frames that come up brightest.
     The scrim does the work; this keeps the edges from fraying on a blown-out
     highlight without putting a visible box behind anything. */
  text-shadow:0 1px 2px rgba(0,0,0,.55), 0 0 14px rgba(0,0,0,.30);
}
.hdr:not(.is-white):not(.is-menu-open) .mark__svg{
  filter:drop-shadow(0 1px 2px rgba(0,0,0,.5));
}

/* The scrim itself: a gradient hung off the top of the viewport, densest
   behind the type and gone by its own bottom edge, so the hero still reads as
   full-bleed. It is a sibling of .hdr, not a child — the bar is a positioned,
   z-indexed layer and a child would sit above the type it is meant to sit
   under. */
.hdr-scrim{
  position:fixed; inset:0 0 auto 0; z-index:38;
  height:clamp(120px,16vh,200px); pointer-events:none;
  background:linear-gradient(to bottom,
    rgba(0,0,0,.72) 0%, rgba(0,0,0,.56) 28%, rgba(0,0,0,.30) 58%,
    rgba(0,0,0,.10) 82%, transparent 100%);
  transition:opacity .3s var(--ease);
}
/* Once the bar has a background of its own — scrolled past the hero, the phone
   menu, the services panel — the scrim is just a smudge over the page. */
body:has(.hdr.is-white) .hdr-scrim,
body:has(.hdr.is-menu-open) .hdr-scrim,
body:has(.hdr.is-dd-open) .hdr-scrim,
body:has(.hdr .nav__item--menu:focus-within) .hdr-scrim,
body:has(.hdr.is-gone) .hdr-scrim,
body:has(.hdr.is-away) .hdr-scrim{ opacity:0 }
.mark{
  display:block; line-height:0; color:inherit;
}
/* 36px beside a 16px nav, as the header design has it. The phone header sets
   its own 20px. */
.mark__svg{
  display:block; width:auto; height:clamp(24px,2.5vw,36px);
  color:currentColor;
}
/* 24px line on 16px type, and the design's spacing between items: 12px of
   padding either side of each plus a 2px gap, so 26px between labels. */
.nav{ font-size:var(--nav); line-height:1.5; display:flex; gap:clamp(16px,1.8vw,26px); align-items:baseline }
.nav__a{ position:relative }
/* ONE rule under the label serves both states. It sits under the current
   page, it appears under whatever is hovered, and hovering the current page
   changes nothing — there is no second rule to add. The header design draws it
   as a 1px border on the text's own line box, so it sits at the bottom of the
   24px line, 4px under the letters.

   The rule hangs off the label span, not the link, so it runs under the word
   and stops short of the Services chevron. The span is a plain inline-block —
   an earlier bold-twin grid made it 28px tall with the glyphs sitting low in
   it, which is what had the chevron centring 6.5px above the text. */
.nav__t{ position:relative; display:inline-block }
.nav__t::after{
  content:""; position:absolute; left:0; right:0;
  bottom:0; height:1px;
  background:currentColor;
  transform:scaleX(0); transform-origin:left;
  transition:transform .3s var(--ease);
}
/* The current page carries it at every width — on the phone it is the only
   thing marking the current row. Hover only means something with a pointer. */
.nav__a.is-on .nav__t::after{ transform:scaleX(1) }
@media (min-width:821px){
  .nav__a:hover .nav__t::after,
  .nav__a:focus-visible .nav__t::after{ transform:scaleX(1) }
}
/* The way to the services page from inside the phone's fold-out, where the
   Services row itself has become the control that opens it. Desktop keeps the
   row as the link and has no use for this. */
.menu__all{ display:none }


/* ── the toggle only exists under the breakpoint ─────────── */
.menu-toggle{ display:none }

:where(a,button,input):focus-visible{
  outline:2px solid currentColor;
  outline-offset:4px;
}

/* ═══════════════════ 26 · services dropdown ═══════════════════
   Hover (or focus) Services and the seven services come down grouped under
   small eyebrow labels, after the reference: white panel hung from the header,
   three columns, a micro uppercase label over each (.perks__k's numbers),
   items at ordinary link type, 24px inside.

   Three things happen around it, all apple.com's:

     the header   drops its difference blend and turns solid white with dark
                  type. It is drawn with mix-blend-mode:difference so its type
                  inverts against whatever is behind it, and a child panel
                  would be inverted with it — white becoming black over the
                  hero — so the blend has to go for as long as the panel is
                  open. The mobile menu already does the same.
     the page     blurs and dims behind it (body::before, a fixed veil one
                  layer under the header), so the header and panel are the only
                  sharp things on screen.
     the rest     greys out. Whatever the pointer is on stays black; every other
                  item in the panel, and the other links in the nav, step back.

   All of it desktop-only: under the breakpoint the full-screen menu takes over
   and Services is a plain row. */
.nav__item{ display:block }
.nav__item--menu > .nav__a{ display:inline-flex; align-items:center; gap:.45em }
.nav__chev{
  display:block; flex-shrink:0; width:.85em; height:.85em;
  transition:transform .3s var(--ease);
}

@media (min-width:821px){
  .hdr.is-dd-open .nav__chev,
  .nav__item--menu:focus-within > .nav__a .nav__chev{ transform:rotate(180deg) }

  /* Hung from the header's bottom edge, right edge on the nav's. The ::before
     is a transparent strip reaching back up over the header's bottom padding,
     so the pointer never leaves the item on its way down into the panel. */
  /* The header design's box: 40px inside, 80px between columns, each column
     as wide as its longest service, right edge on the nav's — which puts the
     left edge near the design's 196px at 1440. max-content so the columns
     size to their words; the max-width keeps it inside the gutters on a
     narrower desktop, where the grid gives up a column's width instead. */
  .menu{
    position:absolute; top:100%; right:var(--pad); z-index:1;
    width:max-content; max-width:calc(100vw - 2 * var(--pad));
    display:grid; grid-template-columns:auto auto auto;
    gap:clamp(40px,5.5vw,80px);
    padding:clamp(24px,2.8vw,40px);
    background:var(--paper); color:var(--ink);
    box-shadow:0 18px 40px -18px rgba(0,0,0,.28);
    opacity:0; visibility:hidden; transform:translateY(-6px);
    transition:opacity .22s var(--ease), transform .22s var(--ease), visibility 0s linear .22s;
  }
  .menu::before{ content:""; position:absolute; left:0; right:0; bottom:100%; height:20px }
  .hdr.is-dd-open .menu,
  .nav__item--menu:focus-within .menu{
    opacity:1; visibility:visible; transform:none;
    transition:opacity .22s var(--ease), transform .22s var(--ease), visibility 0s;
  }
  /* Two tiers as the header design has them: the category is a small grey
     label — 16px, #535353 — and the services under it are the large black
     type, 20px on a 24px line, 16px apart. */
  .menu__col{ display:flex; flex-direction:column; align-items:flex-start; gap:clamp(12px,1.1vw,16px) }
  .menu__all{ display:none }
  /* Set in caps, after the help-centre reference: a label, and smaller than
     the services it heads, with the tracking caps need. */
  .menu__k{
    display:block; margin:0;
    font-family:var(--sans); font-weight:400; color:#535353;
    font-size:clamp(12px,.95vw,13px); line-height:1.5;
    letter-spacing:.09em; text-transform:uppercase;
  }
  .menu__a{
    display:block; padding:0;
    font-family:var(--sans); font-weight:400; color:var(--ink);
    font-size:clamp(17px,1.4vw,20px); line-height:1.2; text-decoration:none;
    transition:opacity .25s var(--ease);
  }

  /* Focus on the hovered service: the others step back. The design keeps the
     nav's other links at full strength while the panel is open, so they do. */
  .menu:has(.menu__a:hover) .menu__a:not(:hover){ opacity:.5 }

  /* The header while the panel is open, as the design draws it: a white bar
     with a hairline under it (an inset shadow, so the bar stays 72px), nav
     type in #414651 with the rule in black, and the wordmark in its own
     colours — img/logo-color.svg, the design's exported asset — in place of
     the single-colour mark that the blended header needs the rest of the time. */
  /* is-white is painted by main.js §4a: on while the Services panel is open
     and while the page is scrolled past the hero. */
  .hdr.is-white,
  .hdr:has(.nav__item--menu:focus-within){
    mix-blend-mode:normal; background:var(--paper); color:#414651;
    box-shadow:inset 0 -1px 0 #e9eaeb;
    transition:opacity .45s var(--ease), background-color .3s var(--ease),
               color .3s var(--ease), box-shadow .3s var(--ease);
  }
  .hdr.is-white .nav__t::after,
  .hdr:has(.nav__item--menu:focus-within) .nav__t::after{ background:#000 }
  .hdr.is-white .mark__svg,
  .hdr:has(.nav__item--menu:focus-within) .mark__svg{ display:none }
  .hdr.is-white .mark::after,
  .hdr:has(.nav__item--menu:focus-within) .mark::after{
    content:""; display:block;
    height:clamp(24px,2.5vw,36px); aspect-ratio:2298 / 486;
    background:url("../img/logos/logo-color.svg") center / contain no-repeat;
  }

  /* The veil: one layer under the header (39 against its 40), blurring and
     dimming the page. No pointer events, so moving off the header onto the
     page ends the hover and the whole thing folds away. */
  body::before{
    content:""; position:fixed; inset:0; z-index:39; pointer-events:none;
    background:rgba(0,0,0,.22);
    -webkit-backdrop-filter:blur(22px) saturate(1.15);
    backdrop-filter:blur(22px) saturate(1.15);
    opacity:0; transition:opacity .28s var(--ease);
  }
  body:has(.hdr.is-dd-open)::before,
  body:has(.hdr .nav__item--menu:focus-within)::before{ opacity:1 }
}

@media (max-width:820px){
  /* Tapping the Services row folds the grouped services out underneath it,
     and the chevron — sitting with the word rather than off at the margin —
     turns over to say so. The row is still marked up as a link to the
     services page, which is what it does with no JS; with JS the fold-out
     carries .menu__all to the page instead. main.js sets the open height,
     since max-height can't animate to auto. */
  .has-mobile-menu .nav__item--menu > .nav__a{ display:flex; cursor:pointer }
  .has-mobile-menu .nav__a .nav__chev{
    width:13px; height:13px; margin-left:.12em;
    transition:transform .3s var(--ease);
  }
  .has-mobile-menu .nav__item--menu.is-open .nav__a .nav__chev{ transform:rotate(180deg) }

  .menu{ display:none }
  .has-mobile-menu .menu{
    grid-column:1 / -1;
    display:grid; grid-template-columns:1fr; gap:22px;
    padding:0; margin:0;
    max-height:0; overflow:hidden;
    transition:max-height .34s var(--ease);
  }
  /* Its breathing room lives on the first and last child rather than the
     panel, so a collapsed panel is genuinely 0px. */
  .has-mobile-menu .menu__col{ display:flex; flex-direction:column; align-items:flex-start; gap:0 }
  .has-mobile-menu .menu__col:first-child{ padding-top:10px }
  /* Under the categories, and weighted a little over them: the one thing in
     the fold-out that leaves the menu. A hairline beneath closes the
     fold-out off from the nav rows that carry on below it — the one rule in
     the menu, marking where the sub-list ends rather than parting items that
     spacing already separates. Clipped along with everything else while the
     panel is folded. */
  .has-mobile-menu .menu__all{
    display:flex; align-items:center; gap:.5em;
    padding:2px 0 26px;
    font-family:var(--sans); font-weight:500; font-size:17px; line-height:1.35;
    color:var(--ink);
  }
  /* Ruled underneath, the way the nav rules its current page: on the label's
     own line box, under the words and stopping short of the arrow. */
  .has-mobile-menu .menu__t{ position:relative; display:inline-block }
  .has-mobile-menu .menu__t::after{
    content:""; position:absolute; left:0; right:0; bottom:0;
    height:1px; background:currentColor;
  }
  .has-mobile-menu .menu__arrow{ display:block; width:13px; height:13px; flex-shrink:0 }
  .has-mobile-menu .menu__k{
    display:block; margin:0 0 6px;
    font-family:var(--sans); font-weight:400; font-size:12px; line-height:1;
    letter-spacing:.09em; text-transform:uppercase; color:#8a8a8a;
  }
  .has-mobile-menu .menu__a{
    display:block; padding:7px 0;
    font-family:var(--sans); font-weight:400; font-size:17px; line-height:1.35;
    color:#3d3d3d;
  }
}


/* ══════════════════ phone header ══════════════════ */
@media (max-width:820px){
  :root{
    --pad:20px;
    --micro:12px;
    --nav:16px;
  }

  body.menu-open{ overflow:hidden }

  .hdr{
    min-height:64px;
    align-items:center;
    padding:max(10px,env(safe-area-inset-top))
            max(var(--pad),env(safe-area-inset-right)) 10px
            max(var(--pad),env(safe-area-inset-left));
    color:#fff;
    background:transparent;
    mix-blend-mode:difference;
  }
  /* Scrolled past the hero, or with the menu open, the phone header is the
     same white bar with the colour mark. The hairline only when scrolled —
     with the white menu directly beneath it there is nothing to divide. */
  .hdr.is-white,
  .hdr.is-menu-open{
    mix-blend-mode:normal; background:var(--paper); color:#414651;
  }
  .hdr.is-white:not(.is-menu-open){ box-shadow:inset 0 -1px 0 #e9eaeb }
  .hdr.is-white .mark__svg,
  .hdr.is-menu-open .mark__svg{ display:none }
  .hdr.is-white .mark::after,
  .hdr.is-menu-open .mark::after{
    content:""; display:block; height:20px; aspect-ratio:2298 / 486;
    background:url("../img/logos/logo-color.svg") center / contain no-repeat;
  }
  /* Black, not the #414651 the nav type uses: the mark is three hairlines,
     and at 1px the slate grey reads as a lighter, thinner rule than the type
     beside it rather than as the same colour. */
  .hdr.is-white .menu-toggle,
  .hdr.is-menu-open .menu-toggle{ color:#000 }
  /* Visible at all times, matching the desktop rule above: the phone header
     used to fade out while scrolling down and near the footer, which left
     the pinned sections (the amenities filmstrip especially) with no way
     back to the menu. Kept as a no-op override rather than deleted, so the
     JS that still sets these classes stays harmless. */
  .hdr.is-away:not(.is-menu-open),
  .hdr.is-gone:not(.is-menu-open){ opacity:1; pointer-events:auto }
  .hdr .mark{ position:relative; z-index:2 }
  .mark__svg{ height:20px; width:auto }

  /* Three bars. The gap is 5px rather than the 6px the two-bar mark used:
     three 1px rules at 6px apart stand 15px tall against a 22px width, which
     reads as a taller box than it is wide; at 5px the mark is 22x13 and keeps
     the proportion the header was drawn with. */
  .has-mobile-menu .menu-toggle{
    position:relative; z-index:3;
    display:grid; place-content:center; gap:5px;
    width:44px; height:44px; margin:-4px -8px -4px 0;
    padding:0; border:0; border-radius:50%;
    background:transparent; color:#fff; cursor:pointer;
  }
  .menu-toggle span{
    display:block; width:22px; height:1px; background:currentColor;
    transform-origin:center; transition:transform .3s var(--ease),opacity .2s ease;
  }
  /* Closing to an X: the outer bars sit 6px off centre (1px rule + 5px gap),
     so that is the distance each travels in. The middle bar has nowhere to go
     and fades instead. */
  .hdr.is-menu-open .menu-toggle span:first-child{ transform:translateY(6px) rotate(45deg) }
  .hdr.is-menu-open .menu-toggle span:nth-child(2){ opacity:0 }
  .hdr.is-menu-open .menu-toggle span:last-child{ transform:translateY(-6px) rotate(-45deg) }

  .hdr.has-mobile-menu .nav{
    position:fixed; inset:0; z-index:1;
    display:flex; flex-direction:column; align-items:stretch; justify-content:flex-start;
    overflow-y:auto; -webkit-overflow-scrolling:touch;
    gap:0; padding:calc(84px + env(safe-area-inset-top)) var(--pad) max(30px,env(safe-area-inset-bottom));
    background:var(--paper); color:var(--ink);
    opacity:0; visibility:hidden; pointer-events:none;
    transform:translateY(-12px);
    transition:opacity .3s var(--ease),transform .3s var(--ease),visibility 0s linear .3s;
  }
  .hdr.has-mobile-menu.is-menu-open .nav{
    opacity:1; visibility:visible; pointer-events:auto; transform:none;
    transition:opacity .3s var(--ease),transform .3s var(--ease),visibility 0s;
  }
  /* Rows in the site's sans, held apart by their own space — no rules
     between them, no dot. The current page keeps the 1px line under its
     label, the same one the desktop nav draws. */
  .has-mobile-menu .nav__a{
    display:flex; align-items:center;
    padding:11px 0;
    font-family:var(--sans); font-weight:400;
    font-size:clamp(22px,6vw,26px); line-height:1.3; letter-spacing:-.01em;
  }
  .has-mobile-menu .nav__a.is-on{ text-decoration:none }

}


/* ══════════════════════════ 1 · hero ══════════════════════
   Scroll pulls back from one frame to the whole wall, then the wall washes
   out to white and hands off to the statement below. The section is tall and
   the pin is what you actually see. */
/* 560, not 320: the section now runs two acts — pull back to the wall and wash
   white, then return, travel one tile diagonally and land on it. 320/560 is
   HERO_SPLIT in main.js, which keeps the opening act's on-screen scroll
   distance exactly what it was before the return was added. */
.hero{ position:relative; height:150svh }
.hero__pin{
  position:sticky; top:0; height:100svh; overflow:hidden;
  background:var(--paper);
}
.hero__grid{
  position:absolute; top:50%; left:50%;
  transform:translate(-50%,-50%);
  transform-origin:50% 50%;
  display:grid;
  /* the gutters ARE this background — white, not the pin showing through */
  background:var(--paper);
  will-change:transform;
  /* columns/rows and the gap are written by main.js so the cell aspect stays
     put across viewports */
}
.hero__cell{
  position:relative; overflow:hidden;
  background:var(--ph);              /* grey placeholder until artwork lands */
  /* No will-change: the cells' opacity was animated back when the wall
     washed out to white, and that move is long gone — the wall now stays on
     screen the whole time and only .hero__grid transforms. The hint was
     still promoting all 50 cells to their own layers for nothing. */
}
.hero__cell video,
.hero__cell img{
  position:absolute; left:0; top:-11%;
  width:100%; height:122%; object-fit:cover; display:block;
}
/* The source film is letterboxed: measured 28px of pure black top and bottom on
   a 360px frame, 7.8% a side, identical in every clip. object-fit:cover on an
   exactly-fitting box still shows them — the element fills, the PICTURE does
   not — so the video has to overscan past its box to crop them. 122%/-11%
   clears it with room to spare. */

.hero__title{
  position:absolute; left:var(--pad); bottom:calc(var(--pad) + .4vw); z-index:7;
  color:#fff; font-size:clamp(28px,7vw,64px); line-height:1.03; letter-spacing:-.022em;
  will-change:opacity;
}
.hero__title .ln__i.serif{ font-size:clamp(20px,5vw,48px) }

/* reduced motion: no pin, no zoom — just the first frame */
.no-motion .hero{ height:100svh }
.no-motion .hero__pin{ position:relative }

/* ══════════════════════ 2 · statement ═════════════════════ */
.stmt__head{ padding-inline:var(--pad) }
.stmt__notes{
  display:grid;
  grid-template-columns:26% 18% 8.5% 18% 1fr;
  margin-top:clamp(22px,2.4vw,44px);
  margin-bottom:clamp(60px,8vw,150px);
}

.stmt__notes p:nth-of-type(1){ grid-column:2 }
.stmt__notes p:nth-of-type(2){ grid-column:4 }

.stmt__notes p{
  font-size:var(--micro); line-height:1.42; color:var(--muted);
}


/* ══════════════════════ 2b · about ═══════════════════════ */
/* ═════════════════════ 2b · statement ═════════════════════
   Opens the page below the hero, running the full measure flush to both
   margins — same width the figures below occupy. */
.statement{
  background:var(--paper);
  /* Flat 80px top and bottom (40px on mobile, below), matching .connect.
     Was clamp(80px,9vw,170px)/clamp(70px,8vw,150px) — asymmetric and opening
     out to 170px on a wide screen. Horizontal stays --pad so the block keeps
     the gutter every other section sits on. */
  padding:80px var(--pad);
}
/* The same pill the closing CTA and the contact banner use — its own class
   rather than borrowing .scta__cta, because those two blocks are unrelated
   to this one and a shared selector would tie their future to each other.
   Left-aligned under the paragraph, not centred: the statement sets a left
   edge and the button belongs on it. */
/* Symmetric padding on every pill. These carried padding:18px/14px, an
   optical nudge meant to lift type that sits high in a line-height:1 line
   box — but they are inline-flex with align-items:center, which already
   centres the line box, so the 4px only ever pushed the label below the
   middle of the pill. Measured 2px low before this. */
.statement__cta{
  display:inline-flex; align-items:center; justify-content:center;
  margin-top:clamp(32px,3.8vw,56px);
  padding:calc(16px + var(--pill-shim)) 32px calc(16px - var(--pill-shim)); border-radius:999px;
  /* Filled rather than outlined, unlike the closing CTA and the banner —
     this one sits in the middle of a page of running text and has to read
     as the one thing to press. The hover is the outline it used to be. */
  background:var(--ink); border:1px solid var(--ink); color:var(--ink-i);
  font-family:var(--sans); font-weight:500; font-size:clamp(15px,1.15vw,18px);
  line-height:1; text-decoration:none;
  transition:background .35s var(--ease), color .35s var(--ease), border-color .35s var(--ease);
}
.statement__cta:hover,
.statement__cta:focus-visible{ background:var(--pill-hover-dark); border-color:var(--pill-hover-dark); color:var(--ink-i) }
.statement__t{
  font-family:var(--serif); font-weight:400;
  font-size:clamp(19px,2.1vw,42px); line-height:1.12; letter-spacing:-.006em;
}
/* Gap between multiple statement paragraphs on the pages that run more than
   one (e.g. customer-support-operations.html) — .statement__t is reset to
   margin:0 globally (h1,h2,h3,p,figure,blockquote), so back-to-back
   paragraphs would otherwise sit flush against each other. */
.statement__t + .statement__t{ margin-top:clamp(20px,2.4vw,36px) }

/* ═══════════════════════ 2b2 · stats ══════════════════════
   Figures run the full measure, flush left with the statement above. Four
   across at desktop — with no label rail taking a third of the row, two
   columns would leave each figure stranded in half a viewport. */
.stats{
  background:var(--paper);
  padding:0 var(--pad) clamp(80px,9vw,170px);
}
.stats__grid{
  display:grid; grid-template-columns:repeat(4,1fr);
  column-gap:clamp(20px,2.4vw,48px);
}
/* Four across needs roughly 240px a column before the figures start colliding
   with their own captions; below that, drop to two and let the rows stack. */
@media (max-width:1100px){
  .stats__grid{ grid-template-columns:repeat(2,1fr) }
}
/* Hairline over each cell, matching .office's rules — the figures read as a
   table of record rather than as four floating cards. */
.stat{
  display:block;
  padding:clamp(14px,1.35vw,28px) 0;
  border-top:1px solid #e6e6e6;
}
.stat__n{
  display:block;
  font-family:var(--sans); font-weight:400;
  font-size:clamp(26px,2.9vw,58px); line-height:1.04; letter-spacing:-.035em;
}
.stat__l{
  display:block; margin-top:.55em;
  font-size:max(11px,.85vw); line-height:1.45; color:#9a9a9a;
}

/* ══════════════════════ offices table ═════════════════════
   The index no longer carries an offices section; these rules now serve only
   the list embedded in about.html's philosophy section, which supplies its
   own container, label, and dark treatment via .offices__list--dark. */
.offices__list{
  position:relative; flex:0 0 47.04%; margin-left:4.1%;
  isolation:isolate;
}
.offices__rows{ display:block }
.office{
  display:grid;
  grid-template-columns:28.7% 29.4% 40.8% 1.1%;
  align-items:baseline;
  padding:clamp(9px,.85vw,18px) 0;
  border-top:1px solid #e6e6e6;
  font-size:max(12px,.85vw); line-height:1.3;
  color:var(--ink);
}
.office:last-child{ border-bottom:1px solid #e6e6e6 }
.office__c,.office__a{ color:#9a9a9a }
.office__x{ justify-self:end; font-size:.92em; opacity:.55 }

/* the sliding card: a black plate that carries its own copy of the rows,
   so the type inverts exactly where the plate covers it */
.offices__mask{
  position:absolute; inset:0 0 auto 0; z-index:2;
  overflow:hidden; background:var(--ink);
  opacity:0; pointer-events:none;
  will-change:transform;
}
.offices__mask .office{
  border-top-color:transparent; color:#fff;
}
.offices__mask .office:last-child{ border-bottom-color:transparent }
.offices__mask .office__c,
.offices__mask .office__a{ color:#c9c9c9 }
.offices__mask .office__x{ opacity:1 }
.offices__rows--inv{ will-change:transform }

/* ═══════════════════ 3 · radial / inversion ═══════════════ */
.radial{ height:300vh; position:relative; background:var(--paper) }
.radial__pin{
  position:sticky; top:0; height:100svh; overflow:hidden;
  display:grid; place-items:center;
}
/* A centred SQUARE, not a stretched fill — the reference sizes its fan box to
   ~80% of the viewport height and lets the page breathe around it. The
   numbers ride at 1.06x the tip radius, outside the viewBox, so overflow has
   to stay visible. Rotation happens in the path data now, never as a CSS
   transform, so there is nothing to promote. */
.radial__svg{
  position:absolute; left:50%; top:50%;
  width:min(78svh,84vw); height:min(78svh,84vw);
  transform:translate(-50%,-50%);
  overflow:visible;
}
/* Soft haze behind the centre — the spokes converge and read as a hard knot
   without it. backdrop-filter so it blurs whatever the section currently is,
   which matters because this section inverts white -> black mid-scroll. */
.radial__haze{
  position:absolute; left:50%; top:50%;
  width:min(46vw,620px); aspect-ratio:1;
  transform:translate(-50%,-50%);
  border-radius:50%;
  backdrop-filter:blur(26px);
  -webkit-backdrop-filter:blur(26px);
  -webkit-mask-image:radial-gradient(circle at center, #000 0%, #000 34%, transparent 72%);
  mask-image:radial-gradient(circle at center, #000 0%, #000 34%, transparent 72%);
  pointer-events:none; z-index:1;
}
.radial__type{ z-index:2 }
/* stroke width and group opacity match the reference (0.94 / 0.5 on the
   masked group — the group carries the opacity now, not each path).
   Tick font-size is in SVG USER UNITS, so it scales with the box: 13 units in
   a ~1050px box is ~15px on screen. On a phone the box drops to ~345px and the
   same 13 units render at 4.5px — illegible — so the unit size climbs as the
   box shrinks. */
.radial__svg .spoke{ fill:none; stroke:currentColor; stroke-width:.94 }
.radial__svg .tick{ font-family:var(--sans); font-size:13px; letter-spacing:.06em; fill:currentColor }
@media (max-width:820px){
  .radial__svg{ width:min(64svh,84vw); height:min(64svh,84vw) }
  .radial__svg .spoke{ stroke-width:1.6 }
  .radial__svg .tick{ font-size:27px }
  .radial__haze{ width:min(72vw,620px) }
}
.radial__pin{ color:var(--ink) }
/* The two headlines swap by sliding through this window rather than
   cross-fading. Stacked in one grid cell, a fade left both readable at once
   over the mid-grey of the inversion — a double exposure. Clipping instead
   means only one is ever in view. Padding keeps descenders off the clip edge,
   the negative margin cancels it from layout. */
.radial__type{
  position:relative; z-index:2; display:grid; place-items:center;
  overflow:hidden; padding-block:.16em; margin-block:-.16em;
}
.radial__t{
  grid-area:1/1; text-align:center;
  font-size:clamp(30px,4.2vw,80px); line-height:1.06; letter-spacing:-.028em;
  will-change:transform;
}
/* parked below the window until the swap; travel exceeds 100% so the padded
   band is cleared too */
.radial__t--b{ color:#fff; transform:translateY(112%) }

/* ═══════════════════ 3a · TAP (pinned reveal) ═══════════════════
   Each word is CAP + REST; REST sits clipped to width:0 at rest so the row
   reads as the bare acronym "TAP", then main.js scrubs each REST open to
   its measured natural width as the section scrolls. */
.tap{ background:var(--paper); color:var(--ink); height:250vh; position:relative }
.tap__pin{
  position:sticky; top:0; height:100svh; overflow:hidden;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:clamp(40px,6vw,90px);
}
/* Visible only while the row still reads as the bare acronym — main.js
   fades this out the instant the letters start pulling apart into words. */
.tap__eyebrow{
  display:block; font-family:var(--sans); font-weight:400;
  font-size:clamp(26px,3.2vw,48px); color:var(--muted); text-align:center;
}
/* two nested layers so the one-off entrance zoom (main.js, on .tap__stage)
   and the continuous scroll-scrubbed "shrink to keep fitting" (main.js, on
   .tap__row, as the letters grow into full words) never fight over the
   same transform */
.tap__stage{ display:flex; align-items:center; justify-content:center; width:100% }
.tap__row{
  display:flex; align-items:baseline; justify-content:center;
  padding:0 var(--pad); gap:clamp(22px,5vw,84px);
  transform-origin:50% 50%;
}
.tap__word{ display:inline-flex; align-items:baseline }
.tap__cap,.tap__rest{
  font-family:var(--sans); font-weight:500;
  font-size:clamp(120px,28vw,460px); line-height:1; letter-spacing:-.02em;
  white-space:nowrap;
}
.tap__rest{ display:inline-block; overflow:hidden; width:0; will-change:width }

@media (max-width:820px){
  /* Exactly the desktop behaviour, just smaller: ONE horizontal row that
     scales down as the letters open into words. It used to stack into a
     column here, which broke the whole idea twice over — "TAP" only reads
     as an acronym while the three letters share a line, and a column has no
     line to keep them on, so the scale-to-fit trick the expansion depends
     on had to be switched off with it. */
  /* Desktop's 250vh scroll-through is tuned for three words sharing one
     line at display size; at the smaller mobile type size the same letters
     finish expanding in far less scroll distance, so the full 250vh just
     sat there as dead space once the animation was already done. */
  /* Kept in proportion with .tap__pin below: the pin only fills the top
     part of the viewport now, and the parent's extra height is the pin's
     own scroll-while-stuck travel (main.js anchors the scrub's end to
     .tap's height minus .tap__pin's height, the same sum CSS sticky itself
     releases on) — the taller this is relative to the pin, the longer the
     letters take to fully open. It no longer needs to clear a full
     viewport (main.js used to fall back to the viewport-relative
     'bottom bottom', which is why this once had to stay above 100vh); it
     only needs to clear .tap__pin's own height, so 60vh — ~22vh of real
     pin travel above the 38vh pin — is enough for the three words to
     finish opening without the pin lingering on screen any longer than
     that. (main.js's zoom-in run-in is now tied to .tap__pin's own height
     too, rather than a flat one-viewport lead-in, so shrinking this no
     longer leaves a disproportionately long "nothing happening yet" scroll
     before the pin even sticks.) */
  .tap{ height:60vh }
  /* Sticky doesn't require filling the full viewport — it just needs to
     stick to the top while .tap scrolls past underneath it. The desktop
     100svh reads as a deliberate full-screen "slide"; at the smaller
     mobile type size the same full-screen box just left the words sitting
     in a lot of dead space above and below them. */
  .tap__pin{ height:38vh; gap:clamp(16px,5vw,32px) }
  /* Redundant centering alongside .tap__stage's justify-content:center —
     the row's own width is being rewritten every frame by the letter-width
     scrub, and margin:auto on a flex item re-centers it independently of
     whatever the parent's justify-content last resolved to, so it can't
     drift off-centre as that width changes under it. */
  .tap__row{ margin-inline:auto }
  .tap__eyebrow{
    font-size:clamp(15px,4.6vw,26px);
    padding:0 var(--pad);          /* it ran off the right edge without this */
  }
  .tap__row{ gap:clamp(10px,3.4vw,22px) }
  .tap__cap,.tap__rest{ font-size:clamp(56px,26vw,120px) }
}
.no-motion .tap{ height:auto }
.no-motion .tap__pin{ position:relative; height:auto; padding:22vh 0 }
.no-motion .tap__rest{ width:auto }

/* ═════════════════════════ 3b · values (pinned) ═══════════════════════
   main.js crossfades .value__media/.value__copy pairs and replays the
   incoming headline's mask reveal at each of the two transitions.

   400vh, not 300: the pin eats one viewport, so the scroll-through distance
   is height - 100vh. At 300vh that left only 200vh for three stages, and the
   third one's turn began exactly as the pin released — Productivity was still
   arriving while the section scrolled away, so it never sat centred the way
   the first two do. 400vh gives a full 100vh hold per stage, third included. */
/* 400vh: the pin takes the first 100, leaving 300 of scroll-through — a
   viewport per value. At 220 the whole section was 1.2 viewports of scroll
   and a single decisive flick cleared it, carrying the reader into the next
   section having read one value of three. Length is the only thing that
   actually stops that; the rate cap in main.js §13 keeps a fast gesture from
   jumping stages WITHIN the section, but it cannot hold a reader who has
   already scrolled past the end. */
.value{ position:relative; height:400vh; color:var(--ink-i) }
.value__pin{ position:sticky; top:0; height:100svh; overflow:hidden }
.value__media{ position:absolute; inset:0 }
.value__media img{ width:100%; height:100%; object-fit:cover; display:block }
.value__scrim{
  position:absolute; inset:0; z-index:1;
  background:linear-gradient(180deg, rgba(0,0,0,.15), rgba(0,0,0,.5) 60%, rgba(0,0,0,.65));
}
.value__copy{
  position:absolute; inset:0; z-index:2; text-align:center;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:clamp(20px,2.6vw,35px);
  width:min(84vw,1148px); margin-inline:auto; padding:0 var(--pad);
}
.value__t{
  font-family:var(--sans); font-weight:500; color:#fff;
  font-size:clamp(48px,10.6vw,182px); line-height:1.05; letter-spacing:-.02em;
  margin:0;
}
.value__d{
  font-family:var(--sans); font-weight:400; color:#fff;
  font-size:clamp(17px,1.75vw,30px); line-height:1.27;
  max-width:52ch; margin:0;
}
/* Phone only — the desktop scrollbar already implies progress. */
.value__dots{ display:none }

@media (max-width:820px){
  /* Pinned and full-height, as on desktop. A sticky panel shorter than the
     screen leaves the unused viewport visible as black, so full-height is the
     only size with none. The crop that made a short panel tempting is fixed at
     the source instead — index.html loads portrait crops below 821px. */
  .value{ height:400vh }
  .value__pin{ position:sticky; top:0; height:100svh; overflow:hidden; display:block }
  .value__media{ position:absolute; inset:0; min-height:0 }
  .value__media::after{ content:none }
  /* No per-stage offsets: the portrait crops are already on their focal point. */
  .value__media img{ object-position:center center }
  .value__scrim{ display:block }
  /* On the floor, where the scrim is darkest. */
  .value__copy{
    position:absolute; inset:0;
    justify-content:flex-end;
    width:min(92vw,560px); margin-inline:auto;
    padding:0 var(--pad) 50px; gap:clamp(16px,4.5vw,24px);
  }
  /* Stage indicator, driven from vApply in main.js. Clears the copy's 50px. */
  .value__dots{
    display:flex; gap:7px; position:absolute; z-index:3;
    left:50%; transform:translateX(-50%); bottom:18px;
  }
  .value__dots span{
    width:6px; height:6px; border-radius:50%;
    background:rgba(255,255,255,.38);
    transition:background .3s var(--ease), transform .3s var(--ease);
  }
  .value__dots span.is-on{ background:#fff; transform:scale(1.25) }
  .value__t{
    font-size:clamp(38px,11.5vw,56px);
    line-height:1; white-space:nowrap;
  }
  .value__d{ font-size:clamp(16px,4.25vw,19px); line-height:1.35; max-width:34ch }
}

/* ═══════════ 4-5 · people reveal (placeholder) ═══════════
   Hero-style image reveal, ported from a reveal template as a stand-in for
   the People & Process content — swap the images for the real thing when
   it's ready. Plays once on scroll via a dedicated ScrollTrigger (see
   main.js §10), since the section sits mid-page rather than on page load. */
/* ═══════════════════ 6b · people ═══════════════════
   Four portraits as one wall, and the one you point at takes the room from
   the other three.

   What was here before was a pinned, scrubbed reveal: four cards scattered
   off-screen, converged into a row, and the middle one grew to fill the
   viewport while a caption faded in behind it. It cost a viewport and a half
   of scroll, it had to hide the words until the choreography had finished,
   and on a phone the row came out wider than the screen with each card a
   sliver. It was motion standing in for a design.

   This is a flex row of four panels and nothing else. Flex-grow is the whole
   interaction — the open panel takes ~3 shares against the others' 1, and
   since flex-grow is a number it transitions, so the row redistributes
   itself rather than anything moving. Everything else follows from open:
   the photograph loses its grey, the scrim deepens, the quote comes up.

   Three states, one selector list, in this order:
     · :hover           — the pointer
     · :focus-within    — the keyboard, through tabindex on the panel
     · first child, while the row has neither — so the section is already
       open when it arrives on screen instead of reading as four grey
       rectangles waiting to be touched.

   The row only behaves this way where there is a real pointer to do it
   with: the base layout below is the stacked one, every quote on the page,
   which is what a phone and any touch device gets. */
.ppl{
  background:var(--void); color:var(--ink-i);
  padding:clamp(56px,8vh,112px) var(--pad) clamp(48px,7vh,96px);
}
.ppl__head{ margin-bottom:clamp(26px,4vh,52px) }
/* The site's eyebrow: micro, caps, tracked, muted — the same label idiom the
   services panel and the header dropdown use. */
.ppl__k{
  margin:0 0 clamp(10px,1.6vh,18px);
  font-family:var(--sans); font-weight:400; font-size:var(--micro);
  letter-spacing:.09em; text-transform:uppercase; color:var(--muted-i);
}
.ppl__t{
  margin:0; max-width:16ch;
  font-family:var(--sans); font-weight:400; color:var(--ink-i);
  font-size:clamp(30px,4.6vw,72px); line-height:1.08; letter-spacing:-.022em;
}

/* ── the wall ─────────────────────────────────────────────── */
.ppl__row{
  list-style:none; margin:0; padding:0;
  display:flex; flex-direction:column;
  gap:clamp(12px,3vw,20px);
}
.ppl__p{
  position:relative; overflow:hidden; min-width:0;
  aspect-ratio:4/5; background:var(--ph-dark);
  transition:opacity .8s var(--ease), transform .8s var(--ease);
}
.ppl__p:focus-visible{ outline:2px solid var(--ink-i); outline-offset:3px }
.ppl__img{
  position:absolute; inset:0;
  width:100%; height:100%; object-fit:cover; object-position:50% 20%;
  /* Held a little over frame at rest so opening the panel can settle it back
     to 1 — the photograph arrives rather than just changing colour. The
     panel clips, so the overscan is never visible as an edge. */
  transform:scale(1.06); transform-origin:50% 35%;
  transition:transform 1.1s var(--ease);
}
/* A rule that draws itself across the copy as the panel opens. It is the
   one piece of decoration in the section, and it is a line. */
.ppl__body::before{
  content:""; position:absolute; top:0; left:clamp(18px,4.5vw,28px); right:clamp(18px,4.5vw,28px);
  height:1px; background:rgba(255,255,255,.4);
  transform:scaleX(0); transform-origin:left center;
  transition:transform .75s var(--ease);
}
/* The scrim is its own layer rather than a background on the copy, so its
   weight can change with the panel's state without the text moving. */
.ppl__p::after{
  content:""; position:absolute; inset:auto 0 0 0; height:82%;
  /* Four stops, not two. A straight linear fade spends most of its length in
     the middle greys, which is exactly where the quote sits — the copy ends
     up over half-lit shirt and foliage. Weighting it low and long keeps the
     text on near-black while the top of the frame stays a photograph. */
  background:linear-gradient(to top,
    rgba(0,0,0,.96) 0%, rgba(0,0,0,.90) 34%, rgba(0,0,0,.62) 58%,
    rgba(0,0,0,.22) 82%, transparent 100%);
  pointer-events:none;
}
.ppl__body{
  position:absolute; z-index:1; inset:auto 0 0 0;
  /* the stacked layout is always open: nothing to hover, nothing hidden */
  padding:clamp(18px,4.5vw,28px);
  display:flex; flex-direction:column; gap:clamp(10px,2vw,16px);
}
/* Serif, like the footer tagline and the opening statement: these are the
   only sentences on the page in someone else's voice, and the change of
   face is what says so. The quotation marks are set, not typed, so the copy
   in the markup stays a plain sentence. */
.ppl__q{
  /* No measure cap: the quote runs the full width of the panel it is in, and
     the panel is what sets the line length. */
  margin:0;
  font-family:var(--serif); font-weight:400; color:var(--ink-i);
  font-size:clamp(17px,4.3vw,21px); line-height:1.36;
}
.ppl__q::before{ content:"\201C" }
.ppl__q::after{ content:"\201D" }
.ppl__n{
  margin:0;
  font-family:var(--sans); font-weight:500; color:var(--ink-i);
  font-size:clamp(16px,4vw,19px); letter-spacing:-.01em;
}

/* Every width that is NOT the expanding row shows the panels open, because
   there is no pointer there to open them with. */
@media (max-width:900px), (hover:none){
  .ppl__img{ transform:scale(1) }
  .ppl__body::before{ transform:scaleX(1) }
}

/* ── the phone: one rail, swiped ──────────────────────────
   Not four stacked cards. Stacked, the section ran to four screens of
   scrolling for four quotes, and each portrait arrived alone with no sense
   that there were others — the wall stopped being a wall.

   As a rail it is one object again: a card and a bit of the next one, which
   is the whole affordance (no arrows, no dots — the peek IS the hint), and
   the section costs one screen however many people are in it. Snap is
   mandatory and aligned to the start so a swipe always lands a card against
   the page margin rather than halfway across it.

   The row goes full-bleed to do it. The section's side padding comes off and
   moves onto the header and onto the rail's own padding/scroll-padding, so
   the first card lines up with the heading above it while the last one can
   still be scrolled clear of the right edge. */
@media (max-width:900px){
  .ppl{ padding-left:0; padding-right:0 }
  .ppl__head{ padding:0 var(--pad) }
  .ppl__row{
    flex-direction:row; flex-wrap:nowrap;
    gap:clamp(10px,3vw,16px);
    padding:0 var(--pad); scroll-padding-left:var(--pad);
    overflow-x:auto; overflow-y:hidden;
    scroll-snap-type:x mandatory;
    -webkit-overflow-scrolling:touch;
    /* The bar would sit under the cards on a desktop-class browser at this
       width and read as a second, broken edge. The peek is the affordance. */
    scrollbar-width:none;
    /* overscroll-behavior-x so swiping past the last card does not hand the
       gesture to the browser's back-navigation. */
    overscroll-behavior-x:contain;
  }
  .ppl__row::-webkit-scrollbar{ display:none }
  .ppl__p{
    flex:0 0 min(78vw,360px);
    aspect-ratio:3/4.1;
    scroll-snap-align:start;
  }
  /* The entrance is per-panel and the panels are side by side now, so the
     off-screen ones would arrive having never been seen to move. They fade
     without the rise here; the lift stays a desktop thing. */
  .ppl__row.is-armed > .ppl__p{ transform:none }
  /* 16px flat, not the fluid clamp: the card is a fixed share of the screen
     at every phone width, so the copy inside it does not need to scale with
     the viewport — and 16 is the floor iOS needs to not treat a tap as a
     zoom cue. Weight carries the name against the quote instead of size. */
  .ppl__q, .ppl__n{ font-size:16px }
}

/* ── a wide screen with no pointer (tablet, kiosk) ──────────
   Two up, everything still open: the expanding row below is hover-driven,
   and hover is the one thing this device cannot do. */
@media (min-width:901px) and (hover:none){
  .ppl__row{ display:grid; grid-template-columns:1fr 1fr; gap:clamp(12px,1.6vw,20px) }
}

/* ── the expanding row ────────────────────────────────────── */
@media (min-width:901px) and (hover:hover){
  .ppl__row{
    flex-direction:row; gap:clamp(8px,.9vw,14px);
    height:clamp(440px,56vh,640px);
  }
  .ppl__p{
    flex:1 1 0; aspect-ratio:auto;
    /* opacity/transform carried over from the base rule — this replaces the
       whole shorthand, so dropping them here would leave the entrance with
       nothing to animate on exactly the widths that have room for it. */
    transition:flex-grow .6s var(--ease), opacity .8s var(--ease), transform .8s var(--ease);
  }
  .ppl__img{
    filter:grayscale(1) brightness(.78) contrast(1.06);
    transition:filter .5s var(--ease);
  }
  .ppl__p::after{ opacity:.8; transition:opacity .5s var(--ease) }
  /* Faded, not removed. The body is anchored to the bottom of the panel, so
     a hidden quote still holds its height and grows UPWARDS into the part of
     the frame the scrim already covers — the name sits at the same point in
     a closed panel as in an open one, and opening it moves nothing. */
  .ppl__q{
    opacity:0; transform:translateY(10px);
    font-size:clamp(18px,1.45vw,24px);
    transition:opacity .45s var(--ease), transform .45s var(--ease);
  }
  .ppl__n{ font-size:clamp(17px,1.2vw,21px) }

  /* open: pointed at, focused, or the first one while the row is neither */
  .ppl__p:hover,
  .ppl__p:focus-within,
  .ppl__row:not(:hover):not(:focus-within) > .ppl__p:first-child{ flex-grow:2.9 }

  .ppl__p:hover .ppl__img,
  .ppl__p:focus-within .ppl__img,
  .ppl__row:not(:hover):not(:focus-within) > .ppl__p:first-child .ppl__img{ filter:none }

  .ppl__p:hover::after,
  .ppl__p:focus-within::after,
  .ppl__row:not(:hover):not(:focus-within) > .ppl__p:first-child::after{ opacity:1 }

  .ppl__p:hover .ppl__q,
  .ppl__p:focus-within .ppl__q,
  .ppl__row:not(:hover):not(:focus-within) > .ppl__p:first-child .ppl__q{
    opacity:1; transform:none;
  }

  /* the photograph settles, and the rule draws */
  .ppl__p:hover .ppl__img,
  .ppl__p:focus-within .ppl__img,
  .ppl__row:not(:hover):not(:focus-within) > .ppl__p:first-child .ppl__img{ transform:scale(1) }

  .ppl__p:hover .ppl__body::before,
  .ppl__p:focus-within .ppl__body::before,
  .ppl__row:not(:hover):not(:focus-within) > .ppl__p:first-child .ppl__body::before{ transform:scaleX(1) }
}

/* ── the entrance ─────────────────────────────────────────
   The panels rise in turn as the row arrives. is-armed is added by main.js
   §10 and ONLY there: without the script (or without IntersectionObserver)
   the class never lands and the panels are simply on the page, rather than
   being hidden by a stylesheet waiting for a reveal that cannot come.

   A transition and not a keyframe, deliberately: a transition's end state is
   the declared value, so if the animation never runs — a browser that skips
   it, a paused document timeline — the panel is simply visible. An animation
   that fails to start leaves the panel at the hidden state forever, which is
   the whole section gone.

   The stagger is the moment the class is added (main.js), not a
   transition-delay, so nothing is left sitting on the panel afterwards for
   the first hover to wait out. */
.ppl__row.is-armed > .ppl__p{ opacity:0; transform:translateY(26px) }
.ppl__row.is-armed > .ppl__p.is-in{ opacity:1; transform:none }

/* Nothing else here depends on the script, so .no-js needs no rules — only
   the transitions come off when motion is turned down. */
.no-motion .ppl__p,
.no-motion .ppl__img,
.no-motion .ppl__p::after,
.no-motion .ppl__body::before,
.no-motion .ppl__q{ transition:none }
.no-motion .ppl__row.is-armed > .ppl__p{ opacity:1; transform:none }
/* and the overscan comes off, or a still frame is a crop for no reason */
.no-motion .ppl__img{ transform:scale(1) }


/* ═════════════════ 6c · office / amenities ═════════════════
   A horizontal filmstrip driven by vertical scroll, over a static intro
   screen. The intro (.aslide-intro) fills the pinned viewport and never
   moves: black copy panel left, full-bleed photo right — that is the first
   screen, before any scrolling. The strip (.aslider__track) is ONE flex row
   parked just off the right edge, which JS translates leftwards; because it
   carries its own white background it slides in across the intro photo,
   covering it from the right, and then keeps going. No per-slide show/hide
   and no crossfade anywhere: the strip just moves, and the viewport is a
   window onto it.

   Per slide the three pieces are separate track columns in reading order
   (heading, image, caption) rather than a copy block beside the image. That
   is what gives the reference its rhythm — a heading arriving top-left, its
   image filling the middle, its caption trailing along the bottom while the
   NEXT heading is already entering from the right. */
.amenities{ position:relative; background:var(--void) }

/* Height is overwritten by JS to (viewport + one viewport of lead-in +
   horizontal distance) so the strip travels 1:1 with the wheel; the calc is
   the pre-JS fallback and keeps the page from jumping if the script never
   runs. */
.aslider{ position:relative; height:calc(8 * 100vh) }
.aslider__pin{ position:sticky; top:0; height:100svh; overflow:hidden; background:var(--paper) }

/* The static first screen. Sits under the strip, so the strip's white
   background is what wipes it away — the intro itself never moves. */
.aslide-intro{ position:absolute; inset:0; z-index:1; display:flex; align-items:stretch }
.aslide-intro__copy{
  flex:0 0 auto; width:min(49vw,760px);
  background:var(--void); color:var(--ink-i);
  display:flex; flex-direction:column; justify-content:center;
  gap:clamp(18px,2.4vw,32px);
  padding:0 var(--pad);
}
/* Takes whatever the copy panel leaves, and bleeds to the top, bottom and
   right edges of the viewport. */
.aslide-intro__media{ position:relative; flex:1 1 auto; min-width:0; overflow:hidden; background:#111 }
.aslide-intro__media img{
  position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; display:block;
}

/* z-index above the intro, and its own opaque background: together those are
   what make the strip read as sliding in over the intro photo rather than
   appearing beside it. The left padding keeps the first heading off the
   white's leading edge as it arrives. */
.aslider__track{
  position:absolute; inset:0 auto 0 0; z-index:2; height:100%;
  /* Roughly double the slide's own internal gap: the strip has to read as a
     sequence of separate slides, so the space between one slide's caption
     and the NEXT slide's image must clearly beat the space between a slide's
     own text column and its image. At parity the two gaps were
     indistinguishable and the next photo looked like it belonged to the
     caption beside it. */
  display:flex; align-items:stretch; gap:clamp(88px,10vw,200px);
  padding-left:clamp(28px,3vw,60px);
  /* Matches main.js's trailing gutter (A_TAIL) — as PADDING, not just extra
     scroll travel, so the track's own white background still covers that
     gutter. It used to be travel-only: the strip's box (background included)
     is only as wide as its content, so scrolling A_TAIL px further than that
     ran the box itself off-screen before the scroll-through finished,
     exposing the never-moving .aslide-intro layer underneath — its black
     panel/photo flashing back in right at the end, reading as the slider
     "getting cut off". */
  padding-right:clamp(60px,8vw,120px);
  background:var(--paper);
  will-change:transform;
}
.amenities__t{
  text-align:left; letter-spacing:-.02em; margin:0; max-width:18ch;
  font-family:var(--sans); font-weight:400;
  font-size:clamp(28px,3.6vw,52px); line-height:1.12;
}
.amenities__d{
  font-size:clamp(15px,1.35vw,20px); line-height:1.5; color:var(--muted-i);
  max-width:40ch; margin:0; text-align:left;
}
/* The banner's pill, on the copy panel's black — white fill, inverting to
   an outline on hover. align-self:flex-start, because the panel is a column
   flex with stretched items: without it the pill would run the full width
   of the panel instead of sizing to its label.

   The extra top margin sits on top of the panel's own gap, so the button
   reads as an action after the paragraph rather than as a third line of
   the same block. */
.amenities__cta{
  align-self:flex-start;
  margin-top:clamp(8px,1.2vw,20px);
  display:inline-flex; align-items:center; justify-content:center;
  padding:calc(16px + var(--pill-shim)) 32px calc(16px - var(--pill-shim)); border-radius:999px; border:1px solid #fff;
  background:#fff; color:var(--ink); text-decoration:none;
  font-family:var(--sans); font-weight:500;
  font-size:clamp(15px,1.15vw,18px); line-height:1;
  transition:background .35s var(--ease), color .35s var(--ease), border-color .35s var(--ease);
}
.amenities__cta:hover,
.amenities__cta:focus-visible{ background:var(--pill-hover-light); border-color:var(--pill-hover-light); color:var(--ink) }

/* Symmetric vertical padding: it is the slide's own inset that puts the
   image's top and bottom edges where they are, and the same box is the one
   the text column centres itself in — so the heading/caption block sits on
   the image's vertical midline. */
.aslide{
  flex:0 0 auto; color:var(--ink);
  display:flex; align-items:stretch; gap:clamp(40px,5vw,96px);
  padding:clamp(48px,7.6vh,110px) 0;
}
/* Deliberately narrow measure: the reference wraps even a short heading to
   two or more lines, which is what keeps the heading reading as a column of
   the strip rather than a banner running into the image. Still stretched to
   the slide's full content height, but the heading and caption now sit as
   one centred block against the middle of the image rather than being
   pushed to its top and bottom edges — the captions vary enough in length
   (one line of source against four) that space-between left each slide with
   a differently sized hole down the middle of the column. No top margin: it
   would shift the centred block off centre by its own height. */
.aslide__text{
  flex:0 0 auto; align-self:stretch; width:min(26vw,380px);
  margin:0;
  display:flex; flex-direction:column; justify-content:center;
  gap:clamp(18px,2.4vw,32px);
}
.aslide__t{
  margin:0;
  font-family:var(--sans); font-weight:400; color:var(--ink);
  font-size:clamp(28px,3.6vw,54px); line-height:1.14; letter-spacing:-.015em;
}
/* .aslide__cap is a plain block inside .aslide__text, sitting directly under
   the heading in that centred column — full body size, ink, sentence case,
   meant to be read rather than the tracked-out micro label the card
   sections use (see .aslide__d/.aslide__source below). */
.aslide__cap{ margin:0 }
.aslide__d{
  margin:0 0 .9em; color:var(--ink);
  font-size:clamp(15px,1.5vw,24px); line-height:1.42;
}
.aslide__d:last-child{ margin-bottom:0 }
/* The cited organisation's mark, above its own Source line. One shared
   height for all six on desktop, same as the Slack mark — width follows
   from object-fit:contain, so wordmarks (Steelcase, Microsoft) just read
   wider than the near-square ones (Slack, CDC, Harvard) at this same
   height rather than each shape getting its own height tier. On mobile,
   Harvard, CDC and Delhi Metro get a bump via .aslide__logo--lg and
   Steelcase a reduction via .aslide__logo--sm, both below — see those
   rules' comments. */
.aslide__logo{
  display:block; width:auto; height:clamp(18px,1.9vw,30px);
  margin:0 0 clamp(10px,.9vw,14px);
  object-fit:contain; object-position:left center;
}

.aslide__source{
  margin:0; color:var(--muted);
  font-size:clamp(13px,1.1vw,17px); line-height:1.42;
}
.aslide__source strong{ color:var(--ink); font-weight:600 }
.aslide__media{
  position:relative; flex:0 0 auto; align-self:stretch;
  width:min(55vw,860px);
  overflow:hidden; border-radius:2px;
  background:#ececec;
}
/* Oversized inside its frame so the internal pan (see main.js) has room to
   move the picture without ever exposing an edge: 12% of the frame's width
   of slack on each side against a pan that reaches 10.5%, so there is ~1.5%
   of margin left over for subpixel rounding at the extremes. */
.aslide__media img{
  position:absolute; left:-12%; top:-6%; width:124%; height:112%;
  object-fit:cover; display:block; will-change:transform;
  /* Which part of the photo survives the crop. The frames go portrait on a
     tall viewport while these photos are landscape, so cover throws away a
     lot of width — and dead centre is not always where the subject is. Per
     slide override with --focus on .aslide__media. Percentages, so the
     shift scales with however much overflow there actually is. */
  object-position:var(--focus,50%) var(--focus-y,50%);
}
@media (max-width:820px){
  /* Keep the desktop filmstrip choreography on phones. The intro becomes a
     two-row viewport, while the white strip still wipes across it from the
     right and continues horizontally through every amenity. */
  .aslider{ height:calc(8 * 100vh) }
  .aslider__pin{ position:sticky; top:0; height:100dvh; overflow:hidden }
  .aslider__track{
    position:absolute; inset:0 auto 0 0; height:100%;
    flex-direction:row; gap:clamp(56px,16vw,96px);
    /* Wider than the page gutter (--pad is 18px here): this is the inset
       between the white strip's own leading edge and the first heading as
       the strip wipes in, and at the gutter's width the text read as though
       it were falling off the edge of the incoming panel. */
    padding-left:clamp(28px,7.5vw,44px); padding-right:clamp(42px,14vw,72px);
  }
  .aslide-intro{ position:absolute; inset:0; flex-direction:column }
  .aslide-intro__copy{
    flex:0 0 56%; width:100%;
    /* 18px off each end. The top keeps its safe-area term — that part is the
       notch, not padding, and trimming it would put the heading under the
       status bar on a notched phone. */
    padding:calc(46px + env(safe-area-inset-top)) var(--pad) clamp(12px,3.4vw,26px);
    gap:clamp(16px,4vw,22px);
  }
  /* The panel is only 56% of a phone screen here and the picture takes the
     rest, so the pill drops to the copy's own size rather than holding the
     desktop's padding. 14px, not 13: at a 15px label and line-height 1 the
     smaller figure lands the button on 43px, a pixel under the 44px
     minimum for a touch target. */
  .amenities__cta{
    padding:calc(14px + var(--pill-shim)) 24px calc(14px - var(--pill-shim));
    margin-top:clamp(4px,1.5vw,10px);
  }
  .aslide-intro__media{ flex:1 1 auto; height:auto; min-height:0; aspect-ratio:auto }
  .aslide-intro__media img{ object-position:center 42% }
  .amenities__t{ max-width:none }
  /* Stacked, not side-by-side: text on top, image below as a fixed-ratio
     landscape frame rather than the desktop row's tall portrait one.
     .aslide still stretches to the track's full (100dvh) height — see
     .aslider__track's align-items:stretch above — but the media no longer
     flex-fills whatever's left of it (that produced a portrait box on the
     longer captions, since "remaining height" varied slide to slide); its
     own aspect-ratio decides its shape now, and any leftover height below
     it is just blank. */
  .aslide{
    flex-direction:column; align-items:stretch; justify-content:space-between;
    width:min(84vw,380px);
    /* Vertical padding trimmed to the bare clearance the fixed header needs
       and nothing more: the bar is 64px and the heading can rise to the top
       of this box on a short viewport, where anything less ran the first
       line of a two-line heading in behind it. The 24px of air that used to
       sit on top of the bar is gone, as is the bottom inset — on a phone the
       photo's own bottom edge is the slide's bottom edge now, and only the
       device's safe area is held back. */
    padding:calc(64px + env(safe-area-inset-top)) 0 env(safe-area-inset-bottom);
    gap:clamp(16px,4.6vw,22px);
  }
  /* Fills whatever the (fixed-height) photo leaves, and centres the heading
     and caption inside it — justify-content:center comes from the shared
     .aslide__text rule above, which has nothing to override it here.
     The column deliberately does NOT distribute its slack between its own
     children: on a tall phone (a 1258px viewport in devtools, say) the pin
     is far taller than the slide's content, and pushing the citation to the
     bottom of the column opened a half-screen hole between the paragraph
     and the logo. Centred, that leftover height splits above and below the
     text block instead, where it reads as margin rather than as a gap
     someone forgot to close. */
  .aslide__text{
    width:100%; flex:1 1 auto; min-height:0; margin:0;
    gap:clamp(14px,3.4vw,22px);
  }
  .aslide__t{ font-size:clamp(32px,10vw,44px) }
  .aslide__d{ font-size:clamp(16px,4.3vw,19px) }
  .aslide__source{ font-size:clamp(14px,4.1vw,17px) }
  .aslide__logo{ height:clamp(17px,4.6vw,22px); margin-bottom:clamp(8px,2.4vw,11px) }
  /* Harvard, CDC and Delhi Metro carry finer internal detail (a crest, a
     wordmark inside a box, a part-mark-part-wordmark combo) that reads as
     noticeably smaller than the plainer wordmark logos at the same height
     — bumped up on mobile only, to match how large the other marks read
     here. Desktop is unaffected: no .aslide__logo--lg rule outside this
     breakpoint, so it just falls back to the shared .aslide__logo height
     there. */
  .aslide__logo--lg{ height:clamp(30px,8vw,38px) }
  /* Steelcase is the widest wordmark of the six (5.18:1) — at the shared
     .aslide__logo height it reads noticeably larger/wider than the others
     even though the height matches, since object-fit:contain lets its
     width run out proportionally further. Sized down on mobile only to
     bring its apparent size back in line; no .aslide__logo--sm rule
     outside this breakpoint, so desktop keeps the shared height. */
  .aslide__logo--sm{ height:clamp(13px,3.4vw,17px) }
  /* Anchored to the bottom of the slide by .aslide's space-between above,
     not stacked directly under the text: the text column's height varies
     slide to slide (two-line headings, longer sources), and with the image
     riding on top of it every frame started at a different height — one
     slide's photo high, the next one's low. Pinned to the bottom edge
     instead, with a fixed ratio and width, every slide's image occupies the
     same box; the slack from a shorter text block falls between the caption
     and the photo, where it reads as breathing room rather than as drift. */
  /* The frame takes the whole viewport MINUS a fixed allowance for the text,
     rather than a capped fraction of it (the old min(46dvh,390px)). That cap
     was the source of the dead white space: on a tall narrow viewport the
     photo stopped growing at 390px while the pin kept getting taller, and
     every pixel past that landed in .aslide__text as slack — 784px of column
     around ~340px of text at 425x1258.

     Sizing it the other way round makes the text column a CONSTANT 390px at
     every viewport height and hands the photo all the variation, so the slack
     is only ever the difference between one slide's text and the tallest
     slide's. Measured across the whole ≤820px range, the tallest slide's
     heading + caption is 362px (it peaks at ≥640px wide, where .aslide is
     already capped at 380px and the font clamps have all topped out), so 390
     leaves ~28px on the worst slide and ~90px on the lightest.

     476px = 64px header clearance + the 22px column gap + that 390px
     allowance. The 150px floor is for viewports under ~625px tall, where the
     subtraction would otherwise drive the frame to nothing; below ~598px tall
     the text no longer fits and starts to compress, which on a ≤820px-wide
     screen means a landscape phone — every portrait phone and small tablet
     clears it. */
  .aslide__media{
    width:100%; flex:0 0 auto; align-self:stretch; margin-top:auto;
    height:max(150px,calc(100dvh - 476px - env(safe-area-inset-top) - env(safe-area-inset-bottom)));
  }
}

/* ═════════════════ 6d · scroll-activated text ═════════════════
   Adapted from Codegrid's "scroll-activated text blocks": three short
   statements sit in fixed columns; as the pinned section scrolls, each
   column's words mask out while the next column's words mask in — so the
   three ideas play in sequence rather than all at once. A velocity-reactive
   image marquee rides along the bottom of the same pin. */
.scrolltext{ background:var(--void); color:var(--ink-i); height:400vh; position:relative }
.scrolltext__pin{ position:sticky; top:0; height:100svh; overflow:hidden }

/* The title is placed absolutely on its own on the home page. Where it has
   a lede under it (why-us), the pair is wrapped so the two travel together
   and the lede cannot drift over the columns. */
.scrolltext__head{
  position:absolute; left:var(--pad); top:clamp(28px,4vw,56px); z-index:1;
  max-width:min(70vw,780px);
}
.scrolltext__title{
  position:absolute; left:var(--pad); top:clamp(28px,4vw,56px); z-index:1;
  max-width:min(70vw,780px); margin:0;
  font-family:var(--sans); font-weight:400;
  font-size:clamp(28px,3.8vw,56px); line-height:1.1; letter-spacing:-.01em;
}
/* inside the wrapper it is a normal block again */
.scrolltext__head .scrolltext__title{
  position:relative; left:auto; top:auto; max-width:none;
}
.scrolltext__lede{
  margin:.5em 0 0;
  font-family:var(--sans); font-weight:400; color:var(--muted-i);
  font-size:clamp(14px,1.35vw,20px); line-height:1.4;
}
.scrolltext__indicator{
  position:absolute; width:min(160px,14vw); height:1px; z-index:1;
  top:clamp(30px,4.3vw,58px); right:var(--pad);
  background:#2f2f2f;
}
.scrolltext__indicator::after{
  content:''; position:absolute; inset:0;
  background:#fff; transform-origin:left; transform:scaleX(var(--progress,0));
  will-change:transform;
}

.scrolltext__copy{
  position:absolute; left:0; top:50%; transform:translateY(-50%);
  width:100%; padding:0 var(--pad);
  display:flex; gap:clamp(18px,2.6vw,40px);
}
.scrolltext__block{ flex:1 }
/* The step's heading: sans and a size up from the serif body under it, so
   the two read as heading + paragraph rather than one bolded sentence at
   the top of a block. */
.scrolltext__block .scrolltext__lead{
  font-family:var(--sans); font-weight:500; color:var(--ink-i);
  font-size:clamp(21px,2.4vw,36px); line-height:1.15; letter-spacing:-.015em;
  margin-bottom:.6em;
}
.scrolltext__block p{
  font-family:var(--serif); font-weight:400;
  font-size:clamp(17px,1.7vw,26px); line-height:1.34; letter-spacing:-.006em;
  margin:0;
}
.scrolltext__block p .word{ will-change:transform }

.scrolltext__marquee{
  position:absolute; left:0; bottom:clamp(20px,2.8vw,36px); width:100%;
  height:clamp(130px,16vw,230px); overflow:hidden;
  display:flex; align-items:center;
}
.scrolltext__track{ display:flex; gap:14px; will-change:transform }
.scrolltext__item{
  flex:none; width:clamp(200px,22vw,320px); height:100%;
  border-radius:2px; overflow:hidden; background:var(--ph-dark);
}
.scrolltext__item img{ width:100%; height:100%; object-fit:cover; display:block }

/* ── the marquee-less variant (why-us) ────────────────────
   With no image strip at the bottom, the default composition pools its
   space in the two worst places: the heading is absolutely pinned to the
   top edge (so it sits under the fixed header with nothing above it) and
   the columns are centred on their own (so a tall viewport leaves a dead
   band beneath them). Here the two are laid out as ONE centred group, which
   gives the heading air above it and takes the slack out of the bottom.

   Scoped to the modifier rather than changed on .scrolltext itself: on the
   home page the marquee is absolutely positioned along the bottom, and a
   taller centred group would run into it. */
.scrolltext--plain .scrolltext__pin{
  display:flex; flex-direction:column; justify-content:flex-start;
  padding:clamp(72px,9vh,110px) var(--pad) clamp(36px,5vh,64px);
}
.scrolltext--plain .scrolltext__head,
.scrolltext--plain .scrolltext__title{
  position:relative; left:auto; top:auto;
}
/* The heading stays at the TOP of the pin — centring the pair together sent
   it down to ~40% of the viewport, which read as the section starting late.
   margin-block:auto centres only the COLUMNS in whatever is left below the
   heading, so the heading keeps its place and the slack is split rather than
   all falling under the columns. */
.scrolltext--plain .scrolltext__copy{
  position:relative; left:auto; top:auto; transform:none;
  width:auto; padding:0;
  margin-block:auto;
}

/* static fallback (phone width, or reduced-motion): JS skips the pin and
   the word-mask entirely, so the track never gets a transform — let it
   scroll natively instead of clipping the later images away */
@media (max-width:820px){
  .scrolltext{ height:auto }
  .scrolltext__pin{ position:relative; height:auto; padding:100px 0 40px; overflow:visible }
  /* Was left position:absolute while its siblings above were un-stacked
     back into normal flow — it stayed pinned to the top-left corner of the
     section, floating over whichever paragraph now flowed underneath it. */
  .scrolltext__title{ position:relative; left:auto; top:auto; max-width:100%; margin:0 0 28px; padding:0 var(--pad) }
  .scrolltext__head{ position:relative; left:auto; top:auto; max-width:100%; margin:0 0 28px; padding:0 var(--pad) }
  .scrolltext__head .scrolltext__title{ margin:0; padding:0 }
  .scrolltext__copy{ position:relative; top:auto; transform:none; flex-direction:column; gap:28px }
  .scrolltext__marquee{ position:relative; bottom:auto; margin-top:36px; overflow-x:auto; scrollbar-width:none }
  .scrolltext__marquee::-webkit-scrollbar{ display:none }
}
.no-motion .scrolltext{ height:auto }
.no-motion .scrolltext__pin{ position:relative; height:auto; padding:100px 0 40px; overflow:visible }
.no-motion .scrolltext__title{ position:relative; left:auto; top:auto; max-width:100%; margin:0 0 28px }
.no-motion .scrolltext__head{ position:relative; left:auto; top:auto; max-width:100%; margin:0 0 28px }
.no-motion .scrolltext__copy{ position:relative; top:auto; transform:none; flex-direction:column; gap:28px }
.no-motion .scrolltext__marquee{ position:relative; bottom:auto; margin-top:36px; overflow-x:auto; scrollbar-width:none }
.no-motion .scrolltext__marquee::-webkit-scrollbar{ display:none }

/* ═══════════════════════ 6 · brands ══════════════════════ */
.brands{ background:var(--void); color:var(--ink-i); height:200vh; position:relative }
.brands__pin{
  position:sticky; top:0; height:100svh; overflow:hidden;
  display:grid; place-items:center;
}
.brands__ring{
  position:absolute; inset:0;            /* fills the pin, so its LAYOUT box
                                            gives JS a resolvable size to read.
                                            No transform of its own — GSAP owns
                                            the scroll scale on this element. */
  will-change:transform;
}
/* The ring is an ELLIPSE, not a circle, and it is taller than it is wide.
   Measured off the reference: horizontal semi-axis 508px, vertical 563.5px in
   a 1394px viewport — B/A = 1.1085, A = 0.3646vw. That ratio is what reads as
   a ring lying back in 3D rather than a flat hoop. The vh term keeps it from
   running off a short window. */

.chip{
  position:absolute; left:50%; top:50%;
  will-change:transform,opacity;
  /* JS writes width/height/transform/opacity/z-index every frame */
}
.chip__i{
  display:block; width:100%; height:100%;
  border-radius:50%; overflow:hidden;
  background:#f8f8f8;                    /* the reference chip is a near-white disc */
  transform:translateZ(0);
  backface-visibility:hidden;
}
.chip__i img{ display:block; width:100%; height:100%; object-fit:cover }

/* Reference heading: 127px at a 1394px viewport (0.0911vw), leading 0.8167,
   block 804px wide = 0.577vw. The block has to stay narrower than the ring's
   1016px horizontal span or the words collide with the chips instead of
   sitting in the hole. z-index 2 puts it between the back and front chips. */
.brands__t{
  position:relative; z-index:2; text-align:center;
  width:min(57.7vw, 900px); max-width:none;
  margin-inline:auto; padding-inline:0;
  /* Reference sets 127px in an 804px block — but that block is 0.79 of the
     ring's 1016px span, and THAT proportion is what keeps the words inside the
     hole. Source Serif is wider than the reference face, so holding 0.577vw
     and trimming the type 6.7% (0.0911 -> 0.085vw) lands the same four lines
     inside the same proportion, rather than widening into the chips. */
  /* width and font-size are written by layoutBrands() from the ring's own A,
     so the words always sit inside the hole; these are the pre-JS fallback */
  font-size:clamp(28px, 8.5vw, 170px);
  line-height:.8167; letter-spacing:-.018em;
}

/* ═════════════════ 6e · culture: the folder drawer ═════════════════
   Codegrid's "Wildy Riftian Hover Effect" (~/Downloads/services-section).
   The demo's geometry is kept because it IS the effect: rows overlapping by
   -13/-7.5/-2rem inside a bottom-aligned, clipped frame, so each folder
   shows only its tab; a 40%-wide index block whose ::after is a clip-path
   triangle, which is what makes the shape read as a file tab rather than a
   rectangle; and previews parked behind the tab, to be thrown upward on
   hover.

   The palette keeps the demo's logic — folders are solid objects and the
   one you point at is the only lit one — but not its cream and yellow, and
   not the mid-greys this section carried first. Large slabs of #d9d9d9 and
   #8c8c8c on pure black read as muddy planes rather than as paper: the
   section is black, so anything mid-grey looks like a washed-out hole in
   it, and with every folder already light there was nothing left for hover
   to do.

   So the folders are near-black surfaces, a three-step ramp that is just
   separable where they overlap, carrying white text like the rest of the
   section — and hover is the only light in the whole block: the folder
   under the pointer flips to paper with ink text while the others sink a
   step further into the ground. One lit folder, which is the effect. */
.fold{
  --fold-v1:#16171a;
  --fold-v2:#1d1e21;
  --fold-v3:#24262a;
  --fold-on:#363636;       /* the folder under the pointer */
  --fold-off:#0f1011;      /* a folder while another is hovered */

  /* The drawer's geometry, as variables, because the four numbers are tied
     to each other and guessing at any one of them breaks the stack:

       card     how tall one folder is
       o1/o2/o3 how far each row is pushed down; the DIFFERENCE between
                consecutive offsets is the overlap, so the visible tab is
                (card - 76px) — o1 alone sets how much empty space sits
                above the stack
       lift     headroom above the first row, so the 25px the tab rises on
                hover is not clipped by the frame's overflow:hidden. It was
                100px while the folders threw photographs upward; with those
                gone it only has to cover the lift itself

     The frame's height then follows from them rather than being a guess
     (see .fold__rows). */
  /* The floor is 224, not 200: the row step is (card - overlap), so a
     shorter card at narrow widths pulls the rows together and leaves the
     pop-out almost nowhere to go before it hits the title above. */
  --fold-card:clamp(224px,16.4vw,248px);
  /* The DIFFERENCE between consecutive offsets is the overlap, and the row
     step is (card - overlap). The demo's 76px overlap was fine at 1512,
     where the card is 248 and the rows sit 172 apart — but the card is
     fluid, and by 1100 it bottoms out at 200, the step falls to 124, and
     the title of the row above drops to only 28px clear of the next tab.
     The photographs then cross it however small the pop-out is.

     A 40px overlap keeps the step at (card - 40), so the clearance holds at
     every width the previews exist at — and the visible tab gets taller
     with it, which is the direction this section was going anyway. */
  --fold-o1:5.5rem;
  --fold-o2:3rem;
  --fold-o3:.5rem;
  /* Each offset is 40px less than the one before it — that constant is the
     overlap. Continuing the run past row 3 takes it negative, which is
     simply a row that has to sit ABOVE its own flow position to keep the
     same overlap now that there are four of them. */
  --fold-o4:-2rem;
  --fold-lift:40px;
  /* How many rows the markup holds. The frame's height is derived from it
     (see .fold__rows), so adding or removing a row is this one number plus
     an offset above, not a re-guessed pixel height. */
  --fold-rows:4;

  position:relative; background:var(--void); color:var(--ink-i);
  /* No floor under the stack: the footer that follows is the same black and
     opens on its own clamp(40px,7vh,96px) of padding, so the gap the drawer
     used to hold itself was being paid twice and read as a band of dead
     ground between the last tab and the wordmark. The footer's own top
     padding is the separation now. (Still kept under 1000px, where the rows
     flatten into a list and the last card would otherwise butt into it.) */
  padding:clamp(44px,4.4vw,76px) 0 0;
}
/* A row, not a column: the heading holds the left and the "View All" link
   the right. With only the heading in it (the homepage's copy) a one-item
   row renders exactly as the column did, so this is safe for both. Centre
   alignment, so the link sits on the heading's middle rather than dropping
   to its baseline; wrap, so a narrow screen drops the link under the
   heading instead of crushing it. */
.fold__head{
  display:flex; flex-wrap:wrap; align-items:center;
  justify-content:space-between; gap:clamp(8px,.8vw,14px) clamp(16px,2vw,32px);
  padding:0 var(--pad) clamp(20px,2.2vw,36px);
}
.fold__all{
  flex:0 0 auto;
  font-family:var(--sans); font-weight:400; color:var(--ink-i);
  font-size:clamp(16px,1.55vw,24px); line-height:1;
  text-decoration:underline; text-underline-offset:.3em;
  text-decoration-thickness:1px;
  transition:opacity .25s var(--ease);
}
.fold__all:hover,
.fold__all:focus-visible{ opacity:.65 }
/* A standalone call to action set at 16px/1 is a 16px-tall thing to tap. The
   underline is part of the design, so the box is grown around the words
   rather than by moving them: padding on the block axis, pulled back out with
   an equal negative margin so the row it sits in keeps its own spacing. */
@media (max-width:820px){
  .fold__all{ padding-block:11px; margin-block:-11px }
}
.fold__t{
  margin:0; font-family:var(--sans); font-weight:500;
  font-size:clamp(30px,2.92vw,52px); line-height:1.2; letter-spacing:-.02em;
}
/* Bottom-aligned and clipped: the overlap below pushes the rows down out of
   the box, and this is what crops them into tabs.

   The height is derived, not chosen. Content is three cards tall; the rows
   are bottom-aligned and then pushed down by o1/o2/o3, which lifts the
   first row's top back up by o1 — so the empty band above the stack is
   (height - 3*card + o1), and solving that for the headroom the
   photographs need gives the line below. Choosing a number by eye instead
   is how the first pass ended up at 442px, short enough to clip the top
   row's tab off entirely. */
.fold__rows{
  position:relative; width:100%;
  height:calc(var(--fold-rows) * var(--fold-card) - var(--fold-o1) + var(--fold-lift));
  display:flex; flex-direction:column; justify-content:flex-end;
  overflow:hidden;
}
.fold__row{ position:relative; width:100%; display:flex }
/* the demo's own offsets */
.fold__row:nth-child(1){ bottom:calc(-1 * var(--fold-o1)) }
.fold__row:nth-child(2){ bottom:calc(-1 * var(--fold-o2)) }
.fold__row:nth-child(3){ bottom:calc(-1 * var(--fold-o3)) }
.fold__row:nth-child(4){ bottom:calc(-1 * var(--fold-o4)) }
/* and its uneven split on the middle row, so the stack is not a grid */
.fold__row:nth-child(2) .fold__card:nth-child(1){ flex:2 }
.fold__row:nth-child(2) .fold__card:nth-child(2){ flex:3 }

/* NOT .fold: the section carries that, and a card matching its own section's
   selector inherits the section background and padding — which is what made
   the tabs vanish and left the preview photographs floating in mid-air. */
.fold__card{
  position:relative; flex:1; height:var(--fold-card);
  display:flex; flex-direction:column;
  text-decoration:none; color:inherit; cursor:pointer;
}
.fold__wrap{
  position:relative; width:100%; height:100%;
  transform:translateY(25px); will-change:transform;
}
/* min-height, because the tab's height used to come from the number sitting
   in it. index.html's folders have no number any more, so without a floor the
   tab would collapse to its own padding and take the diagonal shoulder below
   with it. The value is what the numbered version measures, so about.html —
   which still carries numbers — does not move. */
.fold__num{ position:relative; width:40%; padding:.75rem; min-height:clamp(36px,2.8vw,42px) }
/* the tab's diagonal shoulder */
.fold__num::after{
  content:''; position:absolute; top:0; left:99%;
  height:101%; aspect-ratio:1;
  clip-path:polygon(0 0, 25% 0, 100% 100%, 0% 100%);
}
/* The tab used to carry a number; the folders are identified by name alone
   now, so only its shape and colour are left. Its min-height above is what
   holds that shape open. */
/* The top padding is not decoration. align-items:flex-start put the text's
   line box flush against the top of the block, and at line-height:1 a
   line box is shorter than the glyphs it holds — so caps and ascenders sat
   on, and appeared sheared by, that edge. Padding drops the text clear of
   it and 1.12 gives the line box room for its own ascenders. */
.fold__name{
  width:100%; height:100%;
  display:flex; align-items:flex-start;
  padding:clamp(8px,.8vw,16px) .25rem 0 2rem;
}
.fold__name h3{
  margin:0; font-family:var(--sans); font-weight:400;
  font-size:clamp(22px,2.5vw,42px); line-height:1.12; letter-spacing:-.02em;
}
.fold__num, .fold__num::after, .fold__name{ transition:background-color .25s ease }
.fold__name h3{ transition:color .25s ease }

.fold__card--v1 .fold__num, .fold__card--v1 .fold__num::after, .fold__card--v1 .fold__name{ background:var(--fold-v1) }
.fold__card--v2 .fold__num, .fold__card--v2 .fold__num::after, .fold__card--v2 .fold__name{ background:var(--fold-v2) }
.fold__card--v3 .fold__num, .fold__card--v3 .fold__num::after, .fold__card--v3 .fold__name{ background:var(--fold-v3) }
/* The lit folder. Painted from :hover rather than from a class, so it
   cannot be left behind if a pointer leaves the window mid-hover; the
   script only ever handles the SIBLINGS' dimming and the lift. */
.fold__card:hover .fold__num,
.fold__card:hover .fold__num::after,
.fold__card:hover .fold__name,
.fold__card:focus-visible .fold__num,
.fold__card:focus-visible .fold__num::after,
.fold__card:focus-visible .fold__name{ background:var(--fold-on) }
/* white, not ink: the lit tone is #363636 now, and ink on that is 1.6:1 —
   unreadable. The lift, the photographs and the siblings dropping back are
   what mark the folder out; the text just has to stay legible. */
.fold__card:hover .fold__name h3,
.fold__card:focus-visible .fold__name h3{ color:var(--ink-i) }
.fold__card:focus-visible{ outline:none }

.fold__card.is-off .fold__num,
.fold__card.is-off .fold__num::after,
.fold__card.is-off .fold__name{ background:var(--fold-off) }
.fold__card.is-off .fold__name h3{ color:var(--muted-i) }

@media (max-width:1000px){
  /* The demo's phone layout, which is NOT a flat list: the cards keep their
     height and overlap by half a rem, so each one shows its own tab while
     the card above it shows through to the RIGHT of that tab — the name
     block is taller than the card box by the height of the number strip, so
     it runs on behind its neighbour. Flattening these to height:auto (and
     dropping the shoulder) is what turned the stack into a plain list. */
  .fold__rows{ height:auto; overflow:visible }
  .fold{ padding-bottom:clamp(40px,10vw,64px) }
  .fold__row{ flex-direction:column; bottom:0 !important }
  /* flex:0 0 auto here too, not flex:1 — these two selectors outrank the
     .fold__card rule below, so leaving them on a flex basis let the middle
     row's two cards size to their text while the other four kept the pinned
     height, and the stack stepped unevenly. */
  .fold__row:nth-child(2) .fold__card:nth-child(1),
  .fold__row:nth-child(2) .fold__card:nth-child(2){ flex:0 0 auto }
  /* flex:0 0 auto, or the height below is ignored: these are items of a
     column flex container, and with the demo's flex:1 the card takes its
     size from the container instead — which on the demo is a 100svh box,
     but here the frame is height:auto on phones, so the cards collapsed to
     their text (98px) and the stack flattened. */
  .fold__card{
    flex:0 0 auto;
    height:clamp(148px,40vw,200px); margin-bottom:-.5rem;
  }
  .fold__wrap{ transform:none }
  .fold__num{ width:40%; padding:.7rem var(--pad) }
  /* The top padding has to be restated here. This rule replaces the
     shorthand set on .fold__name above, and writing 0 for the top undid the
     clearance the titles need — at line-height 1.12 the glyphs are taller
     than their line box, so flush against the number strip the caps and
     ascenders look sheared off. Same bug as the desktop one, reintroduced
     by a shorthand. */
  .fold__name{ padding:clamp(10px,3vw,16px) var(--pad) 2rem }
  .fold__name h3{ font-size:clamp(20px,5.6vw,30px) }

  /* Two tones, alternating, and BOTH clearly off the section's black.
     The area beside each shoulder is transparent by design — what shows
     through it is the card above, which is how the stack reads as folders
     in front of each other. At #16171a that card was indistinguishable
     from the black behind the section, so the gap read as a bite taken out
     of the tab rather than as the next folder. Lifting both tones is what
     makes it read as a stack; the desktop ramp can stay dark because there
     the rows overlap far more and no background shows between them.

     odd/even is first/second card of each row, which is the reading order
     once the rows become a single column — the desktop order
     (v1,v2,v2,v3,v1,v2) would otherwise put two identical greys adjacent
     and the seam would disappear. */
  .fold__card:nth-child(odd) .fold__num,
  .fold__card:nth-child(odd) .fold__num::after,
  .fold__card:nth-child(odd) .fold__name{ background:#24262b }
  .fold__card:nth-child(even) .fold__num,
  .fold__card:nth-child(even) .fold__num::after,
  .fold__card:nth-child(even) .fold__name{ background:#3a3d44 }
}

/* ═══════════════════════ 7 · footer ══════════════════════ */
.foot{ background:var(--void); color:var(--ink-i); position:relative }
.foot__inner{
  /* No fixed height, no scroll travel, and nothing to clip. The footer used
     to be a 170vh box with a 100svh sticky pane inside it, so the last screen
     of the page held still while a viewport and a half of scroll ran past.
     The position, the height and the overflow all belonged to that box; it is
     now a plain flex column sized by its own content, so the page ends where
     the footer's last line ends. */
  display:flex; flex-direction:column;
  /* Links at the TOP again — the header hides itself when the footer arrives,
     so there is nothing left to clear. The wordmark takes margin-top:auto, so
     the spare height sits between the links and the mark, the way the
     reference composes it (their columns sit 137px into a 1354px frame ≈ 10vh). */
  padding:clamp(40px,7vh,96px) var(--pad) clamp(12px,1.6vh,22px);
}
.foot__cols{
  display:flex; align-items:flex-start; gap:2.4%;
  font-size:var(--nav);
  /* Set on the columns, not on .foot__v, so the label and its value share a
     line box and therefore a first baseline in the side-by-side tiers. 1.3
     was the header's own ratio applied to a stacked list, where it reads as
     cramped: the nav is one horizontal row, these are eight rows of links
     with nothing but leading between them. */
  line-height:1.7;
}
.foot__col{ flex:1 1 0; display:flex }
/* The label column is a FIXED width, not a share of the column: the reference
   starts every value exactly 107px right of its label at vw 1639 (6.53vw),
   which is what makes the four pairs read as aligned. The old 44% reservation
   scaled with the column and left each value stranded far from its label. */
/* White, not --muted-i. The labels were the only grey text in the link
   block, which read as two tiers; the footer now sets the whole block in
   --ink-i so label and value are one colour. The fixed label width and the
   gap below still carry the grouping. */
.foot__k{ flex:0 0 clamp(84px,6.53vw,110px); color:var(--ink-i);
  /* 700, the real Nimbus Sans bold (the family only ships 400 and 700, so
     500 would round back to regular and synthesise nothing). The labels and
     their links are the same white at the same size, so weight is what
     separates the two now. */
  font-weight:700;
  /* Short labels (Address, Company) left slack inside this fixed box that
     read as the gap before .foot__v — nothing explicit ever separated the
     two. A longer label like "Our Policies" fills that box with little to
     no slack left over, so the value ran right up against it. An explicit
     margin guarantees a minimum gap no matter how much of the box the
     label text fills. */
  margin-right:0.75em;
}
.foot__v{ flex:1 1 0; display:flex; flex-direction:column }
.foot__v a{ position:relative; width:fit-content }
.foot__v a.is-on{ text-decoration:underline; text-underline-offset:.22em }
.foot__v a:hover{ color:#b9b9b9 }
.foot__v p{ margin:0 }

/* Social links carry their mark ahead of the name. The icon is sized in em so
   it tracks the link text through every footer breakpoint, and it inherits
   colour so it fades with the label on hover rather than staying lit. */
.foot__soc{ display:inline-flex; align-items:center; gap:.6em }
.foot__soc__i{
  width:1em; height:1em; flex:none; fill:currentColor;
  /* the glyphs are drawn to the full 24-box, so they read a touch heavier
     than the type beside them; this settles them back into the line */
  opacity:.9; transform:translateY(-.02em);
}

/* ── the footer between the phone and a wide desktop ──────────
   Four columns across, each with its label beside its value, is a layout that
   needs room. The label box alone is a fixed 84-110px, so at 821px — the first
   width that gets this treatment rather than the phone's — a quarter column
   leaves the value about 89px to live in, and the address comes out two or
   three words to a line. It reads as broken long before it reads as a grid.

   The phone already solves this by putting the label above its value and
   halving the column count, and that solution is just as good at 900px as it
   is at 390px. So it runs on up to the width where four columns of pairs
   become worth having at all. */
@media (min-width:821px) and (max-width:1199px){
  .foot__cols{
    display:grid; grid-template-columns:1fr 1fr;
    gap:clamp(30px,4.4vw,52px) clamp(32px,5vw,72px);
    align-items:start;
  }
  .foot__col{ display:block }
  .foot__k{ display:block; margin-right:0; margin-bottom:.7em }
}

/* From 1200 there is room for the design's four columns, but still not for a
   fixed 84-110px label sitting *beside* each value: that eats a third of the
   column, and the address — whose longest authored line is "4th Floor, Golf
   Course Road, Sector 53," — needs about 270px to set as it was written. Four
   of those plus their labels does not fit until somewhere past 1650px, which
   is about the 1639px the pair layout below was drawn at.

   So the four columns come back here while the label stays above its value,
   which gives the value the whole column. The side-by-side pairs return at
   the width they were designed for. */
@media (min-width:1200px) and (max-width:1679px){
  .foot__col{ display:block }
  .foot__k{ display:block; margin-right:0; margin-bottom:.7em }
}

/* The wordmark opens the footer instead of closing it: small, top left, on
   the same --pad margin as the header logo and the link columns below it.
   order:-1 rather than a markup move, because components/footer.html is the
   build source for all 22 pages and .foot__inner is already a flex column —
   this puts the wrap first in the flow with no DOM change and no resync. */
.foot__markwrap{
  order:-1;
  flex:0 0 auto; display:flex; align-items:flex-start;
  min-height:0;
  /* The gap the mark used to take ABOVE itself, now taken below: it separates
     the mark from the link columns rather than from the page above. */
  margin-bottom:clamp(36px,6vh,72px);
}
/* No flex:1 any more — the mark is sized by the SVG, not stretched to the
   frame, so the wrap hugs it and it sits hard left. */
.foot__mark{
  display:block; line-height:0;
}
/* Header-sized: the same clamp .mark__svg uses, so the footer wordmark and
   the one in the bar read as the same object. width:auto lets the intrinsic
   ratio set the width off that height. */
.foot__mark__svg{
  display:block; width:auto; height:clamp(24px,2.5vw,36px); color:currentColor;
}

/* The closing row. The tagline keeps the left margin the mark and the columns
   share; the rights line is pushed to the opposite end. Baseline-aligned, so
   the two read as one line rather than two blocks that happen to be side by
   side. (It used to sit under the wordmark — the mark now opens the footer,
   so this row closes it on its own.) */
.foot__base{
  flex:0 0 auto;
  display:flex; align-items:baseline; justify-content:space-between;
  gap:clamp(16px,3vw,48px);
  padding-top:clamp(10px,1.6vh,20px);
}
/* Serif, at one size for both — the sans at --nav made the rights line read
   as small print bolted onto a display line. */
.foot__tagline,
.foot__rights{
  margin:0; flex:0 1 auto;
  font-family:var(--serif); font-weight:400; color:var(--muted-i);
  font-size:clamp(20px,2vw,30px); line-height:1.2;
}
.foot__tagline{ text-align:left }
.foot__rights{ text-align:right; white-space:nowrap }


@media (max-width:640px){
  /* Two 20px serif lines cannot share a row this narrow, so they stack and
     both hang off the left margin. */
  .foot__base{ flex-direction:column; align-items:flex-start; gap:.25em }
  .foot__rights{ text-align:left }
}

/* ══════════════════════ ABOUT PAGE ══════════════════════ */
.is-about{ background:var(--void); color:var(--ink-i) }

/* 1 · hero
   The photograph is a real <img> in the markup rather than a background-image
   on an empty <span>. A background is invisible to the browser's preload
   scanner, so the largest image on the page was only discovered once the
   stylesheet had parsed; an <img> with fetchpriority="high" starts
   downloading straight from the raw HTML. It also carries alt text and an
   intrinsic size, neither of which a background can.

   It is also the only thing in this section left in normal flow, and it keeps
   its own aspect ratio — width:100%, height:auto, no object-fit. That is what
   guarantees the picture is never cropped: the section is exactly as tall as
   the photograph is at the current width, instead of the photograph being cut
   to fit a fixed 100svh frame. A phone is a ~0.35 aspect box and these photos
   are 1.78, so a full-height hero was showing a fifth of the frame's width —
   on a group shot that is five people out of thirty.

   Only .ahero__copy is taken out of flow, laid over the picture. Height comes
   from the image; the copy is positioned against it. */
.ahero{ position:relative; overflow:hidden; background:var(--void) }
.ahero__media{ position:relative; display:block; margin:0; background:#4a4a4a }
/* height:auto is what keeps the picture uncropped — but on its own it also
   lets the section grow past the viewport, and the copy is pinned to the
   BOTTOM of that section, so it lands below the fold. About is 3:2, which at
   1440px wide is a 960px hero on a 900px screen: the headline was off-screen
   until you scrolled.

   max-height caps the box at the viewport and object-fit decides what happens
   when it bites. Below the cap nothing is cropped, because the box is already
   the photo's own shape and `cover` has nothing to trim. Above it the crop is
   centred on --focal instead of the section simply clipping its own overflow,
   which would have cut the picture off at the bottom edge. */
.ahero__img{
  display:block; width:100%; height:auto;
  max-height:100svh;
  object-fit:cover; object-position:var(--focal,center 30%);
}

/* 1b · the same hero running film (culture.html)
   A <video> cannot borrow .ahero__img's height:auto trick. An <img> hands the
   layout its intrinsic ratio out of the width/height attributes before a byte
   of the file arrives; a <video> reports 300x150 until its metadata lands, so
   height:auto would lay the page out against a stub box and then jump the
   whole hero the moment the header downloaded. The ratio is therefore stated
   on the FIGURE, which reserves the right box on the first paint, and the
   film is stretched over it.

   --film-ar is the film's own shape (1280x604 — the source's baked-in 2.10:1
   letterbox is cropped out of the file rather than shown as black bands over
   a black page), so at desktop widths the frame is full-bleed and entirely
   uncropped: object-fit:cover has nothing to trim when the box is already the
   footage's shape. It only bites at the 100svh cap and on phones, where the
   token is overridden — see §20a. */
.ahero--film .ahero__media{
  --film-ar:1280/604;
  aspect-ratio:var(--film-ar);
  width:100%; max-height:100svh;
  background:#000;
}
/* Absolute, not in flow: the figure already owns the height, and a sibling in
   flow would push .ph--shade's scrim (z-index:1) off the picture. */
.ahero__video{
  position:absolute; inset:0; z-index:0;
  display:block; width:100%; height:100%;
  object-fit:cover; object-position:var(--focal,center);
  background:#000;
}
/* A deeper bottom scrim than the stills get, because a still can be chosen to
   suit the type over it and film cannot. .ph--shade's .42 was set against a
   photograph whose lower third is reliably dark; this footage pans through a
   lit office and a white shirt crosses exactly where the lede sits, which
   left it barely readable for a second or two at a time. The ramp is also
   started higher (56% rather than 42%) so it reads as the frame falling off
   rather than as a bar under the words. Phones override this again in §20a —
   that rule sits later in the file, which is what lets it win. */
.ahero--film .ph--shade::before{
  background:
    linear-gradient(to top, rgba(0,0,0,.74), rgba(0,0,0,.36) 24%, rgba(0,0,0,0) 56%),
    linear-gradient(to bottom, rgba(0,0,0,.34), rgba(0,0,0,0) 26%);
}
.ahero__t{
  position:absolute; left:var(--pad); bottom:calc(var(--pad) + .4vw); z-index:3;
  max-width:min(90vw,1100px);
  font-family:var(--sans); font-weight:400; color:#fff;
  font-size:64px; line-height:1.03; letter-spacing:-.022em;
}

/* 2 · philosophy + offices */
.phil{
  background:var(--void);
  display:flex; align-items:flex-start;
  padding:clamp(80px,11vw,200px) var(--pad) clamp(40px,5vw,90px);
}
.phil__k{
  flex:0 0 45%;
  font-family:var(--serif); font-size:max(12px,1.15vw); line-height:1.35;
  padding-top:clamp(50px,7vw,130px);
}
.phil__body{ flex:1 1 0; min-width:0 }
.phil__lede{
  font-family:var(--serif); font-weight:400;
  font-size:clamp(17px,1.6vw,26px); line-height:1.3; letter-spacing:-.006em;
  margin:0 0 1.1em;
}
.phil__lede:last-of-type{ margin-bottom:0 }

/* Reused as the closing "Looking Ahead" statement, straight after .mates'
   own bottom padding — the top padding tuned for clearing the hero photo
   above §2 is excess space here, so trim it down. */
.phil--closing{ padding-top:clamp(40px,5vw,90px) }
.phil--closing .phil__k{ padding-top:0 }

.offices__list--dark{ flex:none; margin-left:0; width:90% }
.offices__list--dark .office{ border-top-color:#2a2a2a; color:var(--ink-i) }
.offices__list--dark .office:last-child{ border-bottom-color:#2a2a2a }
.offices__list--dark .office__c,
.offices__list--dark .office__a{ color:#7d7d7d }
/* the plate inverts the other way on a dark page */
.offices__list--dark .offices__mask{ background:#fff }
.offices__list--dark .offices__mask .office{ color:var(--ink) }
.offices__list--dark .offices__mask .office__c,
.offices__list--dark .offices__mask .office__a{ color:#5e5e5e }

/* 3 · the team */
.mates{ background:var(--void); padding-bottom:clamp(80px,10vw,190px) }
.mates__head{ padding:clamp(70px,9vw,170px) var(--pad) clamp(46px,5.5vw,105px) }
.mates__t{
  text-align:center;
  font-size:clamp(44px,7.4vw,148px); line-height:.98; letter-spacing:-.035em;
}
.mates__notes{
  margin-top:clamp(26px,3vw,56px);
}
.mates__notes p{
  font-size:clamp(15px,1.3vw,20px); line-height:1.5; color:var(--muted-i); text-align:center;
  max-width:52ch; margin-inline:auto;
}
.mates__grid{
  display:grid; grid-template-columns:repeat(4,1fr);
  gap:clamp(40px,4.4vw,84px) clamp(8px,.9vw,18px);
  padding-inline:var(--pad);
}
.mate{ display:block }
/* the row is staggered, as in the reference */
.mate--0{ margin-top:0 }
.mate--1{ margin-top:calc(3.2vw) }
.mate--2{ margin-top:calc(6.4vw) }
.mate--3{ margin-top:calc(1.6vw) }
.mate__ph{
  display:block; position:relative; overflow:hidden;
  aspect-ratio:4/5; background:#4a4a4a;
}
.mate__ph img{ display:block; width:100%; height:100%; object-fit:cover }
.mate__cap{
  display:flex; justify-content:space-between; gap:8px;
  margin-top:calc(10px + .5vw);
  font-size:var(--micro); line-height:1.3;
}
.mate__cap span:last-child{ color:var(--muted-i) }

/* 4 · mission & vision (ported from Figma node 179:94) */
.mission{
  background:var(--paper); color:var(--ink);
  padding:clamp(60px,8vw,120px) var(--pad);
}
.mission__row{
  display:flex; align-items:flex-start; justify-content:space-between;
  gap:clamp(14px,1.6vw,28px);
  padding-block:clamp(40px,6vw,80px);
}
.mission__label{
  flex:0 0 auto; width:min(12vw,140px);
  font-family:var(--sans); font-weight:400; color:var(--ink);
  font-size:clamp(19px,1.6vw,24px);
}
.mission__copy{
  flex:0 1 420px; max-width:480px; min-width:0;
  display:flex; flex-direction:column; gap:clamp(16px,2vw,28px);
}
.mission__t{
  margin:0; font-family:var(--sans); font-weight:500; color:var(--ink);
  font-size:clamp(25px,2.8vw,38px); line-height:1.28; letter-spacing:-.02em;
}
.mission__d{
  margin:0; max-width:52ch; color:var(--muted);
  font-size:clamp(17px,1.3vw,19px); line-height:1.5; letter-spacing:-.01em;
}
.mission__media{
  flex:0 0 clamp(280px,36vw,508px); height:clamp(260px,26vw,380px);
  border-radius:4px; overflow:hidden; background:var(--ph);
  box-shadow:0 2px 8px rgba(0,0,0,.06);
}
.mission__media img{ display:block; width:100%; height:100%; object-fit:cover }
.mission__rule{ height:1px; background:#e6e6e6 }

@media (max-width:820px){
  .ahero__t{ font-size:clamp(24px,7vw,40px) }
  .phil{ flex-direction:column; gap:clamp(30px,6vw,60px) }
  .phil__k{
    flex:0 0 auto; padding-top:0;
    font-family:var(--sans); font-weight:400; font-size:clamp(19px,1.6vw,24px);
  }
  .phil__k br{ display:none }
  .mates__notes{ display:flex; flex-direction:column; gap:18px }
  .mates__notes p{ text-align:left }
  .mates__grid{ grid-template-columns:repeat(2,1fr) }
  .mate--1,.mate--2,.mate--3{ margin-top:0 }
  /* Name/designation stacked, not side by side — at two-up card width the
     space-between row squeezed the designation into wrapping awkwardly
     next to (or under) the name instead of reading as its own line. */
  .mate__cap{ flex-direction:column; gap:.2em }
  /* justify-content is reset because the desktop row uses space-between to
     push the media to the right gutter. Along a COLUMN that same value
     distributes vertically, and this row can be taller than its content,
     so it opened a gap between the paragraph and the photograph. */
  .mission__row{ flex-direction:column; align-items:stretch; justify-content:flex-start;
    gap:20px; padding-block:clamp(28px,7vw,40px) }
  .mission__label{ width:auto }
  /* flex-basis is measured along the MAIN axis, so the desktop row's
     flex:0 1 420px — a 420px-wide text column — became a 420px-TALL one
     the moment this breakpoint turned the row into a column. The copy
     runs to about 250px, so the remaining ~170px sat as dead white space
     between the paragraph and the photograph. Sized by its content here. */
  .mission__copy{ max-width:none; flex:0 0 auto }
  .mission__media{ flex:0 0 auto; width:100%; height:clamp(220px,60vw,320px) }
}

/* ══════════════════════ responsive ══════════════════════ */
@media (max-width:820px){

  /* Two across on a phone, not one. At 440px each column is ~192px, which
     clears the widest figure ("2+ Years" sets to about 100px at the 26px
     clamp floor) and lets the longest caption wrap to two lines. Keeps the
     block reading as the table it is at every other width rather than as
     four stacked rows. No row-gap on purpose: each .stat's own hairline and
     padding separate the rows, the same way the 2x2 already works at
     1100px. */
  .stats__grid{ grid-template-columns:repeat(2,1fr) }


  /* Duplicates §3b's phone block and beats it by being later in the file —
     keep the two in step. */
  .value{ height:400vh }

  .offices__list{ flex:1 1 auto; width:100%; margin-left:0 }
  .office{ grid-template-columns:1fr auto; row-gap:3px; padding-block:14px }
  .office__n{ grid-area:1 / 1 }
  .office__x{ grid-area:1 / 2 }
  .office__c{ grid-area:2 / 1 }
  .office__a{ grid-area:3 / 1 }
  .offices__mask{ display:none }

  .stmt__notes{ display:flex; flex-direction:column; gap:22px }
  .stmt__notes p{ grid-column:auto }

  .radial{ height:230vh }
  .radial__t{ font-size:clamp(24px,7.4vw,44px) }

  .brands__t{ max-width:100% }

  /* Most of the row height is leading now (1.7 x 16px = 27px, already clear
     of the 24px floor), so this only tops the target up to the same ~33px
     it had when the leading was tight — it is no longer carrying the gap
     between one link and the next on its own. */
  .foot__v a{ padding-block:3px }
  .foot__v{ margin-block:-3px }
  /* the reference's phone footer: two columns, values BELOW their labels */
  .foot__cols{ display:grid; grid-template-columns:1fr 1fr;
    gap:clamp(28px,7vw,44px) 6%; font-size:16px }
  .foot__col{ display:block }
  .foot__k{ display:block; margin-bottom:.7em }
  /* Address and Our Policies are the two blocks a half column can't hold: the
     address sets three or four words to a line at half width, and the policy
     names are the longest links in the footer. Both take the full row —
     Company and Follow us, whose items are one or two words, keep the pair.
     The policy list then runs as its own two columns inside that full row,
     so eight long links don't become an eight-row stack. */
  .foot__col:first-child,
  .foot__col:last-child{ grid-column:1 / -1 }
  .foot__col:last-child .foot__v{
    display:grid; grid-template-columns:1fr 1fr;
    column-gap:6%; align-items:start;
  }
  /* was width:100% for the old full-bleed mark; the clamp holds it at 24px */
  .foot__markwrap{ margin-top:0; margin-bottom:clamp(28px,5vh,44px) }
}

.no-motion .radial__t--b{ display:none }
.no-motion .radial{ height:auto }
.no-motion .radial__pin{ position:relative; height:auto; padding-block:26vh }
.no-motion .brands{ height:auto }
.no-motion .brands__pin{ position:relative; height:auto; padding-block:20vh }

/* ═══════════════════════ 13 · contact page ═══════════════════════ */
/* The statement runs at display size so the page opens on the sentence
   itself rather than on a form. Centred, matching the reference. No fixed
   100svh: the block is as tall as the words need, and what follows starts
   right under them. */
/* ═══════════════════ 13b · contact: enquiry form ═══════════════════
   The reference's underline fields, inverted onto paper: hairlines and
   labels in ink instead of white, and a filled ink Submit where its was a
   white pill on black.

   The label sits ABOVE its own rule rather than floating as a placeholder.
   A placeholder disappears the moment someone types, which on a seven-field
   form means they lose track of which box they are in — and it is not an
   accessible name either, so the field would be unlabelled to a screen
   reader. Every input here has a real <label for>. */
.cform{
  background:var(--paper); color:var(--ink);
  padding:0 var(--pad) clamp(70px,9vw,140px);
}
.cform__form{ max-width:1180px; margin:0 auto }
/* Two columns, as the reference has, and the brief spans both. */
.cform__grid{
  display:grid; grid-template-columns:repeat(2,minmax(0,1fr));
  gap:clamp(28px,3.4vw,54px) clamp(24px,3.6vw,64px);
}
.cform__f{ margin:0; display:flex; flex-direction:column; gap:clamp(10px,1vw,16px) }
.cform__f--wide{ grid-column:1 / -1 }
.cform__l{
  font-family:var(--sans); font-weight:400; color:var(--ink);
  font-size:clamp(15px,1.25vw,20px); line-height:1.2; letter-spacing:-.01em;
}
.cform__opt{ color:var(--muted) }
/* A rule, not a box: border-bottom only, and a transparent background so
   the field reads as the reference's line rather than as an input chip.

   margin-top:auto — two fields sharing a row are not the same height. The
   file control carries a pill the browser paints, so Attachments stands
   ~16px taller than Current Designation beside it and their rules landed
   at two different heights. Labels stay locked to the top of the row; the
   auto margin drops each control to the bottom of it, so the rules in a
   row sit on one line. On one column a field is alone in its row, there is
   no spare height, and the margin does nothing. */
.cform__i{
  width:100%; margin:auto 0 0; padding:0 0 clamp(8px,.9vw,12px);
  background:none; border:0; border-bottom:1px solid rgba(13,13,13,.28);
  border-radius:0; color:var(--ink);
  font-family:var(--sans); font-weight:400;
  font-size:clamp(15px,1.15vw,18px); line-height:1.4;
  transition:border-color .25s var(--ease);
}
/* Focus is the rule going dark and solid — no ring. A box around a field
   that is drawn as a single line reads as a different control appearing;
   the line itself is the thing to change, so it thickens to 2px in full
   ink against the 1px hairline it sits next to. border-bottom-width is
   traded against padding-bottom so the text does not shift by the extra
   pixel when the field takes focus. */
.cform__i:focus,
.cform__i:focus-visible{
  outline:none;
  border-bottom-color:var(--ink); border-bottom-width:2px;
  padding-bottom:calc(clamp(8px,.9vw,12px) - 1px);
}
.cform__i--file:focus,
.cform__i--file:focus-visible{ padding-bottom:calc(clamp(10px,1.1vw,14px) - 1px) }
.cform__i--area{ resize:vertical; min-height:calc(3em * 1.4) }
/* The file button is the one control a browser paints itself; this keeps it
   from arriving as a grey chrome-blue chip in the middle of the line.

   line-height:1 is load-bearing. Without it the button inherits the input's
   1.4, so its line box is half a line taller than the text and the label
   rides low inside the pill. Its own line box plus symmetric padding is
   what actually centres it. */
.cform__i--file{ padding-bottom:clamp(10px,1.1vw,14px); font-size:clamp(14px,1vw,16px) }
.cform__i--file::file-selector-button{
  margin-right:12px; line-height:1;
  padding:calc(11px + var(--pill-shim)) 18px calc(11px - var(--pill-shim));
  background:none; border:1px solid rgba(13,13,13,.28); border-radius:999px;
  font-family:var(--sans); font-size:inherit; color:var(--ink); cursor:pointer;
  transition:background var(--ease), color var(--ease);
}
.cform__i--file::file-selector-button:hover{ background:var(--ink); color:var(--ink-i) }

/* Consent left, Submit right — the reference's footer row. */
.cform__foot{
  display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between;
  gap:clamp(20px,2.4vw,40px);
  margin-top:clamp(36px,4.4vw,64px);
}
.cform__consent{
  display:flex; align-items:flex-start; gap:12px;
  max-width:68ch; cursor:pointer;
  font-family:var(--sans); font-size:clamp(13px,1vw,15px);
  line-height:1.5; color:var(--muted);
}
/* accent-color rather than a hand-built box: it keeps the native control,
   its keyboard behaviour and its indeterminate state, and only restyles the
   tick's colour. */
.cform__check{
  flex:0 0 auto; width:16px; height:16px; margin:.2em 0 0;
  accent-color:var(--ink); cursor:pointer;
}
.cform__consent a{ color:var(--ink); text-decoration:underline; text-underline-offset:.22em }
/* Centred by flex with SYMMETRIC padding, unlike the site's other pills.
   Those are <a> elements carrying padding:18px/14px — a deliberate optical
   nudge downward. A <button> already centres its own content, so the same
   asymmetry lands the label 2px below the middle of the pill instead of
   correcting anything. */
.cform__submit{
  flex:0 0 auto;
  display:inline-flex; align-items:center; justify-content:center;
  padding:calc(16px + var(--pill-shim)) 44px calc(16px - var(--pill-shim)); border-radius:999px;
  background:var(--ink); border:1px solid var(--ink); color:var(--ink-i);
  font-family:var(--sans); font-weight:500; font-size:clamp(15px,1.15vw,18px);
  line-height:1; cursor:pointer;
  transition:background .35s var(--ease), color .35s var(--ease), border-color .35s var(--ease);
}
.cform__submit:hover,
.cform__submit:focus-visible{ background:var(--pill-hover-dark); border-color:var(--pill-hover-dark); color:var(--ink-i) }

/* ── form state: errors, status, thank-you ──────────────────
   The fields are rules rather than boxes, so an invalid one says so by
   turning its rule red and putting a line underneath — there is no box to
   tint. Never colour alone: the message is always there to read. */
.cform__i[aria-invalid="true"]{ border-bottom-color:#b3261e }
.cform__i[aria-invalid="true"]:focus{ border-bottom-color:#b3261e }
.cform__err{
  font-family:var(--sans); font-size:clamp(12px,.92vw,14px);
  line-height:1.4; color:#b3261e;
}
/* Sits under the footer row and carries what a field cannot: a failed send,
   or the consent box left unticked. */
.cform__status{
  margin:clamp(18px,2vw,28px) 0 0;
  font-family:var(--sans); font-size:clamp(13px,1vw,15px);
  line-height:1.5; color:#b3261e;
}
.cform__status a{ color:inherit; text-decoration:underline; text-underline-offset:.22em }
/* The honeypot. Not display:none — some bots skip what is hidden that way.
   Taken out of flow and parked off-screen instead, where a person cannot
   reach it (tabindex=-1 keeps it off the keyboard path too). */
.cform__bot{
  position:absolute; left:-9999px; width:1px; height:1px;
  opacity:0; pointer-events:none;
}
/* Takes the form's place, on the same measure, so the section does not jump
   to a different width when it swaps in. */
.cform__done{
  max-width:1180px; margin:0 auto;
  padding:clamp(40px,5vw,72px) 0;
}
.cform__done:focus{ outline:none }
.cform__done:focus-visible{ outline:2px solid var(--ink); outline-offset:8px }
.cform__done__t{
  margin:0 0 .4em;
  font-family:var(--serif); font-weight:400;
  font-size:clamp(30px,4vw,64px); line-height:1.1; letter-spacing:-.012em;
}
.cform__done__d{
  margin:0; max-width:52ch;
  font-family:var(--sans); font-size:clamp(15px,1.25vw,20px);
  line-height:1.5; color:var(--muted);
}
/* Sending: the button keeps its size so the footer row does not reflow when
   the label changes to "Sending...". */
.cform__submit[disabled]{ opacity:.55; cursor:default }
.cform__submit[disabled]:hover{ background:var(--ink); border-color:var(--ink) }

@media (max-width:820px){
  /* One column: two fields across a phone leaves each of them a few
     characters wide. */
  .cform__grid{ grid-template-columns:minmax(0,1fr) }
  .cform__foot{ flex-direction:column; align-items:stretch }
  .cform__submit{ width:100%; padding:calc(16px + var(--pill-shim)) 24px calc(16px - var(--pill-shim)) }
}

.cintro{
  background:var(--paper); text-align:center;
  padding:clamp(110px,15vh,210px) var(--pad) clamp(50px,7vw,110px);
}
.cintro__t{
  max-width:22ch; margin:0 auto clamp(22px,2.8vw,38px);
  font-family:var(--serif); font-weight:400;
  font-size:clamp(34px,6.6vw,112px); line-height:1.05; letter-spacing:-.014em;
}
.cintro__d{
  max-width:54ch; margin:0 auto;
  font-family:var(--sans); font-weight:400; color:var(--muted);
  font-size:clamp(16px,1.4vw,21px); line-height:1.5;
}

/* ── how to reach us ──────────────────────────────────────────────
   Three columns, but only the middle one holds content: the value is
   centred on the page and the label is pushed to the inside edge of the
   left column, which is what hangs it in the gutter beside the value
   instead of indenting the value to make room for it. align-items:baseline
   sets the label on the value's FIRST baseline, so a value that wraps still
   has its label level with its opening line. */
/* Two columns: the map on the left, the three label/value routes on the
   right. These were two stacked full-width sections (.creach over .cband),
   and both were worse for it — a value with a whole page to fill grew to
   76px, and a full-bleed map under it read as an unrelated band rather
   than as the answer to "where".

   The rows are FIRST in the markup and the map is placed back on the left
   by explicit grid-column rather than by reordering the source. That keeps
   the actionable routes ahead of an iframe for a screen reader, and it is
   also the order the section stacks in below the breakpoint — no `order`
   or `row-reverse`, which would have split those two apart. */
.cmeet{
  background:var(--paper);
  display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  align-items:center;
  gap:clamp(24px,3.4vw,56px);
  padding:clamp(32px,4vw,64px) var(--pad) clamp(70px,9vw,150px);
}
/* The map sits inside the page gutter like everything else. It ran to the
   left edge at first, carried over from when it was a full-bleed band, but
   in a column it just read as a missing margin. */
.cmeet__map{ grid-column:1; grid-row:1 }
.cmeet__map iframe{
  display:block; width:100%; border:0;
  height:clamp(320px,38vw,560px);
}
.cmeet__rows{
  grid-column:2; grid-row:1;
  padding-left:clamp(8px,2vw,40px);
}

/* Stacked, left-aligned — the label above its value rather than hanging in
   a gutter beside it. The hanging arrangement needed a full page width to
   read; in half of one the gutter eats the measure the values need. */
.creach__row{
  display:block;
  padding:clamp(12px,1.6vw,24px) 0;
}
.creach__k{
  display:block; margin-bottom:.5em;
  font-family:var(--sans); font-weight:400; color:var(--muted);
  font-size:var(--micro); line-height:1.3; letter-spacing:.03em;
}
/* Down from 76px at the top of the clamp: the longest value here,
   contact@moretasks.com, is 21 characters and has to fit a half column. */
.creach__v{
  margin:0;
  font-family:var(--serif); font-weight:400; color:var(--ink);
  font-size:clamp(22px,2.7vw,46px); line-height:1.14; letter-spacing:-.012em;
  overflow-wrap:anywhere;
}
/* Underline on hover only: at this size a permanent rule under every value
   would read as three heavy bars down the column. */
.creach__v a{
  color:inherit; text-decoration:none;
  transition:opacity .25s var(--ease);
}
.creach__v a:hover{ text-decoration:underline; text-underline-offset:.12em }

/* The address is three lines, not one short string, so it cannot take the
   size the other three values do — at 46px its longest line runs about 840px
   against a column of roughly 450. Its own scale keeps the designed line
   breaks intact instead of letting it wrap arbitrarily. */
.creach__row--addr .creach__v{
  font-size:clamp(16px,1.75vw,28px); line-height:1.35;
}

@media (max-width:820px){
  /* One column, and the markup order takes over: routes first, map under
     them. The map keeps running edge to edge. */
  .cmeet{
    grid-template-columns:minmax(0,1fr);
    gap:clamp(28px,7vw,44px);
    padding:clamp(20px,3vw,44px) var(--pad) clamp(60px,9vw,110px);
  }
  .cmeet__map,
  .cmeet__rows{ grid-column:1; grid-row:auto }
  /* the section carries the gutter at this width, so the rows drop the
     inset that separates them from the map in the two-column layout */
  .cmeet__rows{ padding-left:0 }
  .cmeet__map iframe{ height:clamp(260px,64vw,420px) }
}

.connect{
  background:var(--void); color:var(--ink-i);
  /* Flat 80px top and bottom — the clamp's own floor, now held at every
     width instead of opening out to 170px on a wide screen. Horizontal stays
     --pad so the block keeps the gutter every other section sits on. */
  padding:80px var(--pad);
}
.connect__head{ max-width:640px; margin-bottom:clamp(40px,5vw,64px) }
.connect__t{
  font-family:var(--serif); font-weight:400; margin:0 0 .5em;
  font-size:clamp(30px,4.2vw,60px); line-height:1.08; letter-spacing:-.012em;
}
.connect__d{
  font-family:var(--sans); font-size:clamp(15px,1.2vw,18px); color:var(--muted-i);
  line-height:1.5; margin:0;
}
.connect__list{ display:flex; flex-direction:column }
.connect__a{
  display:flex; align-items:center; justify-content:space-between;
  padding:clamp(18px,2.4vw,30px) 0; border-top:1px solid #2a2a2a;
  font-family:var(--serif); font-size:clamp(22px,3vw,44px); letter-spacing:-.01em;
  transition:opacity .25s var(--ease);
}
.connect__list .connect__a:last-child{ border-bottom:1px solid #2a2a2a }
.connect__a:hover{ opacity:.6 }
/* The mark sits with the name, not with the row: the row is a space-between
   pair and a third child would push the arrow off the right edge. */
.connect__n{ display:inline-flex; align-items:center; gap:.42em; min-width:0 }
.connect__i{
  /* .58em of a 22-44px serif line lands the glyph at roughly the cap height
     of the name beside it, so it reads as part of the word, not a bullet. */
  width:.58em; height:.58em; flex:none; fill:currentColor; opacity:.85;
}
.connect__x{
  font-family:var(--sans); font-size:max(14px,1.1vw); flex-shrink:0; margin-left:1em;
}

.no-motion .radial__t--b{ display:none }
.no-motion .radial{ height:auto }
.no-motion .radial__pin{ position:relative; height:auto; padding-block:26vh }
.no-motion .brands{ height:auto }
.no-motion .brands__pin{ position:relative; height:auto; padding-block:20vh }

@media (prefers-reduced-motion:reduce){
  *{ animation-duration:.001s !important; transition-duration:.001s !important }
}

/* ═══════════════════════ 14 · careers page ═══════════════════════
   Layout follows figma.com/careers — hero, why-us, values, life, benefits,
   location, then the grouped openings — but every part is built out of this
   site's own pieces: the About page's .ahero and .phil, the contact page's
   hanging label/value pairing, and the .connect row for the listings. */
.is-careers{ background:var(--void); color:var(--ink-i) }

/* The hero photograph — its own crop of the office rather than the About
   page's team photo, so the two heroes are not the same picture twice — is
   now an <img> in careers.html, not a background here. */

/* ═══════════════════════ 14b · culture page ═══════════════════════
   The page is the Careers page's furniture — .ahero, .phil, .vals, .scta —
   so it needs no layout of its own; only the dark page ground and its own
   hero crop, which is what keeps the four interior heroes from being the
   same photograph four times. */
.is-culture{ background:var(--void); color:var(--ink-i) }

/* ═══════════ 14c · culture: the infinite photo strip ═══════════
   Codegrid's infinite parallax slider (~/Downloads/gallery), in this site's
   type and monochrome. The demo's numbers where they carry the look — 350
   x 500 slides, 20px either side, the 2.25x image scale the parallax needs
   to have something to move inside — and this site's fluid sizing where
   they do not.

   No scroll hijack: the strip drifts, rides the page's scroll speed and
   takes a drag (main.js §18), but the wheel keeps scrolling the page. */
.gal{
  position:relative; background:var(--void); color:var(--ink-i);
  padding:clamp(56px,6vw,104px) 0 clamp(64px,7vw,120px);
  overflow:hidden;
}
.gal__head{
  display:flex; align-items:baseline; justify-content:space-between;
  gap:clamp(16px,2vw,32px);
  padding:0 var(--pad) clamp(30px,3.4vw,56px);
}
.gal__t{
  margin:0; font-family:var(--sans); font-weight:500;
  font-size:clamp(24px,2.4vw,40px); line-height:1.2; letter-spacing:-.02em;
}
.gal__n{
  flex:0 0 auto; color:var(--muted-i);
  font-family:var(--sans); font-weight:400;
  font-size:clamp(13px,1.1vw,17px); letter-spacing:.02em;
}
/* The viewport onto the track. grab/grabbing rather than a scrollbar: the
   gesture here is a drag, and the cursor is the only thing that says so. */
.gal__strip{
  position:relative; width:100%; overflow:hidden;
  cursor:grab; user-select:none; -webkit-user-select:none;
  touch-action:pan-y;                 /* vertical swipes still scroll the page */
}
.gal.is-dragging .gal__strip{ cursor:grabbing }
.gal__track{ display:flex; will-change:transform }
.gal__slide{
  flex:0 0 auto; position:relative;
  width:clamp(200px,23vw,350px); height:clamp(285px,33vw,500px);
  margin:0 clamp(8px,1.3vw,20px);
}
.gal__media{ width:100%; height:100%; overflow:hidden; background:var(--ph-dark) }
/* Oversized on purpose: the parallax in §18 slides the picture inside this
   frame, and at 1:1 it would expose an edge the moment it moved. */
.gal__media img{
  width:100%; height:100%; object-fit:cover; display:block;
  transform:scale(2.25); will-change:transform;
  -webkit-user-drag:none; user-drag:none;
}
/* Under the frame, not over the picture — the demo hangs it below the slide
   and so does this. It appears on hover, and only while the strip is near
   enough to still: reading a caption on something sliding past is a
   frustration, not a feature. */
.gal__cap{
  position:absolute; left:0; right:0; bottom:-1.75rem;
  display:flex; align-items:center; justify-content:space-between;
  pointer-events:none; opacity:0; transition:opacity .3s ease;
}
.gal__slide:hover .gal__cap{ opacity:1 }
.gal.is-dragging .gal__cap{ opacity:0 }
.gal__cap span{
  font-family:var(--sans); font-weight:500; color:var(--ink-i);
  font-size:clamp(11px,.85vw,13px); letter-spacing:.06em; text-transform:uppercase;
}
@media (max-width:820px){
  .gal{ padding:clamp(44px,11vh,72px) 0 clamp(52px,12vh,84px) }
  .gal__head{ padding:0 var(--pad) clamp(22px,6vw,34px) }
  .gal__t{ font-size:clamp(24px,7vw,34px) }
  /* the demo's own phone size */
  .gal__slide{ width:175px; height:250px; margin:0 10px }
  .gal__cap{ display:none }
}
/* Without the script the track is a plain horizontal scroll box: same
   photographs, same order, swipeable, just not infinite. */
.no-js .gal__strip,
.no-motion .gal__strip{ overflow-x:auto; cursor:auto }
.no-js .gal__media img,
.no-motion .gal__media img{ transform:none }

/* ═══════════ 14c-b · culture: what it feels like ═══════════
   Figma 206:2128, measured in its own 1470-wide frame (≈1:1 with this
   site's 1512 reference) and carried over as the vw each number works out
   to: heading 54/64 at -2px tracking, lede 20/30 in #6e6e6e on a 981
   measure, then a row of seven pictures — 302x454, 273x273 over 273x408,
   the 544x614 centre, and the mirror of those two on the right.

   The row comes to ~1758px, deliberately wider than the viewport: the
   outermost pictures run off both edges and the section clips them, which
   is what governorsmansion.org does and what stops the collage reading as a
   tidy centred grid. */
.feel{
  position:relative; overflow:hidden;
  background:var(--paper); color:var(--ink);
  /* 160px above the heading at 1512 — the reference's own figure, and well
     past the Figma frame's 60, which left the heading sitting on the edge
     of the section. Much less underneath: the collage ends on a picture,
     not on type, so it needs no breathing room below — and once the centre
     has opened to full height, any padding there reads as a white band
     under an otherwise edge-to-edge image.

     Raised from the reference's 160 to 200: the section above it is the
     black hero, and 160 of white read as a thin margin against that edge
     rather than as space the heading sits inside. */
  padding:clamp(112px,13.2vw,200px) 0 clamp(20px,2.1vw,32px);
}
.feel__head{
  /* The reference leaves 227px between its lede and the collage, but part
     of that is a call-to-action link this section does not have; 140 is the
     same air without the hole. */
  max-width:981px; margin:0 auto clamp(64px,9.3vw,140px);
  padding:0 var(--pad); text-align:center;
  display:flex; flex-direction:column; gap:clamp(10px,1.06vw,16px);
}
.feel__t{
  margin:0; font-family:var(--sans); font-weight:500; color:var(--ink);
  font-size:clamp(28px,3.6vw,55px); line-height:1.19; letter-spacing:-.037em;
  /* One line on desktop. The 981px head, less its 80px gutters, leaves 821
     of measure, which the heading is a shade too wide for — so it broke
     after "MoreTasker" and left one word hanging. nowrap lets it overrun
     the head's measure instead; the head is centred inside a full-width
     section, so the overrun is symmetrical and there is room for it at
     every width above the phone breakpoint. The lede keeps the 821 — it is
     a paragraph and wants the short line. */
  white-space:nowrap;
}
.feel__d{
  margin:0; color:var(--muted-i);
  font-family:var(--sans); font-weight:400;
  font-size:clamp(16px,1.33vw,20px); line-height:1.5;
}
/* A full-width flex row with justify-content:center, NOT width:max-content
   with auto margins — auto margins resolve to 0 once the element is wider
   than its container, so that version sat flush left and bled off one edge
   only. Centred flex overflows symmetrically, which is what puts a picture
   off each side. The items keep their true sizes (flex:0 0 auto below) and
   the section's overflow does the cropping. */
.feel__collage{
  display:flex; align-items:center; justify-content:center;
  gap:max(8px,1.06vw);
  width:100%;
}
/* No upper bound on these. The reference's row is a constant 1.16x the
   viewport, so the outer pictures are cut by the screen edges at every
   width; capping them in px (302/544/272, the figures at 1512) froze the
   row at 1756 and left a white margin either side on anything wider. Pure
   vw, with a floor for small screens, keeps the overrun proportional. */
.feel__col{ flex:0 0 auto }
.feel__col--tall{ width:max(120px,19.97vw); aspect-ratio:302/454 }
.feel__col--hero{ width:max(200px,35.98vw); aspect-ratio:544/680 }
.feel__col--pair{
  width:max(110px,17.99vw);
  display:flex; flex-direction:column; gap:max(8px,1.06vw);
}
.feel__im--sq{ aspect-ratio:1 }
.feel__im--rect{ aspect-ratio:273/408 }
/* NOT the img/hero/cell-* posters in here: those are 760x427 video stills
   with black letterbox bars baked into the file, and object-fit:cover keeps
   the bars because they are part of the picture's own height. */
.feel__col > img,
.feel__im{ display:block; width:100%; overflow:hidden; background:var(--ph) }
.feel__col--tall > img,
.feel__col--hero > img{ height:100%; object-fit:cover }
.feel__im > img{ width:100%; height:100%; object-fit:cover; display:block }

@media (max-width:820px){
  /* The outer pair goes. Five pictures across a phone would each be ~70px
     wide — at that size they stop being photographs and start being
     texture, and the row still would not fit. */
  /* Written long rather than as "X 0": the shorthand would put this new
     top figure underneath the collage as well, where the section wants
     almost nothing. */
  .feel{ padding:clamp(68px,15vw,104px) 0 clamp(36px,9vw,52px) }
  .feel__head{ margin-bottom:clamp(26px,7vw,40px) }
  .feel__t{ font-size:clamp(26px,7.2vw,38px); letter-spacing:-.028em; white-space:normal }
  .feel__d{ font-size:clamp(15px,4.1vw,18px) }
  .feel__col--tall{ display:none }
  .feel__col--hero{ width:46vw }
  .feel__col--pair{ width:25vw }
}

/* ═══════════ 14d · culture: beyond the workday ═══════════
   The stack mechanic is Codegrid's "BrandAppart sticky cards"
   (~/Downloads/Beyond the workday); the card itself is hildenkaira.fi's
   services block, which is what this was asked to look like. Measured on
   that site at 1512: a #181818 ground, a centred serif section heading, and
   a light card 878 x 483 carrying a serif title top-left, its index number
   top-right, body copy bottom-left and a PORTRAIT media panel on the right.
   The cards behind it show as edges of a different tone.

   Light cards, then — the opposite of every other stack on this site, and
   deliberately so, because that contrast against black is the whole look
   there. Theirs are lime/white/turquoise; ours run a paper-to-grey ramp,
   which does the same job (each card's edge separates from the one in
   front) inside a monochrome palette.

   Everything that moves is written by main.js §20. Nothing here animates. */
.beyond{
  position:relative; width:100%; height:100svh; overflow:hidden;
  background:var(--void); color:var(--ink-i);
  perspective:1000px;
}
/* Centred and serif, as the reference's "Our services" is — not the small
   uppercase margin label this section carried first. Below every card
   (z-index 2-5) rather than above them — the cards travel up over this
   heading as they animate, and it must stay tucked behind the (opaque)
   front card instead of bleeding through its text. */
.beyond__t{
  position:absolute; z-index:1; top:clamp(64px,9vh,110px); left:0; right:0;
  margin:0; text-align:center;
  /* sans, not the serif this and the gallery heading started as — the
     tracking goes a touch tighter with it, which large sans sizes need and
     the serif did not */
  font-family:var(--sans); font-weight:400; color:var(--ink-i);
  font-size:clamp(28px,3.4vw,56px); line-height:1.1; letter-spacing:-.02em;
}
/* Not inset:0. The cards are centred inside this box, so insetting it below
   the heading is what lets the card grow: centred in the whole viewport,
   every extra pixel of card height ate two of clearance under the heading,
   which capped the card at ~544 tall on a 850 screen. */
.beyond__stack{
  position:absolute;
  inset:clamp(96px,14vh,150px) 0 clamp(24px,4vh,48px);
}
.bcard{
  position:absolute; top:50%; left:50%;
  /* The reference's front card is 878 x 483 at 1512 — a 1.82 landscape.
     Holding that as width 58% / height 57% only works at that one viewport:
     the width caps at 880px while the height keeps tracking the viewport,
     so on a tall or wide screen the card creeps toward square (measured
     ~880 x 800 on a 1400-tall display). An aspect-ratio pins the SHAPE at
     every size, and the max-height only ever makes it wider than 1.82 —
     never taller — so it cannot go square again on a short viewport.

     Sized up from the reference's own 58%/880: 66% and 1020 here. The
     max-height moved with it, and the heading sits nearer the top edge to
     pay for that — the card is centred in the viewport, so every extra
     pixel of height costs half a pixel of clearance under the heading. */
  width:min(78%,1220px); aspect-ratio:1.82; height:auto; max-height:78vh;
  display:flex; flex-direction:column; justify-content:space-between;
  gap:clamp(14px,1.8vw,28px);
  padding:clamp(20px,2.2vw,34px);
  border-radius:2px;
  color:var(--ink);
  transform-origin:center bottom;
  /* No transform here: the centring is xPercent/yPercent in main.js §20, and
     a translate(-50%,-50%) in CSS does not sit underneath that — GSAP reads
     the computed matrix as its starting x/y and then adds its own -50%, so
     every card lands a full width left and a full height high. */
  will-change:transform;
}
/* Paper to grey, front to back: the ramp is what makes the edges of the
   cards behind read as separate cards rather than one thick slab. */
.bcard:nth-child(1){ background:#ffffff; z-index:5 }
.bcard:nth-child(2){ background:#fff2da; z-index:4 }
.bcard:nth-child(3){ background:#ffffff; z-index:3 }
.bcard:nth-child(4){ background:#d6fffc; z-index:2 }

.bcard__top{
  display:flex; align-items:flex-start;
  gap:clamp(16px,2vw,32px);
}
.bcard__t{
  margin:0; max-width:16ch;
  font-family:var(--serif); font-weight:500; color:var(--ink);
  font-size:clamp(30px,3.9vw,66px); line-height:1.04; letter-spacing:-.015em;
}
/* Copy on the left, the image panel hard right — the reference's lower
   half exactly. */
.bcard__bottom{
  flex:1 1 auto; min-height:0;
  display:flex; align-items:flex-end;
}
.bcard__copy{ flex:0 1 auto; max-width:42ch }
.bcard__k{
  margin:0 0 .5em; color:rgba(13,13,13,.55);
  font-family:var(--sans); font-weight:500;
  font-size:clamp(12px,.95vw,15px); letter-spacing:.08em; text-transform:uppercase;
}
.bcard__d{
  margin:0; color:var(--ink);
  font-family:var(--sans); font-weight:400;
  font-size:clamp(15px,1.3vw,21px); line-height:1.45;
}
/* Matches the Figma "Article (Culture)" component (node 206:2180) exactly:
   the panel (507 x 536, at 579,45) is pulled out of the copy row entirely
   and pinned by percentage against the card's own box (1120 x 615), so it
   sits flush against the heading's top and the paragraph's bottom at any
   card size — a flex margin-top guess can't track that as the card scales,
   since the heading's own two-line height doesn't scale in lockstep with
   card width the way these percentages (both axes of the same fixed-ratio
   box) do. */
.bcard__media{
  position:absolute;
  left:51.7%; top:7.32%; width:45.27%; height:87.15%;
  overflow:hidden; background:var(--ph-dark);
}
.bcard__media img{ width:100%; height:100%; object-fit:cover; display:block }

/* Short windows only. The card is centred, so height costs clearance under
   the heading at twice the rate: at 64% on a 700px-high window the two were
   5px apart. This pulls only the height back, so the card keeps its full
   width on a laptop and the shape simply widens. */
@media (max-height:780px){
  .bcard{ width:min(70%,1040px); max-height:68vh }
}

@media (max-width:1000px){
  /* The card is centred in the viewport, so its top is (100% - height)/2 —
     at 74% that put it under the heading on a phone. 68% and a heading
     nearer the top edge leave the two clear of each other. */
  .beyond__t{ font-size:clamp(26px,7vw,38px); top:clamp(44px,6vh,64px) }
  .bcard{
    /* upright on a phone, so the ratio above is released here */
    width:calc(100% - 2.5rem); aspect-ratio:auto; height:68%; max-height:none;
    padding:clamp(18px,4.6vw,26px);
  }
  .bcard__t{ font-size:clamp(24px,6.4vw,34px); max-width:none }
  /* The media drops below the copy here — side by side inside a phone-width
     card leaves the paragraph a four-word measure. */
  .bcard__bottom{ flex-direction:column; align-items:stretch; gap:clamp(14px,3.4vw,20px) }
  .bcard__copy{ max-width:none }
  .bcard__media{
    position:static;
    width:100%; height:auto; aspect-ratio:auto; flex:1 1 0; min-height:0;
  }
}

/* Without the script there is nothing to pin and nothing to throw, so the
   stack becomes four cards in normal flow — same copy, same photographs,
   read top to bottom. (main.js returns at §5 under reduced motion, so this
   covers that case too.) */
.no-js .beyond,
.no-motion .beyond{ height:auto; overflow:visible; padding:clamp(72px,9vh,120px) var(--pad) }
.no-js .beyond__t,
.no-motion .beyond__t{ position:static; margin-bottom:clamp(24px,4vh,48px) }
.no-js .beyond__stack,
.no-motion .beyond__stack{
  position:static; display:flex; flex-direction:column; gap:clamp(16px,2.4vh,28px);
}
.no-js .bcard,
.no-motion .bcard{
  position:static; width:100%; height:auto; min-height:340px; transform:none;
}
/* The default .bcard__media is positioned against .bcard's own box, so once
   .bcard above goes position:static it would escalate to positioning
   against .beyond instead — back to a normal flex sibling here, sized the
   way it was before the percentage-positioned rework. */
.no-js .bcard__media,
.no-motion .bcard__media{
  position:static; flex:0 0 auto;
  width:clamp(220px,35vw,460px); height:auto; aspect-ratio:507/536;
}

/* ═══════════ 14e · culture: the gallery ═══════════
   Figma 210:2225. The geometry lives in main.js §22, not here, because a
   frame's size depends on which SLOT it is currently in rather than on
   which frame it is — the photographs rotate through the slots, so a rule
   like ".the big one" has no fixed owner to attach to.

   What is here is the box they move inside. The row is the full width of
   the section and the frames are positioned against its centre, which is
   what lets the two end slots sit outside the viewport and be cut by it.

   The static fallback below is the whole section under no-js and reduced
   motion: the script never runs, so nothing would ever be sized or placed,
   and without it the section would be five zero-height figures. */
.crow{
  position:relative; width:100%; overflow:hidden;
  background:var(--paper); color:var(--ink);
  /* (869 - 437) / 2 in the design, above and below the centre frame. The
     heading did not exist in the design and now takes a share of the space
     above the row, so the top is pulled back by roughly its own height and
     margin — otherwise the section grows by the whole heading and the row
     stops sitting where the frame puts it. */
  padding:clamp(40px,8.2vw,150px) 0 clamp(56px,15vw,260px);
}
/* Centred and serif, the shape the other section headings on this page
   take. It sits in normal flow above the row, so the row's own box (which
   is written by the script, and only knows about the frames) is unaffected
   by it. */
.crow__t{
  margin:0 0 clamp(40px,5.5vw,88px);
  text-align:center;
  font-family:var(--sans); font-weight:400; color:var(--ink);
  font-size:clamp(28px,3.4vw,56px); line-height:1.1; letter-spacing:-.02em;
}

.crow__row{
  position:relative; width:100%;
  /* height is the centre frame's, written by the script — it is the tallest
     thing in the row and nothing else can establish the box */
}
.crow__f{
  position:absolute; left:50%; top:50%;
  margin:0; overflow:hidden; background:var(--ph);
  /* every frame is placed by transform from the row's centre; the script
     writes the translate, and the -50/-50 here is what makes its numbers
     centre-to-centre rather than corner-to-corner */
  transform:translate(-50%,-50%);
  will-change:transform,width,height;
}
.crow__f img{ width:100%; height:100%; object-fit:cover; display:block }

/* No script: the slots are never written, so fall back to a plain row of
   equal frames. Not the design's stepped composition — that composition only
   exists as a consequence of the animation — but a legible gallery. */
.no-js .crow__row,
.no-motion .crow__row{
  height:auto !important;
  display:flex; gap:clamp(8px,1vw,16px); justify-content:center;
  overflow-x:auto;
}
.no-js .crow__f,
.no-motion .crow__f{
  position:static; transform:none !important;
  flex:0 0 auto; width:clamp(220px,32vw,460px); aspect-ratio:699/437;
}

/* ═══════════════════════ 15 · services page ═══════════════════════ */
.is-services{ background:var(--void); color:var(--ink-i) }

/* ── closing CTA ──────────────────────────────────────────── */
.scta{
  background:var(--paper); color:var(--ink);
  padding:clamp(90px,12vw,180px) var(--pad);
  display:flex; justify-content:center; text-align:center;
}
.scta__row{ max-width:1200px }
.scta__t{
  font-family:var(--sans); font-weight:400; margin:0 0 .5em;
  font-size:clamp(34px,5vw,76px); line-height:1.08; letter-spacing:-.012em;
}
.scta__d{
  margin:0 auto clamp(28px,3.4vw,44px);
  font-family:var(--sans); font-size:clamp(15px,1.25vw,19px);
  line-height:1.5; color:var(--muted); max-width:60ch;
}
/* Symmetric padding on every pill. These carried padding:18px/14px, an
   optical nudge meant to lift type that sits high in a line-height:1 line
   box — but they are inline-flex with align-items:center, which already
   centres the line box, so the 4px only ever pushed the label below the
   middle of the pill. Measured 2px low before this. */
.scta__cta{
  display:inline-flex; align-items:center; justify-content:center;
  padding:calc(16px + var(--pill-shim)) 32px calc(16px - var(--pill-shim)); border-radius:999px; border:1px solid rgba(13,13,13,.8);
  font-family:var(--sans); font-weight:500; font-size:18px; line-height:1; color:var(--ink);
  text-decoration:none; transition:background .35s var(--ease), color .35s var(--ease), border-color .35s var(--ease);
}
.scta__cta:hover{ background:var(--ink); border-color:var(--ink); color:var(--ink-i) }

/* ── closing CTA, photo variant ─────────────────────────────
   Same .scta skeleton, full-bleed photo behind it with a dark scrim so the
   white copy stays legible, and the pill inverted to solid white/dark text
   to read against the photo instead of the plain paper background. */
.scta--photo{ position:relative; overflow:hidden; background:var(--void); color:var(--ink-i) }
.scta__bg{ position:absolute; inset:0; z-index:0 }
.scta__bg img{ display:block; width:100%; height:100%; object-fit:cover }
.scta--photo::after{
  content:""; position:absolute; inset:0; z-index:1;
  background:rgba(0,0,0,.5);
}
.scta--photo .scta__row{ position:relative; z-index:2 }
.scta--photo .scta__d{ color:rgba(255,255,255,.78) }
.scta--photo .scta__cta{
  background:#fff; color:#0d0d0d; border-color:#fff;
}
.scta--photo .scta__cta:hover,
.scta--photo .scta__cta:focus-visible{
  background:var(--pill-hover-light); color:var(--ink); border-color:var(--pill-hover-light);
}

/* Two-pill CTA row: real gap between them instead of relying on inline
   whitespace, and a secondary (outlined) variant so they read as
   primary/secondary rather than two identical actions. */
.scta__actions{
  display:flex; align-items:center; justify-content:center;
  gap:clamp(16px,2vw,28px); flex-wrap:wrap;
}
.scta__cta--secondary{ background:transparent; color:var(--ink); border-color:rgba(13,13,13,.8) }
.scta__cta--secondary:hover{ background:var(--ink); color:var(--ink-i) }

.scta--photo .scta__cta--secondary{ background:transparent; color:#fff; border-color:#fff }
.scta--photo .scta__cta--secondary:hover,
.scta--photo .scta__cta--secondary:focus-visible{
  background:#fff; color:var(--ink); border-color:#fff;
}

/* ── values and culture ───────────────────────────────────
   figma.com/careers' arrangement — heading, narrow intro, image-topped
   cards three to a row — in this site's colours and type.

   The row is TOP-aligned and every photo is boxed to 16/9 — the ratio the
   source files are already in — so images and headings line up across a row
   and nothing gets cropped away. */
.vals{
  background:var(--void); color:var(--ink-i);
  padding:clamp(56px,7vw,120px) var(--pad) clamp(90px,12vw,200px);
}
.vals__head{ max-width:60ch; margin-bottom:clamp(44px,6vw,96px) }
.vals__t{
  font-family:var(--serif); font-weight:400; margin:0 0 .5em;
  font-size:clamp(30px,4.2vw,60px); line-height:1.08; letter-spacing:-.012em;
}
.vals__lede{
  margin:0; max-width:52ch;
  font-family:var(--sans); font-weight:400; color:var(--muted-i);
  font-size:clamp(15px,1.25vw,19px); line-height:1.5;
}
.vals__grid{
  display:grid; grid-template-columns:repeat(3,1fr);
  align-items:start;             /* cards top-align; text length sets height */
  gap:clamp(40px,5vw,88px) clamp(24px,3.4vw,60px);
}
.vals__img{
  /* 16/9 is the native ratio of the source files, so cover crops nothing.
     At 4/3 it shaved a quarter off each side, which cut the outer columns
     off the tools sheet. */
  display:block; width:100%; aspect-ratio:16/9; object-fit:cover;
  margin-bottom:clamp(16px,1.8vw,28px);
  background:var(--ph-dark);
}
.vals__k{
  margin:0 0 .6em;
  font-family:var(--sans); font-weight:400; color:var(--ink-i);
  font-size:clamp(19px,1.9vw,28px); line-height:1.2; letter-spacing:-.012em;
}
.vals__d{
  margin:0; font-family:var(--sans); color:#d1cccc;
  font-size:clamp(14px,1.15vw,17px); line-height:1.5;
}

/* ── life here: photo left, copy right ───────────────────── */
.clife{
  background:var(--void); color:var(--ink-i);
  display:flex; align-items:stretch; gap:clamp(24px,4vw,72px);
  padding:0 var(--pad) clamp(90px,12vw,200px);
}
.clife__media{
  flex:0 0 52%; position:relative; overflow:hidden;
  min-height:clamp(320px,44vw,620px); background:var(--ph-dark);
}
.ph__fill--life{
  background:var(--ph) url("../img/podcast.png") center/cover no-repeat;
}
.clife__copy{
  flex:1 1 0; min-width:0;
  display:flex; flex-direction:column; justify-content:flex-end;
  padding-bottom:clamp(6px,1vw,18px);
}
.clife__t{
  font-family:var(--serif); font-weight:400; margin:0 0 .5em;
  font-size:clamp(28px,3.6vw,52px); line-height:1.08; letter-spacing:-.012em;
}
.clife__d{
  margin:0; max-width:38ch;
  font-family:var(--sans); font-size:clamp(15px,1.2vw,18px);
  line-height:1.5; color:var(--muted-i);
}

/* ── location: the contact page's hanging label/value ─────── */
.cloc{
  background:var(--paper); color:var(--ink);
  display:grid; grid-template-columns:1fr auto 1fr;
  column-gap:clamp(18px,3vw,56px); align-items:baseline;
  padding:0 var(--pad) clamp(90px,11vw,180px);
}
.cloc__k{
  grid-column:1; justify-self:start;
  font-family:var(--sans); color:var(--muted);
  font-size:var(--micro); line-height:1.3; letter-spacing:.03em;
}
.cloc__v{
  grid-column:2; margin:0; text-align:center;
  font-family:var(--serif); font-weight:400; color:var(--ink);
  font-size:clamp(22px,3vw,48px); line-height:1.18; letter-spacing:-.012em;
}

/* ── the openings ─────────────────────────────────────────── */
.jobs{
  background:var(--paper); color:var(--ink);
  padding:clamp(80px,11vw,180px) var(--pad) clamp(60px,8vw,120px);
  /* the header is fixed, so an anchor jump has to clear it */
  scroll-margin-top:clamp(56px,7vw,92px);
}
.jobs__head{ max-width:640px; margin-bottom:clamp(40px,5vw,72px) }
.jobs__t{
  font-family:var(--serif); font-weight:400; margin:0 0 .5em;
  font-size:clamp(34px,5.4vw,88px); line-height:1.04; letter-spacing:-.014em;
}
.jobs__d{
  margin:0; font-family:var(--sans); color:var(--muted);
  font-size:clamp(15px,1.2vw,18px); line-height:1.5;
}
/* ── search + team filters ────────────────────────────────
   Only rendered under .js: without the script they would be dead furniture
   in front of a list that reads perfectly well on its own. */
.jobf{ display:none }
.js .jobf{
  display:flex; flex-wrap:wrap; align-items:center;
  gap:clamp(14px,1.6vw,22px) clamp(16px,2vw,28px);
  margin-bottom:clamp(30px,4vw,64px);
}
/* A ruled field rather than a boxed one — the same hairline the job rows
   and the old form inputs use, so it belongs to this page. */
.jobf__search{ flex:1 1 240px; max-width:340px; display:block }
.jobf__input{
  width:100%; border:0; border-bottom:1px solid var(--ink); border-radius:0;
  background:none; padding:.55em 0; outline:none;
  font-family:var(--sans); font-size:clamp(14px,1.05vw,16px); color:var(--ink);
  -webkit-appearance:none; appearance:none;
}
.jobf__input::placeholder{ color:var(--muted) }
.jobf__input:focus-visible{ border-bottom-width:2px; padding-bottom:calc(.55em - 1px) }

.jobf__chips{ display:flex; flex-wrap:wrap; gap:8px }
/* Same pill as the values sound toggle, inverted for light ground. */
.jobf__chip{
  display:inline-flex; align-items:flex-end; gap:.5em;
  padding:.55em 1em; border:1px solid #dcdcdc; border-radius:999px;
  background:none; color:var(--ink); cursor:pointer;
  font-family:var(--sans); font-size:var(--micro); letter-spacing:.03em;
  text-transform:uppercase; line-height:1.3; white-space:nowrap;
  transition:background .2s var(--ease), border-color .2s var(--ease), color .2s var(--ease);
}
.jobf__chip:hover{ border-color:var(--ink) }
.jobf__chip.is-on{ background:var(--ink); border-color:var(--ink); color:var(--paper) }
.jobf__n{ font-size:.86em; opacity:.5 }
/* A team with nothing left after a search is still worth showing as a
   disabled option — hiding chips would make the row jump on every keystroke. */
.jobf__chip[disabled]{ opacity:.32; cursor:default }
.jobf__chip[disabled]:hover{ border-color:#dcdcdc }

.jobf__count{
  margin:0 0 0 auto; flex-shrink:0;
  font-family:var(--sans); font-size:var(--micro); letter-spacing:.03em;
  text-transform:uppercase; color:var(--muted);
}

.jobs__none{
  margin:0; padding:clamp(24px,3vw,44px) 0; border-top:1px solid #e6e6e6;
  font-family:var(--sans); font-size:clamp(15px,1.2vw,18px); color:var(--muted);
}
.jobs__none a{ color:var(--ink) }

.jobs__group + .jobs__group{ margin-top:clamp(44px,5.5vw,88px) }
.jobs__k{
  margin:0 0 clamp(8px,1vw,16px);
  font-family:var(--sans); font-weight:400; color:var(--ink);
  font-size:clamp(20px,2.2vw,32px); line-height:1.2; letter-spacing:-.012em;
}
.jobs__list{ display:flex; flex-direction:column }
/* The .connect row, inverted for light ground: title left, meta right, rule
   above, and the whole row is the target rather than just the words. */
.job{
  display:flex; align-items:baseline; justify-content:space-between; gap:1.5em;
  padding:clamp(15px,1.9vw,26px) 0; border-top:1px solid #e6e6e6;
  color:var(--ink); text-decoration:none;
  transition:opacity .25s var(--ease);
}
.jobs__list .job:last-child{ border-bottom:1px solid #e6e6e6 }
.job:hover{ opacity:.55 }
.job__t{
  font-family:var(--serif); font-weight:400;
  font-size:clamp(18px,1.9vw,30px); line-height:1.2; letter-spacing:-.008em;
}
.job__meta{
  flex-shrink:0; display:flex; align-items:baseline; gap:clamp(10px,1.4vw,26px);
  font-family:var(--sans); font-size:var(--micro); color:var(--muted);
  letter-spacing:.03em; text-transform:uppercase;
}
.job__x{ font-size:max(13px,1vw); color:var(--ink) }

/* ── open application ─────────────────────────────────────── */
/* Sized for its actual content: one label + one email line, not a full
   section of copy. The old 110/180 was section-scale padding around a
   ~90px row, so the band ran to 376px with nothing in most of it — and the
   bottom half of that sat directly on top of the footer, which brings its
   own opening space. Still bottom-heavy, just not by 70px. */
.capply{
  background:var(--paper); color:var(--ink);
  padding:clamp(36px,4.6vw,72px) var(--pad) clamp(44px,5.4vw,84px);
}
.capply__row{
  display:grid; grid-template-columns:1fr auto 1fr;
  column-gap:clamp(18px,3vw,56px); align-items:baseline;
}
.capply__k{
  grid-column:1; justify-self:end; text-align:right;
  font-family:var(--sans); color:var(--muted);
  font-size:var(--micro); line-height:1.3; letter-spacing:.03em;
}
.capply__v{
  grid-column:2; margin:0; text-align:center;
  font-family:var(--serif); font-weight:400;
  font-size:clamp(26px,4.6vw,76px); line-height:1.14; letter-spacing:-.012em;
}
.capply__v a{ color:inherit; text-decoration:none }
.capply__v a:hover{ text-decoration:underline; text-underline-offset:.12em }

@media (max-width:820px){
  /* Two up on tablet, one on phone — the stagger still reads at two. */
  .vals__grid{ grid-template-columns:repeat(2,1fr) }
  .scta__t br{ display:none }
  .clife{ flex-direction:column }
  .clife__media{ flex:0 0 auto; min-height:58vw }
  .cloc, .capply__row{ grid-template-columns:1fr; row-gap:.35em }
  .cloc__k, .capply__k{ grid-column:1; justify-self:start; text-align:left }
  .cloc__v, .capply__v{ grid-column:1; text-align:left }
  /* The meta drops under the title rather than squeezing it to nothing. */
  .job{ flex-direction:column; align-items:flex-start; gap:.5em }
  .jobf__search{ max-width:none; flex-basis:100% }
  .jobf__count{ margin-left:0 }
}
@media (max-width:560px){
  .vals__grid{ grid-template-columns:1fr }
}

/* ═══════════════════ 15 · services: the four capabilities ═══════════════════
   Figma "MoreTasks Website" 69:35 / 69:40 / 69:45 / 69:50. The frame is
   1440x1024: copy at x=61 (4.24vw) on a 520px column (36vw), title 52px
   Medium at -1.04px tracking, caption 24px in rgba(110,110,110,.93) — which
   is this sheet's --muted-i — and the photograph pinned from x=704 (48.9%)
   to the right edge.

   One pinned viewport carries all four. The photographs are stacked in DOM
   order and the incoming one is revealed by opening its clip-path inset
   downwards, scrubbed to scroll (see main.js) — a hard horizontal edge
   travelling down the frame rather than a dissolve. */
/* (stages + 1) * 100vh, where the stages are the opening statement plus the
   six services — eight. The pin eats one viewport and each stage wants a
   hold and a wipe. main.js counts the pairs it finds, so only this
   multiplier needs touching if the count changes. */
.svc{
  position:relative; height:calc(8 * 100vh);
  background:var(--void); color:var(--ink-i);
  /* The subtext in this section only: --muted-i (#6e6e6e) is the site's grey
     on black, but here the lede and the service descriptions are the body of
     a full-bleed panel rather than a caption under something, and at that
     size and length the darker grey reads as switched off. Named once and
     used by both, so the two stay the same colour. */
  --svc-sub:#d1cccc;
}
.svc__pin{
  position:sticky; top:0; height:100svh; overflow:hidden;
  background:var(--void);
}

/* Right of 48.9%, bleeding to the top, right and bottom edges. */
.svc__media{
  position:absolute; inset:0 0 0 48.9%;
  overflow:hidden; background:var(--ph-dark);
}
/* contain, not cover: the stand-in photography isn't shot for this panel's
   aspect ratio, and cover was cropping subjects out of frame. The panel's
   own --ph-dark background (above) shows through as letterboxing. */
.svc__media img{ width:100%; height:100%; object-fit:contain; display:block }
/* The six vertical service photographs (data-i 2-7) ARE shot for this
   panel, unlike the #1 stand-in above, so cover fills the frame edge to
   edge instead of leaving grey letterboxing down the sides. */
.svc__media:not([data-i="1"]) img{ object-fit:cover }
/* ALL of them parked closed, not just the ones after the first: the strip
   opens on the statement over a black viewport, and the first photograph
   wipes in from there. Gated on .js (set inline in <head>) so that without
   the script they simply stack rather than the page painting the last
   photograph and then snapping. */
.js .svc__media{ clip-path:inset(100% 0% 0% 0%) }

/* The left column: section heading on top, the cycling service copy under
   it, the pair centred together. Figma puts the service alone at the
   vertical centre, but the heading has to live somewhere and stranding it
   at the top of the viewport left a screen of black between the two. */
.svc__panel{
  position:absolute; inset:0 51.1% 0 0; z-index:2;
  display:flex; flex-direction:column; justify-content:center;
  padding:0 clamp(20px,4.24vw,61px);
}
/* The opening statement, Figma 69:132: 92px Medium at -1.84px on a 740px
   column. It runs wider than the panel on purpose — at this stage every
   photograph is still closed, so the extra width is over black. */
.svc__copy--intro{ width:min(51.4vw,740px) }
.svc__it{
  margin:0 0 .3em;
  font-family:var(--sans); font-weight:500; color:var(--ink-i);
  font-size:clamp(30px,6.4vw,92px); line-height:1.1; letter-spacing:-.02em;
}
.svc__id{
  margin:0;
  font-family:var(--sans); font-weight:400; color:var(--svc-sub);
  font-size:clamp(14px,1.67vw,24px); line-height:1.34;
}
/* All six in one grid cell: they stack, and the box takes the height of the
   longest one, so nothing below shifts as they cycle. */
.svc__stack{ display:grid }
.svc__copy{
  grid-area:1/1;
  display:flex; flex-direction:column; gap:clamp(10px,1.1vw,16px);
}
.js .svc__stack .svc__copy + .svc__copy{ opacity:0; visibility:hidden }
.svc__t{
  margin:0; font-family:var(--sans); font-weight:500; color:var(--ink-i);
  font-size:clamp(30px,3.6vw,52px); line-height:1.1; letter-spacing:-.02em;
}
.svc__d{
  margin:0; max-width:min(34vw,500px);
  font-family:var(--sans); font-weight:400; color:var(--svc-sub);
  font-size:clamp(15px,1.67vw,24px); line-height:1.34;
}
/* Figma 182:1391: #262626 pill, fully rounded, 20px Medium label, 10/16
   padding — scaled by 0.8249 (the frame is 1833 wide against this site's
   1512 reference). align-self:flex-start so the pill is only as wide as its
   label; the copy block is a stretch-aligned flex column, which would
   otherwise pull it the full width of the panel. The 24px gap the frame
   puts between the lede and the button is margin-top here rather than the
   column's gap, which the heading and lede share at a tighter 16px. */
.svc__cta{
  align-self:flex-start; margin-top:clamp(12px,1.32vw,20px);
  display:inline-flex; align-items:center; justify-content:center;
  padding:calc(clamp(7px,.55vw,10px) + var(--pill-shim)) clamp(12px,.88vw,16px) calc(clamp(7px,.55vw,10px) - var(--pill-shim));
  border-radius:999px; background:#262626; color:var(--ink-i);
  font-family:var(--sans); font-weight:500; text-decoration:none;
  font-size:clamp(14px,1.1vw,17px); line-height:1.5; white-space:nowrap;
  transition:background-color .25s ease;
}
.svc__cta:hover,
.svc__cta:focus-visible{ background:var(--pill-hover-dark) }
.no-motion .svc__cta{ transition:none }

/* Horizontal ticks, right-aligned, the current one twice as long — the
   reference design, replacing the upright 4x30 segments this carried from
   Figma 182:1468.

   Anchored by its RIGHT edge rather than its left. The ticks are the thing
   that marks the seam between the black panel and the photograph (which
   starts at 48.9%), and only a fixed right edge keeps that mark still: with
   a left anchor the lit tick would grow rightward across the seam and the
   line the ticks make would wander every time the stage changed. Right
   edge parked at the seam less the frame's own 47px, so the growth happens
   leftward into the black.

   3px tall, and it stays 3px at every width — a scaled hairline lands on
   half pixels and turns grey. Colours stay the panel's (#747472 against
   #b7b7b5) rather than the reference's black-on-paper, because this sits on
   black. */
.svc__dots{
  position:absolute; z-index:3;
  right:calc(51.1% + clamp(20px,2.6vw,39px));
  top:50%; transform:translateY(-50%);
  display:flex; flex-direction:column; align-items:flex-end;
  gap:clamp(16px,2.6vw,38px);
  pointer-events:none;
  /* Absent on the opening statement and faded in with the first
     photograph. Its whole job is to mark the seam between the black panel
     and the picture, and on the opening stage every photograph is still
     closed — so there is no seam, and a column of segments in the middle of
     an all-black viewport reads as an artefact next to the heading. Starts
     at 0 rather than being switched off by JS, so it cannot flash on before
     the sequence has taken over. */
  opacity:0; transition:opacity .4s ease;
}
.svc__dots.is-live{ opacity:1 }
.no-motion .svc__dots{ transition:none }
.svc__dot{
  display:block; width:clamp(14px,1.5vw,22px); height:3px;
  border-radius:2px; background:#747472;
  transition:width .35s var(--ease), background-color .3s ease;
}
.svc__dot.is-on{ width:clamp(28px,3vw,44px); background:#b7b7b5 }
/* Nothing drives it without the sequence — no JS, reduced motion, or the
   phone layout below, where the captions are static blocks. */
.no-js .svc__dots,
.no-motion .svc__dots{ display:none }
.no-motion .svc__dot{ transition:none }

@media (max-width:820px){
  /* No pin under this breakpoint (main.js skips the sequence), so the four
     read as four normal blocks: caption over its photograph. */
  .svc{ height:auto }
  .svc__pin{ position:relative; height:auto; overflow:visible; display:flex; flex-direction:column }
  .svc__media{
    position:relative; inset:auto; order:2;
    aspect-ratio:4/3; clip-path:none;
  }
  /* Same selector as the desktop rule that parks them closed, or that one
     wins on specificity and the photographs stay clipped away entirely. */
  .js .svc__media{ clip-path:none }
  /* display:contents dissolves the panel and the stack, so the heading and
     the six captions become flex children of .svc__pin alongside the
     photographs and can be ordered against them. */
  .svc__panel, .svc__stack{
    display:contents;
  }
  .svc__copy--intro{
    width:auto; order:0;
    padding:clamp(56px,13vw,90px) var(--pad) clamp(10px,3vw,20px);
  }
  .svc__copy{
    position:relative; width:auto;
    padding:clamp(28px,7vw,44px) var(--pad) clamp(20px,5vw,30px);
  }
  .js .svc__stack .svc__copy + .svc__copy{ opacity:1; visibility:visible }
  .svc__d{ max-width:44ch }
  /* The pill stays — it is the only way into each service from here — but
     the indicator goes: there is no pinned sequence at this width for it to
     indicate, and .svc__panel is display:contents, which would leave it
     positioned against the section instead of the panel. */
  .svc__dots{ display:none }
  .svc__cta{
    margin-top:clamp(14px,4vw,20px);
    padding:10px 16px; font-size:clamp(14px,4vw,16px);
  }
  /* Ordered by data-i, not by position: with the panel dissolved the flat
     child list is six photographs, the heading, then six captions, and any
     positional rule would have to be rewritten every time one is added. */
  .svc__copy[data-i="1"]{ order:1 }   .svc__media[data-i="1"]{ order:2 }
  .svc__copy[data-i="2"]{ order:3 }   .svc__media[data-i="2"]{ order:4 }
  .svc__copy[data-i="3"]{ order:5 }   .svc__media[data-i="3"]{ order:6 }
  .svc__copy[data-i="4"]{ order:7 }   .svc__media[data-i="4"]{ order:8 }
  .svc__copy[data-i="5"]{ order:9 }   .svc__media[data-i="5"]{ order:10 }
  .svc__copy[data-i="6"]{ order:11 }  .svc__media[data-i="6"]{ order:12 }
  .svc__copy[data-i="7"]{ order:13 }  .svc__media[data-i="7"]{ order:14 }
}

/* ═══════════════ 14c · services: what makes us different ═══════════════
   Figma 69:112 (1440x1024). Heading 64px Medium at -1.28px on a 620px
   column from x=84; the list column is 542px wide centred on x=1115, items
   roughly 240px apart, the active one sharp and the rest at 40% behind a
   2px blur.

   One pinned viewport: the list is static and only the focus moves down it
   as you scroll, which is what the frame shows — three items stacked with
   one of them clear. */
.diff{
  position:relative; height:400svh;
  background:var(--paper); color:var(--ink);
}
.diff__pin{
  position:sticky; top:0; height:100svh; overflow:hidden;
  background:var(--paper);
}

.diff__head{
  position:absolute; left:clamp(20px,5.83vw,84px); width:min(43vw,620px);
  top:25.4%; transform:translateY(-50%);
}
.diff__ht{
  margin:0 0 .32em;
  font-family:var(--sans); font-weight:500; color:var(--ink);
  font-size:clamp(28px,4.44vw,64px); line-height:1.12; letter-spacing:-.02em;
}
.diff__hd{
  margin:0;
  font-family:var(--sans); font-weight:400; color:var(--muted);
  font-size:clamp(14px,1.67vw,24px); line-height:1.34;
}

.diff__list{
  position:absolute; left:58.6%; width:min(37.6vw,542px);
  top:50%; transform:translateY(-50%);
  margin:0; padding:0; list-style:none;
  display:flex; flex-direction:column; gap:clamp(28px,6.4vh,84px);
}
/* On desktop main.js (§17) writes opacity / blur / offset every frame as a
   continuous function of scroll, so no transition here — it would only lag
   behind the wheel and fight the inline values. These rules are the resting
   state for the no-JavaScript and reduced-motion cases. */
.diff__i{
  opacity:.38; filter:blur(.8px);
}
.diff__i.is-on{ opacity:1; filter:blur(0) }
@media (min-width:821px) and (prefers-reduced-motion:no-preference){
  html.js .diff__i{ will-change:transform,opacity,filter }
}
.diff__t{
  margin:0 0 .26em;
  font-family:var(--sans); font-weight:500; color:var(--ink);
  font-size:clamp(26px,4.17vw,60px); line-height:1.2; letter-spacing:-.02em;
}
.diff__d{
  margin:0;
  font-family:var(--sans); font-weight:400; color:var(--ink);
  font-size:clamp(14px,1.67vw,24px); line-height:1.34;
}

@media (max-width:820px), (prefers-reduced-motion:reduce){
  /* No pin under this breakpoint (main.js skips it), so the heading and the
     list read as one column and every item is legible — a blurred item in a
     static list is just unreadable text. */
  .diff{ height:auto }
  .diff__pin{ position:relative; height:auto; overflow:visible }
  .diff__head{
    position:relative; left:auto; top:auto; transform:none; width:auto;
    padding:clamp(70px,16vw,110px) var(--pad) clamp(30px,8vw,48px);
  }
  .diff__list{
    position:relative; left:auto; top:auto; transform:none; width:auto;
    padding:0 var(--pad) clamp(70px,16vw,110px);
    gap:clamp(26px,7vw,44px);
  }
  .diff__i, .diff__i.is-on{ opacity:1; filter:none }
}

/* ═════════════════════ mobile finish pass ═════════════════════
   What used to lead this block — .menu-toggle, the focus ring, and the whole
   phone header (bar, toggle, full-screen menu) — now lives with the rest of
   the header, ported from the kit. The copy here duplicated it and WON,
   being later in the file: an is-solid bar with a #050505 serif menu, from
   before the redesign. */
@media (max-width:820px){
  :root{ --micro:12px }

  /* Keep important controls comfortably tappable. */
  .connect__a{ min-height:60px }
  .jobf__chip{ min-height:44px; padding:.7em 1em; align-items:center }
  .jobf__input{ min-height:44px }
  .job{ min-height:64px }

  /* Slightly calmer vertical rhythm on phones, particularly on the long
     About/Careers pages. */
  .statement{ padding-top:40px; padding-bottom:40px }
  /* Half the desktop 80px. The old override here ran to 96px, which left a
     phone padded MORE than a desktop once the base went flat. */
  .connect{ padding-top:40px; padding-bottom:40px }
  .mates__head{ padding-top:clamp(64px,18vw,96px) }
  .vals{ padding-bottom:clamp(70px,20vw,110px) }
  .clife{ padding-bottom:clamp(70px,20vw,110px) }
  .cintro{ padding-top:clamp(112px,30vw,140px) }

  /* Dynamic viewport units follow iOS Safari's expanding/collapsing chrome,
     preventing a stale strip at the top or bottom of full-screen scenes. */
  .hero__pin,
  .radial__pin,
  .value__pin,
  .brands__pin{ height:100dvh }

  /* .ahero is NOT in that list — 100svh is what broke the phone. See the
     phone hero block at the end of §20, which has to sit after .ahero__copy
     to win the cascade. */

  .foot__inner{
    padding-right:max(var(--pad),env(safe-area-inset-right));
    padding-bottom:max(12px,env(safe-area-inset-bottom));
    padding-left:max(var(--pad),env(safe-area-inset-left));
  }
}

@media (max-width:380px){
  :root{ --pad:16px }
  .hero__title{ font-size:clamp(27px,9vw,34px) }
  .hero__title .ln__i.serif{ font-size:clamp(20px,6.3vw,25px) }
  .ahero__t{ max-width:calc(100% - 32px) }
  .foot__cols{ column-gap:10% }
}

/* ═══════════════════════ 16 · legal pages ═══════════════════════ */
.legal{
  background:var(--paper); color:var(--ink);
  padding:calc(140px + env(safe-area-inset-top)) var(--pad) clamp(90px,12vw,160px);
}
.legal__inner{ max-width:760px; margin:0 auto }
.legal__t{
  font-family:var(--serif); font-weight:400; margin:0 0 .3em;
  font-size:clamp(32px,4.4vw,60px); line-height:1.08; letter-spacing:-.012em;
}
.legal__updated{
  margin:0 0 clamp(40px,5vw,64px);
  font-family:var(--sans); font-size:var(--micro); color:var(--muted);
  letter-spacing:.03em; text-transform:uppercase;
}
.legal__inner h2{
  font-family:var(--sans); font-weight:600; margin:1.8em 0 .6em;
  font-size:clamp(18px,1.6vw,24px); line-height:1.25; letter-spacing:-.006em;
}
.legal__inner h2:first-of-type{ margin-top:0 }
.legal__inner h3{
  font-family:var(--sans); font-weight:600; margin:1.4em 0 .5em;
  font-size:clamp(16px,1.3vw,19px); line-height:1.3;
}
.legal__inner p{
  margin:0 0 1.1em; font-family:var(--sans);
  font-size:clamp(15px,1.15vw,18px); line-height:1.6; color:var(--ink);
}
.legal__inner ul{
  margin:0 0 1.1em; padding-left:1.2em;
  font-family:var(--sans); font-size:clamp(15px,1.15vw,18px); line-height:1.6;
}
.legal__inner li{ margin-bottom:.6em }
.legal__inner li strong{ font-weight:600 }
.legal__inner a{
  color:inherit; text-decoration:underline;
  text-underline-offset:.2em; text-decoration-thickness:1px;
}

/* Cookie table (cookies.html): scrolls on its own rather than widening the
   column, per the fixed 760px .legal__inner measure. */
.legal__tablewrap{ overflow-x:auto; margin:0 0 1.1em; -webkit-overflow-scrolling:touch }
.legal__inner table{
  width:100%; min-width:520px; border-collapse:collapse;
  font-family:var(--sans); font-size:clamp(13px,1vw,16px); line-height:1.5;
}
.legal__inner th,
.legal__inner td{
  text-align:left; vertical-align:top; padding:.65em .9em;
  border-bottom:1px solid rgba(13,13,13,.12);
}
.legal__inner th{ font-weight:600; white-space:nowrap }
.legal__inner tr:last-child td{ border-bottom:0 }


/* ════════════ SERVICE SUBCATEGORY PAGES ════════════
   Ported from ~/Downloads/sub-category. One layout, seven pages: dark hero,
   the statement carrying that black down, a numbered "in practice" list,
   then the contact banner the Why Us page already brought in. */

/* Inverted for pages whose hero is dark, so the statement carries the black
   down instead of cutting to paper immediately under the photograph. Set in
   the sans to match the heading standing above it in the hero; the tracking
   follows the site's other sans display type rather than the serif's. */
.statement--void{ background:var(--void); color:var(--ink-i) }
.statement--void .statement__t{ font-family:var(--sans); letter-spacing:-.012em }


/* ═══════════ 2c · what this looks like in practice ═══════════
   arpeggio.framer.website's "what we do" list, less its right-hand column
   of deliverables and its row numbers: a centred heading, then one row per
   point with the title on the left and the sentence on the right behind a
   hairline. Rules run between rows only, so the list opens straight under
   the heading with no line above 01.

   The section is white, but it arrives the way the reference's does: the
   ground is black and the white is a disc hung just inside the top edge,
   which main.js scales up with the scroll. The clip is open at the top on
   purpose — the disc has to climb out through the black statement above so
   the white reads as a dome rising through the dark, the way the
   reference's does through its stats; clipped flush it would only ever
   show its lower half, an arch hanging off the edge. The sides are clipped
   with overflow-x:clip rather than clip-path, because clip-path only trims
   the paint and a three-thousand-pixel disc was still widening the page's
   scroll. With JavaScript off the disc never grows and the ground is plain
   white. */
.prac{
  position:relative; overflow-x:clip; clip-path:inset(-100vh 0 0 0);
  background:var(--paper); color:var(--ink);
  padding:clamp(90px,11vw,180px) var(--pad) clamp(80px,9vw,150px);
}
.js .prac{ background:var(--void) }
/* 500px natural with its top on the section's top edge, exactly as the
   reference places it, so the centre sits 250px in: small, the disc is
   still below the viewport when the section arrives and breaks the bottom
   edge as a dome rather than appearing as a whole circle. The scale
   main.js runs it to is worked out from the section's own size, so the
   bottom corners are always covered. */
.prac__disc{
  position:absolute; left:50%; top:0;
  width:500px; height:500px; margin:0 0 0 -250px;
  border-radius:50%; background:var(--paper);
  transform:scale(0); transform-origin:50% 50%;
  will-change:transform; pointer-events:none;
}
/* Centred and a step up from the row titles — it sits where the disc
   opens, so it is the first thing the white uncovers. */
.prac__t{
  position:relative; z-index:1;
  margin:0 auto clamp(64px,8vw,130px); max-width:16ch; text-align:center;
  font-family:var(--sans); font-weight:400;
  font-size:clamp(34px,4.2vw,64px); line-height:1.04; letter-spacing:-.022em;
}
.prac__list{ position:relative; z-index:1; list-style:none; margin:0; padding:0 }
/* The sentence column is a fixed share pinned to the right margin, so the
   row reads title-left, sentence-right with the middle open — with the
   reference's deliverables column gone, letting the sentence follow the
   title left the right third of every row empty and the list lopsided. */
.prac__row{
  display:grid; grid-template-columns:minmax(0,1fr) clamp(300px,40%,560px);
  gap:clamp(24px,4vw,72px); align-items:start;
  padding:clamp(40px,5vw,72px) 0;
}
.prac__row + .prac__row{ border-top:1px solid rgba(13,13,13,.14) }
.prac__rt{
  margin:0;
  font-family:var(--sans); font-weight:400;
  font-size:clamp(24px,2.9vw,46px); line-height:1.08; letter-spacing:-.02em;
}
/* The reference's trailing dash, "Branding —"; kept out of the copy so the
   titles stay clean for search and for whoever edits them next. It hangs
   off the LAST WORD's inner span rather than the heading, so it rides up
   inside that word's mask with the reveal instead of sitting alone at the
   end of an empty line while the words are still below their masks — and
   being inside the word's inline-block it cannot wrap away from it either.
   The space is the narrow NO-BREAK one (U+202F) for the same reason. The
   second rule is the no-script fallback, where no masks exist. */
.js .prac__rt .msk:last-child .msk__i::after{ content:"\202F\2014" }
html:not(.js) .prac__rt::after{ content:"\202F\2014" }
.prac__rd{
  margin:0;
  padding-left:clamp(16px,1.6vw,28px); border-left:1px solid rgba(13,13,13,.14);
  font-family:var(--sans); font-size:clamp(14px,1.15vw,18px);
  line-height:1.5; color:var(--muted);
}
/* Rows that run more than one sentence (e.g. ecommerce-digital-growth.html)
   wrap their .prac__rd paragraphs in this so they stay a single grid item —
   otherwise the grid's own auto-placement drops the second paragraph into
   the title's column on a new implicit row instead of stacking it under the
   first, and the row-gap that spacing needs would double as unwanted
   column-gap between title and sentence. */
.prac__rc{ display:flex; flex-direction:column; gap:clamp(16px,1.6vw,24px) }

@media (max-width:820px), (prefers-reduced-motion:reduce){
  /* The disc is a desktop flourish; here the section is simply white. */
  .js .prac{ background:var(--paper) }
  .prac__disc{ display:none }
}
@media (max-width:820px){
  .prac__t{ max-width:none }
  .prac__row{ grid-template-columns:1fr; gap:14px }
  .prac__rd{ padding-left:0; border-left:0 }
}

/* Each subcategory hero takes the picture of its own service, so the detail
   page reads as a continuation of the card the visitor clicked. Those seven
   photographs used to be background-images on this rule; they are <img> tags
   in the seven pages now. */


/* ════════════════ WHY US PAGE ════════════════
   Ported whole from ~/Downloads/why-us-section, which is a fork of this
   same stylesheet — so these four blocks are lifted rather than rewritten,
   and none of their selectors (.choose, .process, .cbanner, .ahero--lead)
   existed here before, which is what makes the lift safe. Renumbered 17-20
   to sit after this file's own sections. */

/* ═══════════════════ 17 · why-us: hero copy block ═══════════════════
   The About/Careers hero drops a bare headline into the bottom-left corner.
   Why Us adds a lede and a CTA under it, so the three are wrapped and the
   wrapper — not the h1 — takes the corner placement; otherwise each piece
   would need its own bottom offset and they would drift apart as the
   headline rewraps. */
.ahero__copy{
  position:absolute; left:var(--pad); right:var(--pad); z-index:3;
  bottom:calc(var(--pad) + .4vw);
  max-width:min(92vw,1100px);
}
.ahero--lead .ahero__t{ position:static; max-width:none }
.ahero__lede{
  margin:clamp(14px,1.4vw,22px) 0 0; max-width:min(86vw,60ch);
  font-family:var(--sans); font-size:clamp(15px,1.25vw,19px);
  line-height:1.5; color:rgba(255,255,255,.84);
}
/* Same pill as .scta__cta, inverted for dark photography. */
/* Symmetric padding on every pill. These carried padding:18px/14px, an
   optical nudge meant to lift type that sits high in a line-height:1 line
   box — but they are inline-flex with align-items:center, which already
   centres the line box, so the 4px only ever pushed the label below the
   middle of the pill. Measured 2px low before this. */
.ahero__cta{
  display:inline-flex; align-items:center; justify-content:center;
  margin-top:clamp(20px,2.3vw,32px);
  padding:calc(16px + var(--pill-shim)) 32px calc(16px - var(--pill-shim)); border-radius:999px; border:1px solid #fff;
  background:#fff; color:var(--ink);
  font-family:var(--sans); font-weight:500; font-size:18px; line-height:1;
  transition:background .35s var(--ease), color .35s var(--ease), border-color .35s var(--ease);
}
/* ═══════════════ 20a · the hero on a phone ═══════════════
   This block has to live AFTER .ahero__copy above, not up in §15's phone
   media query, because it overrides that rule at equal specificity and the
   later declaration is the one that wins. Put it there and the copy stays
   absolutely positioned; that is what left a grey band under the picture.

   A phone is a ~0.35 aspect box and these photographs are 1.4 to 1.8, so
   cropping one to fill the screen throws away four fifths of its width — on
   the team shot that is five faces out of thirty. So nothing is cropped
   here. The picture goes back into normal flow at its own ratio, and the
   section is as tall as the photo plus the copy beneath it.

   The fade belongs on the PICTURE, not on the copy below it. Putting it on
   the copy was the obvious move and it leaves a visible line: that block's
   height depends on how long the headline is, so the percentage at which its
   gradient turns solid lands wherever it lands — and it was still only about
   40% opaque where the photograph stopped. 60% of the picture was showing
   against pure black, which reads as an edge however soft the ramp is.

   Anchored to the figure instead, the ramp is measured against the photo's
   own height, so it can be made to arrive at solid #000 exactly where the
   photo ends. Below that the section's own ground is already #000, so there
   is nothing to blend — the two are literally the same colour at the join,
   and the picture dissolves into the page rather than stopping on it. The
   copy needs no background of its own at all; it just rides up into the part
   of the photograph that has already gone black. */
@media (max-width:820px){
  .ahero{ height:auto; min-height:0 }
  .ahero__media{ position:relative; inset:auto; background:#000 }
  .ahero__img{ height:auto; max-height:none }

  /* The film is the one thing here that DOES get cropped on a phone, and for
     the opposite reason to the stills above. A photograph left uncropped is
     still a photograph; 2.10:1 footage left uncropped is a 184px-tall strip
     at 390px wide, and the copy below rides ~66px up into it (--ahero-rise),
     which leaves barely a hundred clean pixels of picture. So the box is
     squared up to 16:10 and object-fit:cover takes it out of the width — the
     film stays full-bleed edge to edge, it just shows the middle three
     quarters of the frame. The cap comes off with the stills' for the same
     reason: nothing here is measured against the viewport. */
  .ahero--film .ahero__media{ --film-ar:16/10; max-height:none }

  /* Two ramps. The first is the top scrim, which keeps the white wordmark
     legible over a bright frame. The second starts a third of the way down
     the picture — early enough that it reads as the light falling off rather
     than as a band switching on — and is fully black by the last pixel. */
  .ahero .ph--shade::before{
    background:
      linear-gradient(to bottom, rgba(0,0,0,.34), rgba(0,0,0,0) 26%),
      linear-gradient(to bottom,
        rgba(0,0,0,0) 34%,
        rgba(0,0,0,.45) 62%,
        rgba(0,0,0,.85) 86%,
        #000 100%);
  }

  /* --ahero-rise is how far the copy climbs back up into the photograph. It
     is set against the blacked-out lower third above, so the type sits on
     solid colour while still overlapping the image box — which is what makes
     the two read as one piece instead of a picture with a caption under it.
     The 20px is the gap between the photo's bottom edge and the headline;
     because the padding already carries the rise, that is all that is left
     over, and it stays 20px whatever the rise is changed to. */
  .ahero__copy,
  .ahero:not(.ahero--lead) .ahero__t{
    --ahero-rise:clamp(64px,17vw,110px);
    position:relative; inset:auto; z-index:3;
    max-width:none; width:auto;
    margin-top:calc(-1 * var(--ahero-rise));
    padding:calc(var(--ahero-rise) + 20px) var(--pad) clamp(34px,9vw,52px);
  }
  .ahero__lede{ max-width:none }
}

/* Inverts rather than dimming — the pill is the only solid shape on the photo,
   so losing the fill on hover is the clearest possible state change. */
.ahero__cta:hover,
.ahero__cta:focus-visible{ background:var(--pill-hover-light); border-color:var(--pill-hover-light); color:var(--ink) }

/* ═════════════ 18 · why-us: why businesses choose MoreTasks ═════════════
   cowboy.com's full-bleed step block. Two moves share one sticky frame:

     enter   the stage rises inset by a margin and grows to full bleed,
             reaching it exactly as the pin catches — i.e. as the stage
             centres in the viewport
     held    four steps play inside the held frame, image and heading
             crossfading together, the tab row filling as the progress bar

   Everything animated here is driven from main.js §12c by writing --g on the
   stage and opacity/transform on the panels. The margins live in CSS so the
   inset is one clamp() rather than a number the script has to re-measure. */
/* White, like the .scta block that closes every page — the photography is the
   only dark thing here, and it earns that by being the thing that grows. */
.choose{ position:relative; background:var(--paper); color:var(--ink) }

.choose__head{
  padding:clamp(90px,12vw,180px) var(--pad) clamp(44px,5.4vw,80px);
  text-align:center;
}
/* .scta__t / .scta__d verbatim: this is the site's centred-heading pair, and a
   second set of numbers for the same job would only drift from it. */
.choose__t{
  margin:0 0 .5em;
  font-family:var(--sans); font-weight:400;
  font-size:clamp(34px,5vw,76px); line-height:1.08; letter-spacing:-.012em;
}
.choose__d{
  margin:0 auto; max-width:60ch;
  font-family:var(--sans); font-size:clamp(15px,1.25vw,19px);
  line-height:1.5; color:var(--muted);
}
/* Extra breathing room under the heading on desktop, where the .5em from
   .choose__t sits too close to the lede below it. Left untouched on phone,
   where the tighter stack already reads fine. */
@media (min-width:821px){
  .choose__t{ margin-bottom:.7em }
}
/* More air between the lede and the photo stage below it on desktop — the
   head's own bottom padding was too tight against the media section that
   follows. Phone keeps the original, tighter padding. */
@media (min-width:821px){
  .choose__head{ padding-bottom:clamp(80px,9vw,140px) }
}

/* The stage is inset on the way in, so what shows around it is this section's
   own ground — white, continuous with the head above it. */
.choose--media{ background:var(--paper) }

/* 100svh of pin + one viewport-ish of travel per step. */
.choose__scroll{ position:relative; height:325svh }
.choose__pin{ position:sticky; top:0; height:100svh }

/* --g: 0 = inset by the margin, 1 = full bleed. main.js scrubs it; the
   fallbacks below pin it at 1 so the stage is simply full width when the
   script never runs. */
.choose__stage{
  --g:0;
  --mx:clamp(16px,5.5vw,110px);
  --my:clamp(18px,5vh,74px);
  position:absolute;
  top:calc(var(--my) * (1 - var(--g)));
  bottom:calc(var(--my) * (1 - var(--g)));
  left:calc(var(--mx) * (1 - var(--g)));
  right:calc(var(--mx) * (1 - var(--g)));
  border-radius:calc(14px * (1 - var(--g)));
  overflow:hidden; background:#0b0b0b;
}

.choose__panels{ position:absolute; inset:0 }
.choose__panel{ position:absolute; inset:0; opacity:0 }
/* the one step that shows with scripting off */
.choose__panel.is-on{ opacity:1 }
.choose__img{
  position:absolute; inset:0; display:block;
  background:var(--ph-dark) center/cover no-repeat;
  transform:scale(1.06); will-change:transform;
}
.choose__panel::after{
  content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
  background:linear-gradient(to top, rgba(0,0,0,.74), rgba(0,0,0,.28) 44%, rgba(0,0,0,0) 74%);
}
/* These photos are portrait-composed (subjects' heads sit close to the top
   edge) but the stage is a wide letterbox, so a center crop was slicing
   heads off. Pulling background-position toward the top keeps the subject
   in frame and lets the crop eat into the empty space below instead. */
.choose__img--people{ background-image:url("../img/why-us/Experienced-Team.png"); background-position:center 12% }
.choose__img--process{ background-image:url("../img/why-us/quality-fa.png"); background-position:center 0% }
.choose__img--tech{ background-image:url("../img/why-us/Tech.png") }
.choose__img--flex{ background-image:url("../img/why-us/Flexible-support.png"); background-position:center 0% }
.choose__img--delivery{ background-image:url("../img/why-us/process-driven-delivery.webp"); background-position:center 25% }

/* Clears the tab row, which owns the bottom edge of the stage. */
.choose__cap{
  position:absolute; z-index:2;
  left:clamp(20px,3.4vw,56px); bottom:clamp(88px,11vh,132px);
  max-width:min(86%,640px);
}
.choose__ct{
  font-family:var(--sans); font-weight:400; color:#fff;
  font-size:clamp(26px,3.9vw,64px); line-height:1.02; letter-spacing:-.03em;
}
/* The site's lede size — the caption was set a step under it and read small
   against the picture. */
.choose__cd{
  margin:clamp(10px,1vw,16px) 0 0; max-width:60ch;
  font-size:clamp(16px,1.45vw,22px); line-height:1.5; color:rgba(255,255,255,.85);
}

.choose__tabs{
  position:absolute; left:0; right:0; bottom:0; z-index:3;
  /* five columns for five tabs. It was repeat(4,1fr) from when there were
     four steps, so the fifth wrapped onto a row of its own under the rest.
     align-items:start because at five across the longer names take a second
     line, and without it the short ones stretch to match. */
  display:grid; grid-template-columns:repeat(5,1fr); gap:clamp(12px,2vw,34px);
  align-items:start;
  padding:0 clamp(20px,3.4vw,56px) clamp(18px,2.2vw,30px);
}
.choose__tab{
  display:block; appearance:none; -webkit-appearance:none;
  background:none; border:0; padding:0; margin:0;
  font:inherit; text-align:left; color:#fff; cursor:pointer;
}
.choose__tab__bar{ position:relative; display:block; height:1px; background:rgba(255,255,255,.26); overflow:hidden }
.choose__tab__bar i{
  position:absolute; inset:0; background:#fff;
  transform:scaleX(0); transform-origin:left center;
}
.choose__tab__l{
  display:block; margin-top:clamp(9px,.9vw,14px);
  font-size:clamp(14px,1.18vw,18px); letter-spacing:.01em; color:rgba(255,255,255,.6);
  transition:color .45s var(--ease);
}
.choose__tab.is-on .choose__tab__l{ color:#fff }

/* On desktop the tab labels show in full ("Technology-Enabled Operations",
   "Flexible Support"...) and can wrap onto a second line at narrower desktop
   widths, growing the tab row taller than the clearance .choose__cap was
   given — the caption text then sits right on top of the animated progress
   line. Give the caption more headroom above the tab row here. */
@media (min-width:821px){
  .choose__cap{ bottom:clamp(112px,15vh,168px) }
}

/* ── phone: the same held frame, tightened ───────────────────
   The reference keeps this block whole on a phone — full-bleed picture,
   caption in the corner, the tab row still doing duty as the progress bar —
   so the mechanic stays and only the measurements change. Naming every step
   in a 375px row is what does not fit, so the row is four bars and the step
   you are on is the only one spelled out. */
@media (max-width:820px){
  .choose__stage{ --mx:clamp(12px,4vw,22px); --my:clamp(14px,3vh,30px) }
  .choose__cap{
    left:clamp(18px,5vw,26px); right:clamp(18px,5vw,26px);
    bottom:clamp(76px,13vh,104px); max-width:none;
  }
  .choose__ct{ font-size:clamp(28px,7.4vw,40px) }
  .choose__cd{ font-size:clamp(14px,3.8vw,16px); max-width:none }
  /* Room under the bars for one line of label. Left in its own column the
     name wraps to three lines and shoves the row about — "03.
     Technology-Enabled Operations" in a 78px column. */
  /* The five progress bars are hidden on a phone — the row of 1px rules at
     the bottom of the frame. The steps still advance on their own clock and
     still scrub with scroll, so nothing about the section stops working;
     what goes with the row is tap-to-jump, which was a 61px-wide target for
     a bar that reads as decoration at this size. Same treatment .no-motion
     already gives the row below. Everything under here is kept so putting
     the row back is one line. */
  .choose__tabs{
    display:none;
    gap:8px; align-items:start;
    padding:0 clamp(18px,5vw,26px) clamp(44px,11vw,54px);
  }
  .choose__tab__l{ display:none }
  /* With the label hidden the button is its 1px bar and nothing else, which
     leaves a 64x1px thing to hit with a thumb. The bar is the design, so it
     stays as it is and the *target* grows instead: a transparent overlay
     centred on the bar, 44px tall, which is the size a touch target is
     supposed to be. Absolutely positioned, so the row's geometry does not
     move a pixel. The tabs are side by side in a grid, so growing them
     vertically cannot make two of them overlap. */
  .choose__tab{ position:relative }
  .choose__tab::after{
    content:""; position:absolute; left:0; right:0; top:50%;
    height:44px; transform:translateY(-50%);
  }
  .choose__tab.is-on .choose__tab__l{
    display:block; position:absolute;
    left:clamp(18px,5vw,26px); right:clamp(18px,5vw,26px);
    bottom:clamp(14px,3.6vw,20px); margin-top:0;
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  }
}
/* Reduced motion only: nothing scrubs, so the frame unwinds into four stacked
   panels, each carrying its own caption, and the tab row goes with the
   scrubbing that gave it meaning. */
.no-motion .choose__scroll{ height:auto }
.no-motion .choose__pin{ position:relative; height:auto }
.no-motion .choose__stage{ --g:1; position:relative; inset:auto; height:auto }
.no-motion .choose__panels{ position:relative; inset:auto; display:flex; flex-direction:column }
.no-motion .choose__panel{ position:relative; inset:auto; height:74svh; opacity:1 }
.no-motion .choose__img{ transform:none }
.no-motion .choose__tabs{ display:none }

/* ═══════════════════ 19 · why-us: closing contact banner ═══════════════════
   The .scta block over photography. Every number here is .scta's — heading,
   lede, pill — because this is the same component wearing a picture; only the
   colours change, and the pill picks up the hero's white fill so the two
   calls to action on the page match. */
.cbanner{
  position:relative; overflow:hidden;
  display:flex; align-items:center; justify-content:center; text-align:center;
  min-height:clamp(520px,74svh,860px);
  padding:clamp(90px,12vw,180px) var(--pad);
  background:var(--void); color:var(--ink-i);
}
.cbanner__media{ position:absolute; inset:0; background:#2a2a2a }
/* The photo drifts inside the frame as the banner scrolls (§6), so the fill
   needs slack on both edges — same bleed the .ahero photo runs on. */
.cbanner .ph__fill{ inset:-14% 0 }
/* One flat scrim, not the .ph--shade pair: the type is centred in the middle
   of the frame rather than hanging off an edge, so it needs the whole picture
   held down evenly, with a little extra weight behind the words. */
.cbanner__media::after{
  content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
  /* .ph::after is the placeholder caption — centred with a
     translate(-50%,-50%). inset:0 alone leaves that transform standing and
     the scrim lands half a frame up and to the left, so it has to go. */
  transform:none;
  background:
    radial-gradient(76% 62% at 50% 50%, rgba(0,0,0,.46), rgba(0,0,0,0) 80%),
    linear-gradient(to bottom, rgba(0,0,0,.3), rgba(0,0,0,.3));
}
.cbanner__row{ position:relative; z-index:2; max-width:1200px }
.cbanner__t{
  font-family:var(--sans); font-weight:400; margin:0 0 .5em; color:#fff;
  font-size:clamp(34px,5vw,76px); line-height:1.08; letter-spacing:-.012em;
}
.cbanner__d{
  margin:0 auto clamp(28px,3.4vw,44px); max-width:60ch;
  font-family:var(--sans); font-size:clamp(15px,1.25vw,19px);
  line-height:1.5; color:rgba(255,255,255,.9);
}
/* why-us runs the shortest lede of the eight banners — one short sentence
   that the 60ch measure still broke across two lines, leaving a stub under a
   heading that is itself two lines. Lifting the cap lets it sit on one line;
   the 1200px .cbanner__row is the real limit above, and below roughly 600px
   the container is narrower than the sentence, so it wraps as before. The
   longer ledes on the service pages keep the 60ch measure. */
.cbanner__d--wide{ max-width:none }
/* Symmetric padding on every pill. These carried padding:18px/14px, an
   optical nudge meant to lift type that sits high in a line-height:1 line
   box — but they are inline-flex with align-items:center, which already
   centres the line box, so the 4px only ever pushed the label below the
   middle of the pill. Measured 2px low before this. */
.cbanner__cta{
  display:inline-flex; align-items:center; justify-content:center;
  padding:calc(16px + var(--pill-shim)) 32px calc(16px - var(--pill-shim)); border-radius:999px; border:1px solid #fff;
  background:#fff; color:var(--ink);
  font-family:var(--sans); font-weight:500; font-size:18px; line-height:1;
  transition:background .35s var(--ease), color .35s var(--ease), border-color .35s var(--ease);
}
.cbanner__cta:hover,
.cbanner__cta:focus-visible{ background:var(--pill-hover-light); border-color:var(--pill-hover-light); color:var(--ink) }

/* ═══════════════════ 20 · why-us: how we get it done ═══════════════════
   jobyaviation.com/experience's block. The heading scrolls past, then one
   pinned frame holds three groups — the step names, the picture, and that
   step's copy. Picture and copy cross together as the step advances.

   Desktop lays the three groups out as columns; the phone stacks them as
   picture / names / copy, which is what the reference does there. Either way
   there is one picture and one paragraph on screen, so the mechanic is the
   same at both sizes rather than unwinding into four of everything.

   Type is the site's: .scta__t / .scta__d for the head, and .legal__inner's
   h2/p pair for the step names and their copy. */

/* --pw is the picture's width, and the middle grid column is set to it. An
   `auto` column there collapses to zero — the only thing in that column is
   sized from `aspect-ratio`, whose intrinsic width contributes nothing for a
   column to size from — and the side columns then split the whole row and run
   under the picture. Measured at 1440x900: the nav's right edge sat at 417px
   with the picture starting at 381.

   Derived from the height cap so the picture can never outgrow the frame: at
   5/6, 93svh of height is 77.5svh of width. The vw term keeps it off the side
   columns on short, wide screens. */
.process{
  --pw:min(77.5svh, 900px, 48vw);
  position:relative; background:var(--paper); color:var(--ink);
}

/* 100svh of pin plus roughly three-quarters of a viewport per step. */
.process__scroll{ position:relative; height:400svh }
/* Tight padding on purpose: the picture is meant to come within a hair of the
   top and bottom of the frame, so this is the only clearance it gets. */
.process__pin{
  position:sticky; top:0; height:100svh;
  display:flex; align-items:center;
  padding:clamp(14px,2vw,32px) var(--pad);
}

/* Scrolls away ahead of the pin, so the held frame is the picture alone. */
.process__head{
  text-align:center;
  padding:clamp(80px,11vw,170px) var(--pad) clamp(30px,4vw,60px);
}
.process__t{
  margin:0;
  font-family:var(--sans); font-weight:400;
  font-size:clamp(34px,5vw,76px); line-height:1.08; letter-spacing:-.012em;
}
.process__d{
  margin:clamp(12px,1.2vw,20px) auto 0; max-width:60ch;
  font-family:var(--sans); font-size:clamp(15px,1.25vw,19px);
  line-height:1.5; color:var(--muted);
}

/* One gap value, and both side columns pulled up against the picture, so the
   nav-to-picture and picture-to-copy gutters are the same measurement. Left to
   `justify-self:center` the nav floats in the middle of its column and the two
   gutters read as wildly different — ~300px against ~50px at 1440. */
.process__row{
  width:100%; height:100%; min-height:0;
  display:grid; grid-template-columns:1fr var(--pw) 1fr;
  gap:clamp(36px,6.2vw,120px); align-items:center;
}

.process__nav{
  grid-column:1; justify-self:end;
  margin:0; padding:0; list-style:none;
}
.process__n{
  display:flex; align-items:baseline; gap:.6em;
  appearance:none; -webkit-appearance:none; background:none; border:0; padding:0;
  margin:0 0 clamp(20px,2.2vw,36px); cursor:pointer; text-align:left;
  font-family:var(--sans); font-weight:500; color:var(--muted);
  font-size:clamp(24px,2.4vw,38px); line-height:1.25; letter-spacing:-.01em;
  transition:color .5s var(--ease);
}
.process__nav li:last-child .process__n{ margin-bottom:0 }
.process__n.is-on{ color:var(--ink) }
/* The dot holds its space at every state, so the labels never shift sideways
   as the step changes — only its opacity moves. */
.process__dot{
  flex-shrink:0; width:6px; height:6px; border-radius:50%;
  background:currentColor; opacity:0;
  transition:opacity .5s var(--ease);
}
.process__n.is-on .process__dot{ opacity:1 }

/* All four pictures in one box, overlapping, so nothing moves as they cross. */
.process__media{
  grid-column:2; position:relative;
  width:100%; aspect-ratio:5/6;
  /* --ps: the entry scale. The picture grows from PROC_IN to full as the
     section rises, reaching full size exactly as the pin catches; the box is
     always the full size, so only the picture moves while it grows. */
  transform:scale(var(--ps,1)); transform-origin:center center;
  will-change:transform;
}
.process__f{
  position:absolute; inset:0; margin:0; opacity:0;
  /* 2px is the site's picture-frame radius — .aslide__media and
     .scrolltext__item both use it. */
  border-radius:2px; overflow:hidden; background:var(--ph);
}
/* the one step that shows with scripting off */
.process__f.is-on{ opacity:1 }
/* The step swap scales this, not the frame — the frame's transform is already
   spoken for by the entry scale.
   An <img> filling the frame rather than a background on an empty span: same
   crop (object-fit:cover centred is what center/cover was), but the picture is
   in the markup with its alt and its intrinsic size. width/height are set here
   because the attributes would otherwise size it — the frame is what sizes it. */
.process__img{
  position:absolute; inset:0; display:block;
  width:100%; height:100%;
  object-fit:cover; object-position:center;
  background:var(--ph);
  will-change:transform;
}
/* One picture is framed with its subject off to the right, so the centred crop
   loses it in the tall 5:6 desktop frame. Only that frame is tall — the phone
   frame below is wide and shallow, where centred is still the right crop — so
   the shift is scoped to the desktop side of the 820px break. */
@media (min-width:821px){
  .process__img--right{ object-position:right center }
}

/* Same trick as the pictures: all four paragraphs in one cell, so the column
   takes the height of the longest and nothing below it shifts. */
/* Fills its column rather than shrinking to fit: justify-self:start left the
   paragraph 194px wide in a 278px column at 1440, which at 22px is about
   eighteen characters a line. The column is the measure; the max-width is only
   a backstop on very wide screens. */
.process__copy{ grid-column:3; display:grid; max-width:34ch }
.process__cw{ grid-area:1/1; opacity:0 }
.process__cw.is-on{ opacity:1 }
/* Kept in the accessibility tree as the copy's heading, out of the picture
   wherever the nav is already carrying the name. */
.process__pn{
  position:absolute; width:1px; height:1px; overflow:hidden;
  clip-path:inset(50%); white-space:nowrap;
}
.process__c{
  margin:0;
  font-family:var(--sans); font-size:clamp(17px,1.5vw,24px);
  line-height:1.5; color:var(--ink);
}

/* ── phone: the same mechanic, stacked ───────────────────────
   Picture, then the names, then the copy — the reference's phone
   arrangement. The pin and the crossfade stay; only the axis changes. */
@media (max-width:820px){
  /* §12d moves the head inside the pin here, so it needs no page padding of
     its own — the frame already has it. */
  .process__pin > .process__head{
    padding:0 0 clamp(16px,4vw,24px);
    flex:0 0 auto;
  }
  .process__t{ font-size:clamp(28px,7.6vw,40px) }
  .process__d{ font-size:clamp(14px,3.8vw,16px) }
  /* The picture takes whatever is left, so the frame's own bottom clearance is
     what keeps the copy off the edge of the screen. */
  .process__pin{
    flex-direction:column; justify-content:center;
    padding-bottom:clamp(22px,6vw,32px);
  }
  .process__row{
    flex:0 1 auto; height:auto;
    display:flex; flex-direction:column; align-items:stretch;
    justify-content:center; gap:clamp(20px,5vw,30px);
  }
  /* Sized rather than greedy: flex:1 let it swallow every spare pixel and the
     frame became a picture with a list under it. It still gives way when the
     copy runs long. */
  .process__media{
    order:1; aspect-ratio:auto;
    flex:0 1 auto; height:40svh; min-height:26svh;
  }
  .process__nav{ order:2; justify-self:auto }
  /* §12d parks each step's copy inside the name it belongs to here, so the
     text reads directly under its own word rather than under the whole list.
     The right-hand column it came from is then empty. */
  .process__copy{ display:none }
  .process__nav .process__cw{
    display:none; max-width:none;
    margin:clamp(5px,1.6vw,9px) 0 0;
  }
  .process__nav .process__cw.is-on{ display:block }
  /* The dot is a desktop affordance. Here the active step is already the only
     inked name and the only one showing its copy, so it says nothing new — and
     held in flow it indented every name by dot+gap while the picture and the
     copy stayed flush at --pad, so the column read as two different left
     edges. Out of flow, name, copy and picture start on the same line. */
  .process__dot{ display:none }
  /* Proximity: a step's copy belongs to its name, so it sits closer to it
     than the next step's name does. Even margins made the four names and the
     one paragraph read as five equal things. */
  .process__n{ font-size:clamp(20px,5.6vw,26px); margin-bottom:0 }
  /* Same treatment as the tabs above: the name sets a ~27px line box, so the
     target is padded out to 44px with an overlay rather than by moving the
     type. The steps are spaced far enough apart that the grown targets do not
     meet. */
  .process__n{ position:relative }
  .process__n::after{
    content:""; position:absolute; left:0; right:0; top:50%;
    height:44px; transform:translateY(-50%);
  }
  .process__nav li{ margin-bottom:clamp(18px,4.6vw,26px) }
  .process__nav li:last-child{ margin-bottom:0 }
  .process__c{ font-size:clamp(16px,4.2vw,18px); line-height:1.5 }
}

/* ── reduced motion: no pin, no swap, so the nav has nothing to switch ──
   The four names and their copy become a plain list — all of the content,
   none of the mechanic — behind the first picture. */
.no-motion .process__scroll{ height:auto }
.no-motion .process__pin{ position:relative; height:auto; display:block; padding:0 var(--pad) }
.no-motion .process__row{ display:block; height:auto }
.no-motion .process__nav{ display:none }
.no-motion .process__media{
  position:relative; height:auto; aspect-ratio:5/6; transform:none;
  margin-bottom:clamp(28px,6vw,48px);
}
.no-motion .process__f{ opacity:1 }
.no-motion .process__f:not(:first-child){ display:none }
.no-motion .process__copy{ display:block; max-width:none }
.no-motion .process__cw{ opacity:1; margin-bottom:clamp(24px,5vw,40px) }
.no-motion .process__cw:last-child{ margin-bottom:0 }
.no-motion .process__pn{
  position:static; width:auto; height:auto; overflow:visible;
  clip-path:none; white-space:normal;
  margin:0 0 .5em;
  font-family:var(--sans); font-weight:600; color:var(--ink);
  font-size:clamp(18px,1.6vw,24px); line-height:1.25; letter-spacing:-.006em;
}
