/* CONTACT DECK — the Easton homepage as one deck of contact prints being handled.
   Seven scenes. Light throughout, responsive to 360px — no dark shell.

   LAYOUT FOUNDATION
   Every card sits in a grid-centred stage: `.table { display:grid; place-items:center }`
   with all children on the same grid area. Cards are therefore centred with no
   absolute positioning and no negative margins, so nothing has to re-derive
   `size + paper + stamp` — the bug that produced clipped cards in scenes 01,
   04, 05 and 07. Transforms move a card from centre; that is all.

   SIZING
   One unit, `--size`, as min(<svh>, <vw>, <cap>) so height and width are
   constrained independently. A single vmin value tracks the shorter axis,
   which on a desktop is the height — that filled only 17% of a 1920px screen. */

:root {
  /* Palette — re-themed to match the site header's teal gradient
     (see header.css) rather than the deck's original client-supplied
     navy/terracotta set: deep teal, mid teal, pale teal-grey, white. Every
     other colour in this file is one of these four, a translucent version
     of one of these four, a named decorative tint/glow derived from the
     accent (--accent-pale/-glow/-tint-1/-2/-3, below — pulled out of three
     gradients that used to carry their hex stops inline), or a deliberately
     untouched exception — a client's own logo colour (the coverflow cards'
     --card-accent, the case-study --ground swatches) or the camera/tripod
     illustration's realistic hardware-metal rendering, neither of which
     belong to Easton's own palette to begin with. */
  --zinc:     #E5EEEC;
  --petrol:   #0B2E2A;
  --print:    #FFFFFF;
  --board:    #E5EEEC;
  --register: #17B69B;

  --shell: var(--zinc);
  --ink:   var(--petrol);
  --ink-2: #146B5E;
  --faint: rgb(11 46 42 / .6);
  --rule:  rgb(11 46 42 / .18);

  --accent:     var(--register);
  --accent-rgb: 23, 182, 155;
  /* The Services cards' second colour (Animation/Marketing Strategy),
     alternating with --accent (Corporate Video/Branding) — same split the
     photos' own diagonal panels are cut in. */
  --brand-teal: #146B5E;

  /* Three decorative gradient stops, named rather than left as inline hex
     where they're used (the clients-orbit dot, the case-rows featured-card
     background, the case-rows glow blob) — lighter/paler siblings of
     --accent/--register that don't have their own semantic role otherwise,
     but still belong in the token block, not improvised at the point of
     use. */
  --accent-pale:    #8CF3D4;
  --accent-glow:    #55E6BE;
  --accent-tint-1:  #EAF7F4;
  --accent-tint-2:  #CFEFE7;
  --accent-tint-3:  #A9E2D3;

  --display: "Archivo", system-ui, sans-serif;
  --body:    "Familjen Grotesk", system-ui, sans-serif;
  --mono:    "Martian Mono", ui-monospace, Menlo, monospace;
  /* One deliberate accent face, used only for the Services cards' big
     numerals — everywhere else on the page, including their own "Services"
     heading, stays Archivo. */
  --serif:   "Playfair Display", Georgia, serif;
  /* Used once, for the "Every Success..." line in the About band — a
     handwritten aside against the deck's own set type, not a system font. */
  --script:  "Alex Brush", cursive;

  /* The print. Height and width are constrained INDEPENDENTLY:
       min(<svh term>, <vw term>, <cap>)
     A single `vmin` value tracks the shorter axis, which on a desktop is the
     height — so a 1920x945 screen rendered a 321px card and filled 17% of the
     width. This formula grows with width while still guaranteeing vertical
     fit, and each scene overrides the three terms for its own composition. */
  --size:  min(56svh, 44vw, 600px);
  --paper: clamp(7px, 1.4vmin, 13px);
  --stamp: clamp(24px, 4.6vmin, 36px);

  --pad:  clamp(16px, 4vw, 56px);
  --ease: cubic-bezier(.16, 1, .3, 1);
  color-scheme: light;
}

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; background: var(--zinc); }

body {
  background: var(--shell);
  color: var(--ink);
  font-family: var(--body);
  /* 16px floor, not 15px: vmin tracks the short edge, which on a phone is
     the width, so the clamp sits on its minimum for every handset — that
     minimum is the mobile body size, and 16px is where comfortable reading
     starts. */
  font-size: clamp(16px, 1.9vmin, 19px);
  line-height: 1.5;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  transition: background .6s var(--ease), color .6s var(--ease);
}

img { display: block; max-width: 100%; }
a { color: inherit; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
::selection { background: var(--accent); color: var(--print); }

.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.skip { position: absolute; left: -9999px; top: 0; z-index: 900; background: var(--ink); color: var(--shell); padding: 12px 18px; font-family: var(--mono); font-size: 10px; }
.skip:focus { left: 10px; top: 10px; }

/* ============ THE PRINT ============ */

.card {
  position: relative;
  width: calc(var(--size) + var(--paper) * 2);
  background: var(--print);
  padding: var(--paper) var(--paper) 0;
  border-radius: 1px;
  border-bottom: 3px solid var(--card-accent, var(--board));
  box-shadow:
    0 calc(var(--lift, 0) * 26px) calc(var(--lift, 0) * 44px) rgb(11 46 42 / .34),
    0 1px 0 rgb(11 46 42 / .18);
  will-change: transform;
}
.card__img { width: var(--size); height: var(--size); object-fit: cover; background: var(--board); }

.card__stamp {
  height: var(--stamp);
  display: flex; align-items: center; gap: 7px;
  font-family: var(--mono); font-size: clamp(8px, 1.15vmin, 10px);
  line-height: 1.1; letter-spacing: .07em; text-transform: uppercase;
  color: var(--petrol); overflow: hidden;
  clip-path: inset(0 calc((1 - var(--stamped, 1)) * 100%) 0 0);
}
.card__stamp b { font-weight: 500; white-space: nowrap; }
.card__stamp i { font-style: normal; color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.card__stamp em { margin-left: auto; font-style: normal; color: rgb(20 107 94 / .55); white-space: nowrap; }
@media (max-width: 560px) { .card__stamp em { display: none; } }

.card__shade { position: absolute; inset: 0; background: var(--petrol); opacity: calc(var(--dim, 0) * .28); pointer-events: none; }

.reg { position: absolute; mix-blend-mode: difference; pointer-events: none; background: #FFF; }
.reg--h { height: 1px; width: 13px; }
.reg--v { width: 1px; height: 13px; }

/* ============ THE STAGE ============
   All children share one grid area, so every card is centred by the grid.
   No absolute positioning, no negative margins, nothing to re-derive. */

.table {
  position: sticky; top: 0;
  height: 100svh; overflow: hidden;
  display: grid; place-items: center;
  grid-template-areas: "stage";
  perspective: 1400px;
}
.table > * { grid-area: stage; }

.scene { position: relative; }

/* scene chrome pinned to the stage edges rather than centred */
.edge-tl { place-self: start start; }
.edge-tr { place-self: start end; text-align: right; }
.edge-bl { place-self: end start; }
.edge-br { place-self: end end; text-align: right; }
.edge-b  { place-self: end center; }

/* ============ 02 SERVICES ============
   Header block (eyebrow + headline + sub), then four white cards — number,
   icon badge, title, copy, an Explore link, with the photo as a strip along
   the card's own bottom edge. Static, normal document flow, no reveal
   choreography — same "let it sit still" call as 04 Clients further down;
   nothing here is scroll-linked. */

.services { padding-block: clamp(50px, 9svh, 110px); }
.services__inner {
  max-width: min(94vw, 1180px); margin-inline: auto; padding-inline: var(--pad);
  text-align: center;
}

.services__eyebrow {
  font-family: var(--mono); font-weight: 500;
  font-size: clamp(10px, 1.2vmin, 11px); letter-spacing: .18em; text-transform: uppercase;
  color: var(--accent); margin-bottom: 10px;
}

.services__head {
  font-family: var(--serif); font-weight: 800;
  font-size: clamp(2rem, 4.2vw, 3rem); letter-spacing: -.01em;
  color: var(--ink); margin-bottom: 14px;
}

.services__sub {
  max-width: 56ch; margin-inline: auto; color: var(--ink-2);
  font-size: clamp(14px, 1.6vmin, 16px); line-height: 1.6;
  margin-bottom: clamp(36px, 6svh, 60px);
}

.svc-row {
  display: grid; gap: clamp(16px, 1.8vw, 24px);
  grid-template-columns: 1fr;
  text-align: left;
}
@media (min-width: 640px) { .svc-row { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .svc-row { grid-template-columns: repeat(4, 1fr); } }

.svc {
  display: flex; flex-direction: column;
  background: var(--print);
  border: 1px solid var(--rule);
  border-radius: 16px;
  overflow: hidden;
  box-shadow: 0 14px 34px rgb(11 46 42 / .1);
}

.svc__top {
  display: flex; align-items: flex-start; justify-content: space-between;
  padding: 22px 22px 0;
}
.svc__no {
  font-family: var(--serif); font-weight: 800;
  font-size: clamp(1.5rem, 2.6vw, 1.85rem); line-height: 1;
}
.svc--pink .svc__no { color: var(--accent); }
.svc--teal .svc__no { color: var(--brand-teal); }

.svc__icon {
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 42px; height: 42px;
  border-radius: 50%; color: #fff;
}
.svc--pink .svc__icon { background: var(--accent); }
.svc--teal .svc__icon { background: var(--brand-teal); }
.svc__icon svg { width: 52%; height: 52%; }

.svc__rule { display: block; width: 30px; height: 3px; margin: 12px 22px; border-radius: 2px; }
.svc--pink .svc__rule { background: var(--accent); }
.svc--teal .svc__rule { background: var(--brand-teal); }
.svc__rule--sm { margin-top: 10px; }

.svc h3 {
  padding: 0 22px;
  font-family: var(--display); font-weight: 800;
  font-size: clamp(1.15rem, 1.8vw, 1.35rem); line-height: 1.15;
  color: var(--ink);
}
.svc p {
  padding: 0 22px; margin-top: 4px;
  font-size: clamp(13px, 1.2vw, 14.5px); line-height: 1.6;
  color: var(--ink-2);
}

/* margin-top:auto — the four cards' descriptions wrap to different line
   counts, which was leaving this at a different height on every card. This
   pushes it (and the photo right after it) down to a shared bottom edge
   instead, so all four "Explore service" links land on one line. */
.svc__link {
  display: inline-flex; align-items: center; gap: 8px;
  margin: 16px 22px 22px; margin-top: auto;
  font-family: var(--mono); font-weight: 500;
  font-size: clamp(10px, 1.1vw, 11px); letter-spacing: .1em; text-transform: uppercase;
  text-decoration: none;
  transition: gap .2s var(--ease);
}
.svc--pink .svc__link { color: var(--accent); }
.svc--teal .svc__link { color: var(--brand-teal); }
.svc__link svg { width: 15px; height: 15px; }
.svc__link:hover { gap: 12px; }

/* .svc__photo-box carries the fixed 3:2 ratio; .svc__photo is pulled out of
   flow (position:absolute) to fill it. Putting aspect-ratio directly on the
   <img> as the last child of a grid-stretched flex column let the browser
   resolve its height against the stretched card instead of its own ratio —
   the box came out far taller than 3:2, so object-fit:contain centred the
   actual photo in the middle with a wall of white above and below it. A
   dedicated box with an absolutely-positioned image can't inherit stray
   height from the flex column, so this can't happen. Also keeps every card's
   photo (and therefore the "Explore service" link above it) the same height. */
.svc__photo-box {
  position: relative; flex: none;
  width: 100%; aspect-ratio: 3 / 2;
  overflow: hidden;
}
.svc__photo {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: contain;
}

/* ============ 03 THE DEAL ============
   A 3D coverflow, not a scroll-jack: the scene no longer reserves any extra
   scroll runway (was 280vh), so .table is unpinned here — a normal 100svh
   block instead of a sticky stage. See assets/js/deal-coverflow.js. */

.deal .table { position: static; }

/* Two quiet ornaments lifted from the reference composition — a fading grid
   of dots under the heading, and a soft brand-teal blob with its own faint
   dot texture in the upper right. z-index: 0 (not negative) keeps them in
   .scene's own stacking order rather than falling through to the document
   root, where a negative z-index would land them behind body's own opaque
   background; the coverflow cards' z-index of 1+ still stacks them above. */
.deal__dots {
  position: absolute; z-index: 0; pointer-events: none;
  left: clamp(16px, 4vw, 56px); top: clamp(210px, 34svh, 330px);
  width: clamp(92px, 8.4vw, 128px); height: clamp(78px, 7.2vw, 108px);
  background-image: radial-gradient(circle, var(--brand-teal) 1.6px, transparent 1.6px);
  background-size: 17px 17px;
  -webkit-mask-image: linear-gradient(135deg, #000 0%, #000 38%, transparent 76%);
          mask-image: linear-gradient(135deg, #000 0%, #000 38%, transparent 76%);
  opacity: .5;
}

.deal__blob {
  position: absolute; z-index: 0; pointer-events: none;
  right: -9vw; top: -7svh;
  width: clamp(320px, 34vw, 560px); height: clamp(320px, 34vw, 560px);
  border-radius: 50%;
  background: radial-gradient(circle at 60% 40%, rgb(47 203 160 / .26) 0%, rgb(47 203 160 / .12) 55%, transparent 78%);
}
.deal__blob::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  background-image: radial-gradient(circle, var(--faint) 1px, transparent 1px);
  background-size: 13px 13px;
  -webkit-mask-image: radial-gradient(circle at 55% 45%, #000 0%, #000 52%, transparent 70%);
          mask-image: radial-gradient(circle at 55% 45%, #000 0%, #000 52%, transparent 70%);
  opacity: .45;
}

@media (max-width: 620px) {
  .deal__dots, .deal__blob { display: none; }
}

.deal__head-block { padding: var(--pad); max-width: 46ch; pointer-events: none; }
.deal__eyebrow {
  font-family: var(--mono); font-size: clamp(8px, 1.2vmin, 10px);
  letter-spacing: .15em; text-transform: uppercase; color: var(--faint);
  margin-bottom: 10px;
}
.deal__head {
  font-family: var(--display); font-weight: 800;
  font-variation-settings: "wdth" 106;
  font-size: clamp(2.1rem, 4.6vw, 3.4rem); line-height: 1.05;
  letter-spacing: -.015em; color: var(--ink);
}

.coverflow {
  position: relative;
  width: min(1100px, 92vw);
  height: clamp(260px, 42svh, 420px);
  display: flex; align-items: center; gap: clamp(6px, 1.6vw, 20px);
  perspective: 1400px;
}
.coverflow__track {
  position: relative; flex: 1 1 auto; height: 100%;
  display: grid; place-items: end center; grid-template-areas: "s";
}
.coverflow__track > * { grid-area: s; }

/* Every card shares one box size (--size is fixed, not content-driven), so
   with a centred transform-origin the side cards' scale-down pulled their
   top AND bottom inward by equal amounts — smaller cards looked like they'd
   floated up off the table instead of just standing further back. Anchoring
   both the grid alignment and the transform-origin to the bottom edge keeps
   every card's stamp/base on the same line regardless of scale; only the
   tops recede, which also reads as the correct "leaning back" coverflow
   perspective rather than shrinking toward the middle. */
.coverflow__card {
  --size: min(34svh, 22vw, 320px);
  cursor: pointer;
  transform-origin: 50% 100%;
  transform: translate3d(var(--tx, 0), 0, var(--tz, 0)) rotateY(var(--rot, 0deg)) scale(var(--scale, 1));
  transition: transform .7s var(--ease), opacity .7s var(--ease);
  z-index: var(--z, 1);
  opacity: var(--op, 1);
}
.coverflow__card:focus-visible { outline: 2px solid var(--accent); outline-offset: 5px; }
.coverflow__card .card__img { -webkit-box-reflect: below 6px linear-gradient(to bottom, transparent, transparent 55%, rgb(255 255 255 / .22)); }

.coverflow__nav {
  /* Cards stack up to z-index 100 (see --z in deal-coverflow.js) and their
     translateX carries them visually past the track's own layout edge on
     wide viewports, painting over these buttons even though they sit right
     next to the track in the flex row. Nav needs to outrank every card. */
  flex: 0 0 auto; z-index: 150;
  display: grid; place-items: center;
  /* The clamp used to bottom out at 32px, i.e. it shrank furthest exactly
     where fingers replace a mouse. These are the only way to drive the
     coverflow, so 44px is the floor. */
  width: clamp(44px, 3.6vw, 48px); height: clamp(44px, 3.6vw, 48px);
  border-radius: 50%; border: 1px solid rgb(11 46 42 / .25);
  background: rgb(255 255 255 / .78); color: var(--ink);
  cursor: pointer;
  transition: background .2s var(--ease), transform .2s var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .coverflow__nav:hover { background: var(--print); transform: scale(1.06); }
}
.coverflow__nav:active { transform: scale(.96); }
.coverflow__nav svg { width: 44%; height: 44%; }

/* One panel, tracking whichever card is centred in the coverflow. Sits on
   the plain page background now (no background video behind it any more),
   so it's dark ink on a paper-tinted backing — same pattern as .row__body
   further down — rather than the white-on-assumed-dark-video treatment
   this used when a video played underneath. */
.deal__panel {
  place-self: end start;
  z-index: 40;
  width: min(23rem, 78vw);
  margin: 0 var(--pad) clamp(44px, 9svh, 92px);
  padding: 14px 16px 14px 14px;
  border-radius: 6px;
  border-left: 2px solid var(--accent);
  background: rgb(231 226 220 / .78);
  opacity: var(--copy, 0); transition: opacity .4s var(--ease);
  pointer-events: none;
}
.deal__panel h3 {
  font-family: var(--display); font-weight: 800; font-variation-settings: "wdth" 100;
  font-size: clamp(1rem, 2.4vmin, 1.6rem); line-height: 1.02;
  text-transform: uppercase; letter-spacing: -.01em;
  color: var(--ink);
}
.deal__panel p {
  font-size: clamp(12px, 1.7vmin, 14px); line-height: 1.55; color: var(--ink-2); margin-top: 7px;
}
.deal__panel b {
  display: block; margin-top: 9px;
  font-family: var(--mono); font-size: clamp(8px, 1.1vmin, 9.5px);
  letter-spacing: .11em; text-transform: uppercase; font-weight: 400;
  color: var(--accent);
}
@media (max-width: 720px) { .deal__panel p { display: none; } }

/* ============ 05 CLIENTS ============
   Static — normal document flow, no scroll-jack, no reveal animation. A
   trust bar of 28 real marks reads more credible calm than performed; the
   diamond lattice itself carries the visual interest, so motion stays to a
   single tasteful hover-lift per tile rather than another scroll mechanism. */

.clients { padding-block: clamp(64px, 12svh, 150px); overflow: hidden; }

.clients__grid {
  max-width: min(94vw, 1460px); margin-inline: auto;
  padding-inline: var(--pad);
  display: grid; gap: clamp(40px, 6vw, 80px);
  grid-template-columns: 1fr;
  align-items: center;
}
@media (min-width: 860px) {
  .clients__grid { grid-template-columns: minmax(280px, .82fr) 1.18fr; }
}

.clients__eyebrow {
  font-family: var(--mono); font-size: clamp(9px, 1.2vmin, 10.5px);
  letter-spacing: .16em; text-transform: uppercase; color: var(--accent);
  font-weight: 500; margin-bottom: 14px;
}
.clients__head {
  font-family: var(--display); font-weight: 800;
  font-variation-settings: "wdth" 106;
  font-size: clamp(1.7rem, 3.6vw, 2.5rem); line-height: 1.12;
  letter-spacing: -.015em; color: var(--ink);
}
.clients__head span { color: var(--accent); }
.clients__rule {
  display: block; width: 46px; height: 3px; margin: 20px 0;
  background: var(--accent); border-radius: 2px;
}
.clients__lead {
  max-width: 42ch; color: var(--ink-2);
  font-size: clamp(14px, 1.6vmin, 16px); line-height: 1.65;
}
.clients__roster {
  margin-top: 18px;
  font-family: var(--mono); font-size: clamp(8.5px, 1.1vmin, 10px);
  letter-spacing: .1em; text-transform: uppercase; color: var(--faint);
  line-height: 1.7;
}
.clients__cta {
  margin-top: clamp(28px, 4svh, 44px);
  display: inline-flex; align-items: center; gap: 14px;
  text-decoration: none; color: var(--ink);
}
.clients__cta b {
  font-family: var(--mono); font-weight: 500; font-size: clamp(9px, 1.2vmin, 10.5px);
  letter-spacing: .14em; text-transform: uppercase;
}
.clients__cta i {
  display: grid; place-items: center; flex: none;
  width: 38px; height: 38px; border-radius: 50%;
  background: var(--accent); color: #fff;
  transition: transform .25s var(--ease), background .25s var(--ease);
}
.clients__cta i svg { width: 46%; height: 46%; }
@media (hover: hover) and (pointer: fine) {
  .clients__cta:hover i { background: var(--ink); transform: translate3d(3px, 0, 0); }
}
.clients__cta:active i { transform: translate3d(2px, 0, 0); }

/* ---------- the logo wall ----------
   A plain 4-column grid of upright cards replaces the old rotated-diamond
   lattice: the marks are photographs of client signage (not vector logos),
   which read as noise at diamond scale and counter-rotated 45°. Grayscale by
   default, full colour on hover, is the standard trust-wall move for a set
   of visually inconsistent source images — it unifies them instead of
   fighting their differences. */
.clients__marks {
  position: relative;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(10px, 1.6vw, 18px);
  padding: clamp(14px, 2.6vw, 28px) 0;
}

.clients__blob {
  position: absolute; z-index: 0; pointer-events: none;
  inset: -8% -6% auto auto; right: -6%; top: -10%;
  width: clamp(260px, 30vw, 460px); height: clamp(260px, 30vw, 460px);
  border-radius: 50%;
  background: radial-gradient(circle at 55% 42%, rgb(47 203 160 / .22) 0%, rgb(47 203 160 / .1) 55%, transparent 78%);
}

/* Two curved leads with node dots, tracing the grid's outer edge — a quiet
   echo of a circuit board rather than a literal wire-to-every-tile diagram. */
.clients__wires {
  position: absolute; z-index: 0; inset: 0; pointer-events: none;
  width: 100%; height: 100%;
  overflow: visible;
}
.clients__wires path {
  fill: none; stroke: var(--brand-teal); stroke-width: .25; opacity: .35;
}
.clients__wires circle { fill: var(--accent); opacity: .5; }

.mark-tile {
  position: relative; z-index: 1;
  aspect-ratio: 5 / 3;
  background: var(--print);
  border-radius: 12px;
  padding: 10%;
  box-shadow: 0 10px 22px -12px rgb(11 46 42 / .28);
  display: grid; place-items: center;
  transition: box-shadow .3s var(--ease), transform .3s var(--ease);
}
.mark-tile img {
  width: 100%; height: 100%; object-fit: contain;
  filter: grayscale(1) contrast(1.05) opacity(.8);
  transition: filter .3s var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .mark-tile:hover {
    transform: translateY(-3px);
    box-shadow: 0 16px 28px -10px rgb(11 46 42 / .34);
  }
  .mark-tile:hover img { filter: none; }
}

@media (max-width: 640px) {
  .clients__marks { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
  .clients__wires { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .mark-tile, .mark-tile:hover, .mark-tile img { transition: none; }
}

/* ---------- copy-column ornament ----------
   A fading dot grid and a concentric "let's talk" ring, echoing the same
   two motifs already established in the Deal section — a deliberate
   repeated visual language, not a one-off. */
.clients__copy { position: relative; }

.clients__dots {
  position: absolute; z-index: 0; pointer-events: none;
  left: 2px; bottom: clamp(-64px, -8vh, -34px);
  width: clamp(90px, 8vw, 120px); height: clamp(76px, 6.6vw, 100px);
  background-image: radial-gradient(circle, var(--brand-teal) 1.5px, transparent 1.5px);
  background-size: 16px 16px;
  -webkit-mask-image: linear-gradient(-45deg, #000 0%, #000 34%, transparent 72%);
          mask-image: linear-gradient(-45deg, #000 0%, #000 34%, transparent 72%);
  opacity: .4;
}

.clients__orbit {
  position: absolute; z-index: 0; pointer-events: none;
  left: clamp(120px, 12vw, 160px); bottom: clamp(-70px, -9vh, -40px);
  width: 74px; height: 74px;
  border-radius: 50%;
  border: 1px solid rgb(11 46 42 / .12);
  display: grid; place-items: center;
}
.clients__orbit::before {
  content: ""; position: absolute; inset: -14px;
  border-radius: 50%; border: 1px solid rgb(11 46 42 / .08);
}
.clients__orbit span {
  width: 30px; height: 30px; border-radius: 50%;
  background: radial-gradient(circle at 38% 32%, var(--accent-pale) 0%, var(--accent) 72%);
}

@media (max-width: 860px) {
  .clients__dots, .clients__orbit { display: none; }
}

/* ============ 06 THE CASE ROWS ============
   Alternating image / text rows with a large numbered marker, per the reference.
   This section is normal document flow rather than a pinned scene: the previous
   four attempts here (two-column slides, a colour flood, a swatch fan, a
   drifting wall) all buried the work inside a device. Here the layout gets out
   of the way and the motion is a reveal, not a mechanism. */

.spread { position: relative; padding-block: clamp(60px, 12svh, 150px); }

/* ---------- layout: one featured case, the rest a compact list ----------
   Of the seven identity programmes, Zea Pop carries the section (its own
   photography already sits on a solid colour ground — the same --ground
   var every row already set — so it doubles as a poster with room for the
   "Featured project" tag). The other six read better as a scannable list
   than as six more full-bleed rows the visitor has to scroll past. */
.rows {
  max-width: min(94vw, 1460px); margin-inline: auto;
  display: grid; gap: clamp(28px, 4.6vw, 56px);
  grid-template-columns: 1fr;
}
@media (min-width: 980px) {
  .rows { grid-template-columns: 1.35fr .9fr; align-items: start; }
}

.rows__list {
  display: flex; flex-direction: column;
  gap: clamp(8px, 1.4vw, 14px);
}

.row__media {
  position: relative; overflow: hidden;
  border-radius: 10px; background: var(--board);
}
.row__media img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform .5s var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .row__media:hover img { transform: scale(1.04); }
}

/* ---------- the featured row ----------
   One pale mint-to-teal card carries both the badge and the case itself,
   rather than the tag floating loose over the photo — the card is the
   poster, not just a backdrop for one. */
.rows__feature {
  position: relative;
  border-radius: 20px;
  padding: clamp(20px, 3.4vw, 38px);
  background: linear-gradient(155deg, var(--accent-tint-1) 0%, var(--accent-tint-2) 58%, var(--accent-tint-3) 100%);
  overflow: hidden;
}
.row__tag {
  position: relative; z-index: 2;
  display: inline-block; margin-bottom: clamp(16px, 2.6vw, 28px);
  font-family: var(--mono); font-weight: 600; font-size: clamp(9px, 1.1vw, 10.5px);
  letter-spacing: .1em; text-transform: uppercase; color: var(--accent);
  background: var(--print); padding: 8px 18px; border-radius: 20px;
  box-shadow: 0 6px 16px -8px rgb(11 46 42 / .25);
  opacity: var(--r, 1); transition: opacity .3s var(--ease);
}
.row--feature {
  position: relative; z-index: 1;
  display: grid; gap: clamp(20px, 3vw, 40px);
  grid-template-columns: 1fr;
}
@media (min-width: 640px) {
  .row--feature { grid-template-columns: .95fr 1.05fr; align-items: center; }
}
.row--feature .row__media {
  position: relative;
  aspect-ratio: 16 / 12;
  background: none; border-radius: 0; overflow: visible;   /* let the blob bleed past the photo's own edge */
  /* wipes open from the outer edge as the row arrives */
  clip-path: inset(0 calc((1 - var(--r, 1)) * 100%) 0 0);
}
.row--feature .row__media img {
  position: relative; z-index: 1;
  width: 100%; height: 100%; object-fit: cover;
  border-radius: 14px;
  transform: scale(calc(1.06 - var(--r, 1) * .06));
}
/* A teal halo bleeding out from behind the photo's own rounded card —
   the source photography already sits on a solid ground (no cutout asset
   to float on a literal circle), so the glow reads as the backdrop
   instead. */
.row__blob {
  position: absolute; z-index: 0; pointer-events: none;
  inset: -14% -10%;
  border-radius: 50%;
  background: radial-gradient(circle at 45% 40%, var(--accent-glow) 0%, var(--register) 55%, transparent 76%);
  opacity: calc(.85 * var(--r, 1));
}

/* ---------- the compact rows ----------
   Ribbon | text | brand-tinted photo, with the arrow overlapping the
   photo's own corner — a scannable list where each card still reads as a
   distinct client, not just a row of six identical thumbnails. */
.row--compact {
  position: relative;
  display: grid; grid-template-columns: auto 1fr clamp(120px, 22vw, 190px);
  align-items: stretch; gap: 0;
  min-height: clamp(84px, 10.4vw, 100px);
  border-radius: 12px;
  background: var(--print);
  box-shadow: 0 8px 20px -16px rgb(11 46 42 / .3);
  opacity: var(--r, 1);
  transform: translate3d(0, calc((1 - var(--r, 1)) * 16px), 0);
  transition: transform .3s var(--ease), box-shadow .25s var(--ease);
}
.row--compact:hover { box-shadow: 0 12px 24px -14px rgb(11 46 42 / .34); }

/* the flag tag: a solid brand-colour block with a notch cut into its own
   bottom edge, like a bookmark tab, instead of a plain numeral in ink */
.row__ribbon {
  display: flex; align-items: center; justify-content: center;
  width: clamp(38px, 4.8vw, 50px);
  background: var(--ground); color: #fff;
  font-family: var(--display); font-weight: 800;
  font-size: clamp(1rem, 1.7vw, 1.25rem);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 82%, 0 100%);
}

.row--compact .row__body {
  display: flex; flex-direction: column; justify-content: center;
  padding: 10px clamp(12px, 1.8vw, 18px);
}
.row--compact .row__media {
  position: relative; overflow: hidden;
  border-radius: 0 12px 12px 0;
}
/* a brand-colour wash so six unrelated product photos read as one set of
   colour-coded cards, echoing the ribbon's own --ground */
.row--compact .row__media::after {
  content: ""; position: absolute; inset: 0;
  background: var(--ground); opacity: .5; mix-blend-mode: multiply;
}
.row--compact .row__note { display: none; }
/* 34px is the drawn circle, below the 44px a finger needs — ::after pads
   the hit area out to 44 without changing what is painted, which is the
   sanctioned way to keep a small visual control properly tappable. */
.row__arrow::after {
  content: ""; position: absolute;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 44px; height: 44px;
}
.row__arrow {
  position: absolute; z-index: 2;
  right: clamp(10px, 1.6vw, 16px); bottom: clamp(10px, 1.6vw, 16px);
  display: grid; place-items: center;
  width: 34px; height: 34px; border-radius: 50%;
  background: var(--ink); border: none; color: #fff;
  transition: background .2s var(--ease), transform .2s var(--ease);
}
.row__arrow svg { width: 44%; height: 44%; }
@media (hover: hover) and (pointer: fine) {
  .row--compact:hover .row__arrow { background: var(--accent); border-color: var(--accent); color: #fff; transform: translate3d(2px, 0, 0); }
}
.row--compact:active .row__arrow { background: var(--accent); color: #fff; transform: translate3d(1px, 0, 0); }

/* The featured row's text now sits directly on its own card's gradient
   ground rather than over the photo, so it no longer needs the paper
   backing + text-shadow insurance that protects text laid over an image. */
.row--feature .row__body { max-width: 40ch; }
.row--compact .row__body { min-width: 0; }

.row__cta {
  display: inline-flex; align-items: center; gap: 10px;
  /* 12px padding around an 18px icon computes to 42px — near enough to the
     44px floor to be worth pinning rather than leaving to round down. */
  min-height: 44px;
  margin-top: clamp(16px, 2.6vw, 26px);
  padding: 12px 20px 12px 22px;
  border-radius: 30px;
  background: var(--ink); color: var(--print);
  font-family: var(--mono); font-weight: 500; font-size: clamp(9px, 1.1vw, 10.5px);
  letter-spacing: .12em; text-transform: uppercase; text-decoration: none;
  opacity: var(--r, 1);
  transition: background .2s var(--ease), transform .2s var(--ease);
}
.row__cta i { display: grid; place-items: center; width: 18px; height: 18px; }
.row__cta i svg { width: 100%; height: 100%; }
@media (hover: hover) and (pointer: fine) {
  .row__cta:hover { background: var(--accent); transform: translate3d(3px, 0, 0); }
}
.row__cta:active { background: var(--accent); transform: translate3d(2px, 0, 0); }

.row__no {
  font-family: var(--display); font-weight: 800;
  font-variation-settings: "wdth" 96;
  font-size: clamp(1.9rem, 4.4vmin, 2.8rem); line-height: 1;
  color: var(--accent); opacity: var(--r, 1);
  letter-spacing: -.02em; margin-bottom: clamp(6px, 1.2vmin, 12px);
  transform: translate3d(0, calc((1 - var(--r, 1)) * 14px), 0);
  transition: transform .3s var(--ease), opacity .3s var(--ease);
}
.row__client {
  font-family: var(--display); font-weight: 800;
  font-variation-settings: "wdth" 104;
  font-size: clamp(1.25rem, 3.1vmin, 2rem); line-height: 1.05;
  letter-spacing: -.02em; margin-bottom: clamp(8px, 1.4vmin, 14px);
  color: var(--ink);
  opacity: var(--r, 1);
  transform: translate3d(0, calc((1 - var(--r, 1)) * 18px), 0);
  transition: transform .3s var(--ease), opacity .3s var(--ease);
}
.row--feature .row__client { text-transform: uppercase; }
.row__claim {
  font-size: clamp(1.05rem, 2.3vmin, 1.6rem); line-height: 1.32;
  font-weight: 600; color: var(--ink);
  opacity: var(--r, 1);
  transform: translate3d(0, calc((1 - var(--r, 1)) * 22px), 0);
  transition: transform .3s var(--ease), opacity .3s var(--ease);
}
.row__note {
  margin-top: clamp(10px, 1.8vmin, 18px);
  font-size: clamp(.9rem, 1.75vmin, 1.05rem); line-height: 1.55;
  color: var(--ink-2);
  opacity: calc(var(--r, 1) * .92);
  transform: translate3d(0, calc((1 - var(--r, 1)) * 26px), 0);
  transition: transform .3s var(--ease), opacity .3s var(--ease);
}
.row--compact .row__client {
  font-size: clamp(.98rem, 1.8vw, 1.2rem);
  margin-bottom: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.row--compact .row__claim {
  font-size: clamp(.76rem, 1.3vw, .86rem); font-weight: 500;
  color: var(--faint); text-shadow: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.rows__head {
  max-width: min(94vw, 1460px); margin: 0 auto clamp(40px, 7svh, 84px);
}
/* Two side-by-side columns from 760px up — the heading + copy on the
   left, the four value props on the right; a single stacked column below
   that, since each would otherwise be squeezed to a sliver. Grid, not
   flex-basis math, so the split is a clean 50/50 rather than whatever two
   competing flex-grow items settle on. */
.rows__top {
  display: grid; grid-template-columns: 1fr; align-items: center;
  gap: clamp(28px, 5vw, 56px);
}
@media (min-width: 760px) {
  .rows__top { grid-template-columns: 1fr 1fr; }
}
.rows__col { min-width: 0; }

/* ---------- the four value props ----------
   A quiet trust signal beside the heading rather than another headline —
   four short claims, each backed by an icon rather than a stat, since
   these aren't numbers Easton counts. Fixed 4-across, deliberately smaller
   than a standalone value-props row: this one sits beside a full paragraph
   of copy, so it reads as a compact strip rather than competing with it —
   one line, vertically centred against the text column via .rows__top's
   own align-items. */
.rows__props {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(10px, 1.6vw, 18px);
  list-style: none;
}
.prop {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  --t: clamp(0, (var(--r, 1) - 0.1) * 3.2, 1);
  opacity: var(--t);
  transform: translate3d(0, calc((1 - var(--t)) * 16px), 0);
  transition: transform .3s var(--ease), opacity .3s var(--ease);
}
.prop__icon {
  display: grid; place-items: center;
  width: clamp(30px, 3vw, 38px); height: clamp(30px, 3vw, 38px);
  border-radius: 50%;
  background: rgb(23 182 155 / .1); color: var(--accent);
  margin-bottom: 8px;
}
.prop__icon svg { width: 46%; height: 46%; }
.prop__label {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(.6rem, .85vw, .68rem);
  color: var(--ink); line-height: 1.2;
}
.prop__sub {
  margin-top: 2px;
  font-size: clamp(.56rem, .78vw, .62rem); color: var(--ink-2);
}
/* Four across is a desk-width layout. Holding it on a phone meant shrinking
   the labels to ~8px to make them fit, which is below the point text is
   readable at all — the single row was never worth that. Two-by-two gives
   each prop roughly half the width and lets the type go back to a normal
   size. */
@media (max-width: 640px) {
  .rows__props {
    grid-template-columns: repeat(2, 1fr);
    gap: clamp(18px, 4vw, 26px) clamp(12px, 3vw, 18px);
  }
  .prop__icon { width: 40px; height: 40px; }
  .prop__label { font-size: .82rem; }
  .prop__sub { font-size: .74rem; }
}
/* Sits straight on the plain page background (no background video here any
   more) — dark ink with the same light halo shadow as .row__client below,
   rather than the white-on-assumed-dark-video treatment this used when a
   video played behind it.

   Reveal: fade + rise, staggered h2 → .rows__say → the closing line, driven
   by --r (set in deck.js spreadUpdate, same continuous/reversible pattern
   as .row below and .about__head above). Default var(--r, 1) keeps it
   visible with JS off. */
.rows__eyebrow {
  font-family: var(--mono); font-size: clamp(9px, 1.2vmin, 10.5px);
  letter-spacing: .16em; text-transform: uppercase; color: var(--accent);
  font-weight: 500; margin-bottom: 12px;
  --t: clamp(0, (var(--r, 1) - 0.00) * 3.2, 1);
  opacity: var(--t);
  transition: opacity .3s var(--ease);
}

.rows__head h2 {
  font-family: var(--display); font-weight: 800;
  font-variation-settings: "wdth" 110;
  font-size: clamp(1.6rem, 5vmin, 3.2rem); line-height: .98;
  text-transform: uppercase; letter-spacing: -.025em; max-width: 22ch;
  color: var(--ink);
  text-shadow: 0 1px 10px rgb(231 226 220 / .85);
  --t: clamp(0, (var(--r, 1) - 0.00) * 3.2, 1);
  opacity: var(--t);
  transform: translate3d(0, calc((1 - var(--t)) * 22px), 0);
  transition: transform .3s var(--ease), opacity .3s var(--ease);
}
.rows__head h2 span { color: var(--accent); }
.rows__head p {
  margin-top: 12px; color: var(--ink-2); max-width: none;
  font-size: clamp(.95rem, 1.9vmin, 1.15rem);
  text-shadow: 0 1px 8px rgb(231 226 220 / .8);
  --t: clamp(0, (var(--r, 1) - 0.14) * 3.2, 1);
  opacity: var(--t);
  transform: translate3d(0, calc((1 - var(--t)) * 22px), 0);
  transition: transform .3s var(--ease), opacity .3s var(--ease);
}
.rows__head p:last-of-type {
  --t: clamp(0, (var(--r, 1) - 0.26) * 3.2, 1);
}

/* Reuses .stat/.stat__num/.stat__label as authored for .about__stats —
   same component, a second instance, not a parallel one-off. */
.rows__stats {
  max-width: min(94vw, 1460px); margin: clamp(48px, 8svh, 88px) auto 0;
  padding: clamp(20px, 3vw, 32px) clamp(22px, 4vw, 44px);
  border-radius: 14px;
  background: rgb(255 255 255 / .55);
  border: 1px solid rgb(11 46 42 / .08);
  display: grid; grid-template-columns: repeat(2, 1fr); gap: clamp(24px, 4vw, 40px);
}
@media (min-width: 720px) {
  .rows__stats { grid-template-columns: repeat(4, 1fr); }
}
.rows__stats .stat__num { font-size: clamp(1.7rem, 3.6vw, 2.4rem); }

/* One stat in this row has no verified source — see the audit note in
   deck.html's Case Rows comment. Marked, not silently guessed at: the
   number is an em dash and the label says so, in the same muted mono
   already used for stat labels rather than an alarming red flag. */
.stat[data-unverified] .stat__num { color: var(--faint); }
.stat__pending {
  display: block; margin-top: 2px;
  font-family: var(--mono); font-weight: 400; font-size: .78em;
  letter-spacing: 0; text-transform: none; color: var(--faint);
}

@media (prefers-reduced-motion: reduce) {
  .row__media { clip-path: none !important; }
  .row__media img, .row__no, .row__client, .row__claim, .row__note,
  .row--compact, .row__tag, .row__cta, .row__blob, .prop {
    transform: none !important; opacity: 1 !important;
  }
}

/* ============ 07 THE CONTACT SHEET ============
   Used to lay the deck out flat as 129 developing photo prints behind a
   dimming scrim and a fading-in sign-off card, all driven by scroll
   progress through a pinned 185vh stage. None of that survived: the photo
   grid never rendered reliably, and the card's own fade kept getting
   caught mid-transition looking like a broken translucent popup rather
   than an intentional reveal. The card itself is also gone now — its
   contact details duplicate the page's actual footer just below.

   What's left is a plain photo grid (below) that fades and rises into
   place as it scrolls into view — driven off the collage's own position in
   normal document flow (collageUpdate in deck.js), not off a pinned
   stage's scroll progress, so like .deal elsewhere in this file the stage
   itself stays unpinned into a normal block rather than a sticky stage
   with scroll runway reserved for it. */

.back { height: auto; }
.back .table {
  position: static; height: auto; overflow: visible;
  padding-block: clamp(60px, 12svh, 150px);
}

/* An ordinary grid, after three increasingly elaborate jigsaw-puzzle
   attempts (a diamond silhouette with empty cells, then hand-placed tiles
   with tab/socket connectors, then a softer connector glyph) each kept
   reading as broken or fussy rather than as a puzzle. auto-fill/minmax
   hands the column count to the browser instead of hand-tuning it per
   breakpoint: however many tiles of at least ~min(--u) fit the available
   width, that's the column count, and 1fr stretches them to fill it
   exactly — genuinely edge-to-edge at any viewport, no silhouette to keep
   in sync, no connector glyph to misalign when a row's length changes.
   Every photo is actually square (610–644px either side — see the real
   files, not the 644×642 attributes carried over from an old crop), so
   every tile stays a plain square via aspect-ratio regardless of column
   width. 17 tiles leaves a partial last row at most column counts — left-
   aligned, which is the ordinary shape for a grid this size, not something
   this needs to hide or center-compensate for. */
.collage {
  --u: clamp(140px, 15vw, 260px);
  --g: clamp(8px, 1.2vw, 18px);
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--u), 1fr));
  gap: var(--g);
  width: min(96vw, 2000px);
}
.collage__tile {
  position: relative; overflow: hidden; border-radius: 10px;
  aspect-ratio: 1;
  background: var(--shell);
  box-shadow: inset 0 0 0 1px rgb(11 46 42 / .1);
  /* Fade + rise, staggered by --i (reading order, 0–16) — --r is
     section-level scroll progress (collageUpdate in deck.js). Its CSS
     fallback of 1 means every tile already sits at --p:1 (fully in place)
     whenever JS never sets --r at all (no-JS, reduced motion, or before
     the section has ever come into view), matching the deck's own reveals
     elsewhere (.prop uses the same var(--r,1) idiom). */
  --p: clamp(0, (var(--r, 1) - var(--i, 0) * .04) * 3.4, 1);
  transform: translate3d(0, calc((1 - var(--p)) * 22px), 0);
  opacity: var(--p);
  transition: transform .4s var(--ease), opacity .4s var(--ease);
}
.collage__tile img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  filter: saturate(.85) contrast(1.03);
}

/* ============ RAIL + SHELL TOGGLE ============ */

.rail {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  display: flex; gap: 2px; height: 24px;
  padding: 0 var(--pad) 7px; align-items: flex-end;
  font-family: var(--mono); pointer-events: none;
}
.rail__block { pointer-events: auto; position: relative; flex: var(--w) 0 0; height: 5px; background: var(--board); text-decoration: none; border-radius: 1px; }
/* The visible bar is 5px so it reads as a progress hairline, but a 5px
   pointer target is a mis-click on a mouse and unusable on a finger. This
   pseudo-element is the actual hit area — the full height of .rail, no
   paint of its own. */
.rail__block::after {
  content: ""; position: absolute;
  left: 0; right: 0; bottom: -7px; top: -14px;
}
.rail__block i { position: absolute; inset: 0 auto 0 0; width: calc(var(--bp, 0) * 100%); background: var(--track, var(--accent)); }
.rail__block b {
  position: absolute; bottom: 9px; left: 0;
  font-size: 8px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--faint); font-weight: 400; white-space: nowrap;
  opacity: 0; transition: opacity .25s;
}
.rail__block[aria-current="true"] b, .rail__block:hover b { opacity: 1; }
.rail__block[aria-current="true"] { height: 9px; }
@media (max-width: 600px) { .rail__block b { display: none; } }
/* On a phone the labels above are hidden, so these become seven unlabelled
   slivers — and several are only a few pixels wide, since each block is
   flexed to its scene's own scroll length. Tapping one is guesswork, and
   they sit along the bottom edge where a thumb rests, so every hit is more
   likely accidental than intended. Below 720px the rail keeps doing the one
   job it does well — showing progress — and stops pretending to be
   navigation. The scenes are all reachable by scrolling. */
@media (max-width: 720px) {
  .rail__block { pointer-events: none; }
  .rail__block::after { content: none; }
}

/* ============ REDUCED MOTION ============
   Every scene collapses to a plain readable document. Nothing is hidden. */

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


  .riffle, .deal, .back { height: auto !important; }
  .table {
    position: static; height: auto; overflow: visible; display: block;
    padding-block: 6svh;
  }
  .table > * { grid-area: auto; }
  .throw, .coverflow__card {
    position: static; visibility: visible !important; transform: none !important;
    width: auto; height: auto;
  }
  .coverflow {
    display: grid !important; gap: 16px; padding-inline: var(--pad);
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    width: 100%; height: auto;
  }
  .coverflow__track { display: contents; }
  .coverflow__nav { display: none; }
  .card__img { width: 100%; height: auto; aspect-ratio: 1; }
  .bridge__claim, .deal__panel {
    position: static; opacity: 1 !important; width: auto; padding: 0 var(--pad);
    text-align: left; margin: 20px 0; transform: none;
  }
  .bridge__claim .ch { color: var(--ink) !important; }
  .fan { display: none !important; }
}
