/* ==========================================================================
   SECTIONS — page-level layout, top to bottom.
   ========================================================================== */

section { position: relative; }

/* ============================================================
   HERO
   ============================================================ */
.hero {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: 100svh;
    padding: var(--header-h) var(--gutter) var(--space-lg);
    overflow: hidden;
    /* Transparent paper fill (body supplies the paper ground) so the fixed
       wire spirit shows through behind the hero content. Tint kept. */
    background:
        radial-gradient(120% 90% at 85% -10%, rgba(12, 93, 136, 0.06), transparent 60%);
}
.hero__rules {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}
/* Wire spirit — the hero's 3D line-drawing (Three.js), viewport-aspect so
   there is no sub-box to clip the shape. It sits fixed BEHIND #main (z:0 <
   #main z:1) and is revealed only where a section background is transparent —
   which, gated to `html.wire-on`, is the hero. So the wireframe reads as dark
   ink over the paper in the hero and is cleanly occluded by every opaque
   section below it as you scroll. (The page-wide persistent-reveal experiment
   was set aside; the motif is carried onward by purpose-built texture instead.)
   Absent on touch / no-hover / reduced-motion (the module never inits). */
.wire-spirit {
    position: fixed;
    inset: 0;
    width: 100vw;
    height: 100svh;
    z-index: 0;
    pointer-events: none;
}
@media (max-width: 1023px) { .wire-spirit { display: none; } }

#main { position: relative; z-index: 1; }

/* Hero background transparent so the spirit shows through over the paper.
   Gated, so non-spirit contexts keep the untouched original hero. */
html.wire-on .hero { background: transparent; }
.hero__inner {
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: var(--container);
    margin-inline: auto;
}

.hero__eyebrow {
    overflow: hidden;
    font-family: var(--font-display);
    font-size: var(--fs-headline);      /* Figma hero-tagline → Font Sizes/h1 60 */
    font-weight: var(--wt-display);     /* Museo Sans 700 */
    line-height: var(--lh-headline);
    letter-spacing: var(--tr-headline); /* -2px @ 60 */
    color: var(--dark-gray);
}
.hero__eyebrow-line { display: inline-block; }

.hero__title {
    margin-block: 0.05em 0;
    font-size: var(--text-hero); /* clamp — responsive, same size for every word */
    line-height: var(--leading-tight); /* single-line */
    letter-spacing: var(--tracking-display);
    min-height: 1.1em; /* reserve space so word swaps never reflow the page */
    perspective: 1100px;
}
/* Two-line titles (e.g. "customer service") — leading .8;
   hero.js FLIP-tweens the box height when wrap state changes. */
.hero__title.is-multiline {
    line-height: 0.8;
}
.hero__word {
    display: inline-block;
    overflow: hidden; /* mask for the char rise/fall (cycle mode) */
    padding-bottom: 0.09em; /* keep descenders inside the mask */
    margin-bottom: -0.09em;
    vertical-align: bottom;
    color: var(--orange);
}
.hero__word .chunk { display: inline-block; white-space: nowrap; }
.hero__word .char {
    display: inline-block;
    will-change: transform, opacity;
}
/* Auto type-loop mode (hero.js): caret positions absolutely inside;
   no rise mask needed — typing never translates vertically */
.hero__word--type {
    position: relative;
    max-width: 100%; /* allow chunk-to-chunk wrap on long phrases */
    overflow: visible;
    padding-bottom: 0;
    margin-bottom: 0;
}
/* The closing word stays brand orange (same as every typed word). */
.hero__word.is-final { color: var(--orange); }

/* Scroll mode (desktop): words stack in 3D and fly through the camera */
.hero__word--stage {
    display: block;
    position: relative;
    height: 1.14em;
    overflow: visible;
    padding-bottom: 0;
    margin-bottom: 0;
    transform-style: preserve-3d;
}
.hero__word--stage .w3d {
    position: absolute;
    left: 0;
    top: 0;
    white-space: nowrap;
    color: var(--orange);
    opacity: 0;
    transform-origin: 38% 50%;
    will-change: transform, opacity;
}
.hero__word--stage .w3d.is-final { color: var(--orange); }

/* Typed characters: hidden until the scrub timeline lands them */
.w3d-char {
    display: inline-block;
    white-space: pre;
    visibility: hidden;
    opacity: 0;
    will-change: transform, opacity;
    transform-origin: 50% 85%;
    backface-visibility: hidden;
}

/* The living caret — types every word, gets left behind at launch */
.hero__caret {
    position: absolute;
    left: 0;
    top: 0;
    width: 0.075em;
    min-width: 3px;
    height: 1em;
    border-radius: 999px;
    background: var(--orange);
    will-change: transform;
    animation: caret-breathe 1.15s ease-in-out infinite;
}
@keyframes caret-breathe {
    0%, 42% { opacity: 1; }
    58%, 88% { opacity: 0.18; }
    100% { opacity: 1; }
}

.hero__foot {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-lg);
    margin-top: var(--space-lg);
}
.hero__foot-lead {
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
}
.hero__counter {
    font-size: var(--text-small);
    font-weight: 700;
    letter-spacing: var(--tracking-label);
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}
.hero__counter span { color: var(--orange); }
.hero__abstract {
    max-width: var(--measure);
    margin: 0;
    font-size: var(--text-lead);
    line-height: 1.55;
    color: var(--muted);
    text-wrap: pretty;
}

/* Editorial CTA row (Arpeggio borrow): wide-tracked small caps with an
   orange underline that wipes on hover, and a nudging arrow */
.hero__actions {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.9rem;
    flex-shrink: 0;
}
.hero__cta {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0.7rem;
    padding-bottom: 0.4rem;
    font-size: var(--text-label);
    font-weight: 700;
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--dark-gray);
    text-decoration: none;
}
.hero__cta::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 2px;
    background: var(--orange);
    transform: scaleX(1);
    transform-origin: right;
    transition: transform var(--dur-med) var(--ease-out);
}
.hero__cta:hover::after,
.hero__cta:focus-visible::after { transform: scaleX(0); transform-origin: right; }
.hero__cta--ghost { color: var(--muted); }
.hero__cta--ghost::after { background: var(--line); }
.hero__cta-arrow {
    color: var(--orange);
    transition: transform var(--dur-fast) var(--ease-out);
}
.hero__cta:hover .hero__cta-arrow,
.hero__cta:focus-visible .hero__cta-arrow { transform: translateX(0.45em); }

/* Locating line bottom-right — balances the scroll cue (Arpeggio's
   "based in Milano" editorial detail) */
.hero__locale {
    position: absolute;
    right: var(--gutter);
    bottom: var(--space-md);
    font-size: var(--text-label);
    font-weight: 700;
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--muted);
}

.hero__scroll-cue,
.scroll-cue {
    position: absolute;
    bottom: var(--space-md);
    left: var(--gutter);
    z-index: 8;
    display: inline-flex;
    align-items: center;
    gap: 0.9rem;
    text-decoration: none;
    font-size: var(--text-label);
    font-weight: 700;
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--muted);
}
.hero__scroll-cue-line {
    width: 3.5rem;
    height: 1px;
    background: currentColor;
    transform-origin: left;
    animation: cue-pulse 2.4s var(--ease-in-out) infinite;
}
@keyframes cue-pulse {
    0%, 100% { transform: scaleX(0.4); opacity: 0.5; }
    50% { transform: scaleX(1); opacity: 1; }
}
html.no-motion .hero__scroll-cue-line { animation: none; }

/* Pinned stages only — Who We Are (desktop pin) and Look at Us (sticky).
   Same bottom-left as the hero. Hidden when that pin is not active. */
.who .scroll-cue { display: none; }
.who.is-pinned .scroll-cue {
    display: inline-flex;
    mix-blend-mode: difference;
    color: #d8d8d8;
}
.work-carousel .scroll-cue { display: none; }
@media (min-width: 1024px) and (orientation: landscape) {
    .work-carousel .scroll-cue {
        display: inline-flex;
        mix-blend-mode: difference;
        color: #d8d8d8;
    }
}

@media (max-width: 720px) {
    .hero__foot { flex-direction: column; align-items: flex-start; gap: var(--space-md); }
    .hero__actions { flex-direction: row; flex-wrap: wrap; gap: var(--space-md); }
    .hero__scroll-cue,
    .hero__locale,
    .scroll-cue,
    .who.is-pinned .scroll-cue,
    .work-carousel .scroll-cue { display: none; }
}

/* ============================================================
   WHO WE ARE — horizontal panel sequence
   Default layout is the vertical stack (mobile, reduced motion,
   no JS). `is-horizontal` is added by who.js on capable desktops.
   ============================================================ */
.who { background: transparent; }
.who-panel__inner {
    width: 100%;
    max-width: var(--container);
    margin-inline: auto;
}
.who-panel { padding: var(--space-2xl) var(--gutter); }

/* ---- Panel 1: statement + stats (Figma 1123:9051) ---- */
.who-panel--statement { position: relative; overflow: hidden; }
.who-statement__content {
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: var(--container);
    margin-inline: auto;
}
.who-statement__headline {
    margin-top: var(--space-md);
    max-width: 15em;
    font-family: var(--font-display);
    font-size: var(--fs-headline-lg);
    font-weight: var(--wt-display);
    letter-spacing: var(--tr-headline-lg);
    line-height: var(--lh-headline-lg);
    color: var(--color-heading);
    text-wrap: balance;
}
.who-statement__headline em { font-style: normal; color: var(--color-accent); }

/* Stats row with divider rules (Figma stat-frame) */
.who-stats {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-lg);
    margin-top: var(--space-xl);
}
.who-stat {
    position: relative;
    min-width: 11rem;
    padding-right: var(--space-lg);
}
.who-stat:not(:last-child)::after {
    content: "";
    position: absolute;
    right: 0;
    top: 0.35em;
    bottom: 0.9em;
    width: 1px;
    background: var(--line);
}
.who-stat__value {
    display: block;
    font-family: var(--font-display);
    font-weight: var(--wt-display);
    font-size: var(--fs-headline);
    line-height: 1;
    letter-spacing: var(--tr-headline);
    color: var(--color-heading);
    font-variant-numeric: tabular-nums;
}
.who-stat__unit { color: var(--color-accent); }
.who-stat__label {
    display: block;
    margin-top: 0.6rem;
    max-width: 12em;
    font-family: var(--font-body);
    font-size: var(--fs-paragraph);
    line-height: var(--lh-paragraph);
    color: var(--color-text-muted);
}
/* (Panel-1 portrait wipe archived — see _reference/who-figure-wipe/) */

/* ---- Panel 2 backdrop: ghost typography (Figma headline-xxl) ----
   Hidden for now — the three ghost headlines (design / experience / solutions)
   are being relocated to another section (Jeff, Who We Are rework). */
.who-ghosts {
    display: none;
    position: absolute;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
}
.who-ghost {
    position: absolute;
    font-size: clamp(16rem, 31.25vw, 28.125rem); /* 450px @1440 */
    font-weight: var(--weight-display);
    line-height: 0.54; /* Figma 240/450 */
    letter-spacing: -0.08em; /* -36px @450 */
    color: transparent;
    -webkit-text-stroke: 1px rgba(255, 255, 255, 0.14);
    white-space: nowrap;
    will-change: transform;
    user-select: none;
}
.who-ghost--1 { left: -8vw; top: 3%; }
.who-ghost--2 { left: 34vw; top: 38%; }
.who-ghost--3 { left: 6vw; top: 70%; }

.who-panel--fluent {
    position: relative;          /* media inset anchor (pre-pin fallback) */
    overflow: hidden;
    background: var(--ink);      /* ground while the reel loads */
    color: var(--paper);
    min-height: 100svh;          /* full screen — pins for the type color-fill */
    display: flex;
    align-items: center;
    justify-content: center;
    /* generous side margins — the statement owns the panel */
    padding-inline: clamp(3rem, 10vw, 12rem);
}
/* Full-bleed promo reel under the typed statement (Arpeggio image
   treatment). who.js scales the video 1.6 → 1 (the Arpeggio grow) as
   Panel 3 rises; non-enhanced builds show it static at rest. */
.who-fluent__media {
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
}
.who-fluent__media img,
.who-fluent__media video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: 50% 50%;
}
/* Whisper of a scrim — the image stays bright and full-opacity, but
   the white type always clears the hottest sky / water zones. */
.who-fluent__media::after {
    content: '';
    position: absolute;
    inset: 0;
    background: rgba(13, 15, 20, 0.16);
}
/* TEMP: hide the watermarked stock reel until a clean clip is ready.
   Restore: remove .who-panel--fluent-hold from index.html (and this
   block). The <video> stays in the markup with preload="none". */
.who-panel--fluent-hold {
    background: var(--deep);
}
.who-panel--fluent-hold .who-fluent__media {
    display: none;
}
.who-panel--fluent .who-panel__inner {
    position: relative;
    z-index: 1;
}
/* Typewriter fill (Step 4): characters start empty; a scrubbed caret types
   them in reading order (who.js). Words wrap as units — never mid-glyph.
   Non-enhanced (no is-fill) = full white text, no caret. */
.who-panel--fluent.is-fill .fluent-word {
    display: inline-block;
    white-space: nowrap;
}
.who-panel--fluent.is-fill .fluent-ch {
    display: inline-block;
    opacity: 0;
}
.who-fluent__text {
    position: relative;
    /* Figma headline-lg on orange: Museo 700, white — spans the inner width */
    max-width: 100%;
    font-family: var(--font-display);
    font-size: var(--fs-headline-lg);
    font-weight: var(--wt-display);
    letter-spacing: var(--tr-headline-lg);
    line-height: var(--lh-headline-lg);
    text-wrap: balance;
}
/* Paper caret on orange — same living-cursor motif as .type-caret / hero */
.who-fluent__caret {
    position: absolute;
    top: 0;
    left: 0;
    width: 0.075em;
    min-width: 3px;
    border-radius: 999px;
    background: var(--paper);
    pointer-events: none;
    animation: caret-breathe 1.15s ease-in-out infinite;
}

/* The ecosystem rides inside the track as its own dark panel; the
   .ecosystem block below carries its layout in both modes. */
.who-panel--eco { padding: 0; }

/* Default (stacked / mobile / no-JS): the label reads as a normal section
   label at the top of the section. */
.who__label {
    max-width: calc(var(--container) + 2 * var(--gutter));
    margin-inline: auto;
    padding-inline: var(--gutter);
    margin-bottom: var(--space-sm);
}

/* Vertical pinned Panel 1→2 stage (Who rework, Step 2). Enhanced desktops get
   `is-pinned` from who.js: the viewport pins at 100svh, the track (Panels 1+2,
   each 100svh) slides up one panel so Panel 2 + cloud replace Panel 1, and the
   fixed label tweens off before Panel 3. Non-enhanced = plain vertical stack. */
.who.is-pinned .who__viewport {
    position: relative;
    height: 100svh;
    overflow: hidden;
}
.who.is-pinned .who__label {
    position: absolute;
    top: calc(var(--header-h) + var(--space-md));
    /* Span the viewport so margin-inline:auto can still centre the capped box —
       otherwise the label shrink-wraps at the gutter and falls off the grid. */
    left: 0;
    right: 0;
    z-index: 5;
    margin-block: 0;
}
.who.is-pinned .who__track {
    position: relative;
    will-change: transform;   /* GSAP slides this up one panel */
}
.who.is-pinned .who__track > .who-panel { height: 100svh; }
.who.is-pinned .who-panel--statement {
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: calc(var(--header-h) + var(--space-lg)) var(--gutter) var(--space-lg);
}
/* Scrim — dims Panel 2 as it's pushed back behind the rising Panel 3.
   Layering inside the pinned viewport: track (z0) < scrim (z2) < Panel 3 (z3)
   < label (z5, gone before Panel 3 arrives). */
.who__scrim { display: none; }
.who.is-pinned .who__scrim {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 2;
    background: #000;
    opacity: 0;              /* GSAP raises it during the slide */
    pointer-events: none;
}
/* Panel 3 — overlay in the pinned stage; GSAP slides it up (yPercent 100 → 0)
   over the held Panel 2, then runs the type-wipe. */
.who.is-pinned .who-panel--fluent {
    position: absolute;
    inset: 0;
    z-index: 3;
    min-height: 0;           /* inset:0 already fills the 100svh viewport */
    will-change: transform;
}

.services__headline em,
.work__headline em,
.clients__headline em,
.quotes__kicker em,
.contact__headline em {
    font-style: normal;
    color: var(--orange);
}

/* ============================================================
   ECOSYSTEM — organic 3D team-cloud web (who panel 2)
   Copy left · web scene right · floating description card docks
   beside the focused node. Cursor-driven exploration; scroll only
   drifts the turntable during the hold — never the focus.
   ============================================================ */
.ecosystem {
    position: relative;
    background: rgba(255, 255, 255, 0.9); /* panel-2: 90% white */
    color: var(--ink);
    overflow: hidden;
    padding-inline: var(--gutter);   /* section gutter (like .services) */
}
.ecosystem__stage {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: clamp(1rem, 2vw, 2.5rem);
    width: 100%;
    max-width: var(--container);     /* 1200 — global content width (matches What We Do) */
    margin-inline: auto;
    min-height: 100svh;
    padding-block: calc(var(--header-h) + var(--space-md)) var(--space-lg);
}
.ecosystem__copy {
    position: relative;
    z-index: 2;
    flex: 0 0 auto;
    width: min(30vw, 26rem);
}
/* "Many disciplines." needs ~444px at the 60px headline. The old 26rem
   (416px) cap — and a ≥1440-only bump — left mid-desktop widths wrapping
   "disciplines." onto a third line. From the cloud breakpoint up, give the
   copy 30rem and nowrap the second line. Scene keeps flexing; --scene-safe
   still lets it bleed right so the constellation isn't crushed. */
@media (min-width: 1024px) {
    .ecosystem.is-3d .ecosystem__copy,
    .who.is-pinned .ecosystem__copy {
        width: 30rem;
        max-width: 42%;
    }
}
/* Portrait tablets: keep the interactive cloud, but stack title + subtext
   above it so the constellation can use the full width. Landscape tablets
   keep the desktop side-by-side. */
@media (orientation: portrait) {
    .ecosystem.is-3d .ecosystem__stage {
        flex-direction: column;
        align-items: stretch;
        justify-content: flex-start;
        gap: var(--space-md);
    }
    .who.is-pinned .ecosystem.is-3d .ecosystem__stage {
        height: 100%;
        min-height: 0;
    }
    .ecosystem.is-3d .ecosystem__copy,
    .who.is-pinned .ecosystem.is-3d .ecosystem__copy {
        width: 100%;
        max-width: 40rem;
        flex: 0 0 auto;
    }
    .ecosystem.is-3d .ecosystem__title-line { white-space: normal; }
    .ecosystem.is-3d .ecosystem__legend {
        grid-template-columns: repeat(3, minmax(0, 1fr));
        margin-top: var(--space-sm);
        column-gap: 0.75rem;
        row-gap: 0.2rem;
    }
    .ecosystem.is-3d .ecosystem__scene {
        --scene-safe: 1.25rem;
        flex: 1 1 auto;
        width: 100%;
        min-width: 0;
        min-height: 22rem;
        height: auto;
        margin-right: 0;
    }
}
.ecosystem__title {
    font-family: var(--font-display);
    font-size: var(--fs-headline);      /* 60px — smaller than panel-1 headline */
    font-weight: var(--wt-display);
    letter-spacing: var(--tr-headline);
    line-height: var(--lh-headline);
    color: var(--blue);
}
.ecosystem__title-line { white-space: nowrap; }
.ecosystem__sub {
    max-width: 20em;
    margin-top: var(--space-md);
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-lg);
    line-height: var(--lh-paragraph-lg);
    color: rgba(22, 22, 26, 0.82);
    text-wrap: pretty;
}

/* Legend — conventional activation path; mirrors the active role
   (built by ecosystem.js in 3D mode only) */
.ecosystem__legend {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: clamp(1rem, 1.8vw, 2rem);
    row-gap: 0.45rem;
    margin-top: var(--space-md);
}
.ecosystem__legend-item {
    appearance: none;
    background: none;
    border: 0;
    padding: 0.1rem 0;
    text-align: left;
    cursor: pointer;
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-sm);
    line-height: 1.4;
    color: rgba(22, 22, 26, 0.6);
    transition: color var(--dur-med) var(--ease-out);
}
.ecosystem__legend-item:hover { color: var(--ink); }
.ecosystem__legend-item.is-active { color: var(--ink); font-weight: 700; }
.ecosystem__legend-index {
    display: inline-block;
    min-width: 1.7rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    color: rgba(22, 22, 26, 0.35);
    font-variant-numeric: tabular-nums;
    transition: color var(--dur-med) var(--ease-out);
}
.ecosystem__legend-item.is-active .ecosystem__legend-index { color: var(--color-accent); }

/* Scene — the cloud's bounded stage. The copy stays inside the 1200 content
   column; the scene BREAKS OUT to the right, extending toward the viewport edge
   for more constellation room — but stops `--scene-safe` short of the edge so a
   focused node (and its docked card, which ecosystem.js flips inward) never
   falls off screen. On narrower viewports the min(0px, …) clamps the breakout
   to nothing. */
.ecosystem__scene {
    position: relative;
    --scene-safe: 5rem;   /* keep the rightmost nodes this far from the edge */
    /* Fill the width between the copy and the description — no fixed aspect,
       so the cloud spreads to use the whole space. Grabbable (drag to spin). */
    flex: 1 1 auto;
    min-width: 0;
    margin-right: min(0px, calc(var(--container) / 2 + var(--scene-safe) - 50vw));
    height: min(80svh, 40rem);
    perspective: 1600px;
    perspective-origin: 50% 50%;
    cursor: grab;
    touch-action: none;
}
/* Pays back the 48px the widened copy takes at ≥1440 (headline-wrap fix), then
   goes further: nodes are clamped to 7–93% of the scene's OWN box (see layout()
   in ecosystem.js) and the docked description card is clamped 8px inside that
   same box, not the viewport — so neither can be cut off no matter how close
   the scene sits to the true edge. Free to run tight. Must sit after the base
   rule above. */
@media (min-width: 1440px) {
    .ecosystem__scene { --scene-safe: 1.5rem; }
}
.ecosystem__scene:active { cursor: grabbing; }
.ecosystem__cloud {
    position: absolute;
    inset: 0;
    transform-style: preserve-3d;
    will-change: transform;
    /* Everything in the scene is pointer-transparent: native 3D hit-testing
       can't reach circles behind the z=0 plane, so ecosystem.js hit-tests
       the projected geometry itself on the scene's events. */
    pointer-events: none;
}
/* Connective rules threading the disciplines in focus order (built by
   ecosystem.js). Coordinates are absolute within the scene (top-left origin). */
.eco-line {
    position: absolute;
    left: 0;
    top: 0;
    height: 1.5px;
    pointer-events: none;    /* rules must never intercept a node click */
    /* Dashed connective rule (Figma), clearly visible on the dark panel */
    background: repeating-linear-gradient(to right,
        rgba(22, 22, 26, 0.62) 0 5px, transparent 5px 11px);
    transform-origin: 0 50%;
    will-change: transform;
}
.eco-line.is-hot {
    height: 2px;
    background: repeating-linear-gradient(to right,
        var(--color-accent) 0 6px, transparent 6px 12px);
}

/* Decorative free rings — the constellation's negative space (ref comp).
   They ride the rotor so they spin with the cloud; never interactive. */
.eco-ring {
    position: absolute;
    top: 0;
    left: 0;
    border-radius: 50%;
    border: 1px solid rgba(22, 22, 26, 0.15);
    pointer-events: none;
    will-change: transform;
}

.eco-node {
    position: absolute;
    top: 0;
    left: 0;
    pointer-events: none;    /* hits are computed from projected geometry (JS) */
    will-change: transform, opacity;
}
.eco-node__inner {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.55rem;
    will-change: transform;  /* cursor-disturbance nudge (JS) */
}
.eco-node__figure {
    position: relative;
    display: grid;
    place-items: center;
    width: 7rem;             /* larger default circle; JS scales per depth + focus */
    aspect-ratio: 1;
    border-radius: 50%;
    /* Opaque disc + ring w/ orange line icon; the active role fills solid */
    background: rgba(255, 255, 255, 0.9); /* screened white — disc reads via its ink line */
    border: 1px solid rgba(22, 22, 26, 0.3);
    color: var(--color-accent);
    transition: background-color var(--dur-med) var(--ease-out),
                border-color var(--dur-med) var(--ease-out),
                color var(--dur-med) var(--ease-out),
                box-shadow var(--dur-med) var(--ease-out);
}
.eco-node__figure svg { width: 46%; height: 46%; }
.eco-node:not(.is-focus).is-hint .eco-node__figure,
.eco-node:not(.is-focus).is-nudge .eco-node__figure,
.eco-node:not(.is-focus):focus-visible .eco-node__figure {
    border-color: rgba(22, 22, 26, 0.65);
}
/* Hover hint (.is-hint via JS hit-test) — a pulse ring emanating from the
   circle; activation is click. .is-nudge is the same ring, played once by
   the ambient idle loop so random icons invite a click. */
.eco-node__figure::after {
    content: '';
    position: absolute;
    inset: -1px;
    border-radius: 50%;
    border: 1px solid var(--color-accent);
    opacity: 0;
    pointer-events: none;
}
.eco-node:not(.is-focus).is-hint .eco-node__figure::after,
.eco-node:not(.is-focus):focus-visible .eco-node__figure::after {
    animation: eco-pulse 1.4s cubic-bezier(0.25, 0.6, 0.35, 1) infinite;
}
.eco-node:not(.is-focus).is-nudge .eco-node__figure::after {
    animation: eco-pulse 1.4s cubic-bezier(0.25, 0.6, 0.35, 1) 1;
}
@keyframes eco-pulse {
    0%   { opacity: 0.9; transform: scale(1); }
    75%  { opacity: 0;   transform: scale(1.6); }
    100% { opacity: 0;   transform: scale(1.6); }
}
.eco-node__label {
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-sm);
    font-weight: 700;
    letter-spacing: 0.03em;
    color: rgba(22, 22, 26, 0.6);
    white-space: nowrap;
    opacity: 0;              /* only the focused node shows its label */
    transition: opacity var(--dur-med) var(--ease-out), color var(--dur-med) var(--ease-out);
}
.eco-node.is-focus { z-index: 5; }
.eco-node.is-focus .eco-node__figure {
    background: var(--color-accent);
    border-color: var(--color-accent);
    color: var(--white);
    box-shadow: 0 0 0 8px rgba(240, 82, 35, 0.16), 0 22px 60px rgba(240, 82, 35, 0.3);
}
.eco-node.is-focus .eco-node__label { opacity: 1; color: var(--ink); }

/* Floating description card — ecosystem.js docks it beside the focused
   node (data-side flips the leader). Never intercepts the cursor. */
.ecosystem__panel {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 6;
    width: 18rem;
    padding: 1.4rem 1.5rem 1.5rem;
    background: rgba(255, 255, 255, 0.92);
    border: 1px solid rgba(22, 22, 26, 0.12);
    box-shadow: 0 18px 50px rgba(22, 22, 26, 0.14);
    border-radius: var(--radius);
    backdrop-filter: blur(6px);
    opacity: 0;                /* revealed per focus by ecosystem.js */
    pointer-events: none;
    will-change: transform, opacity;
}
.ecosystem__panel::before {
    content: '';
    position: absolute;
    top: var(--leader-y, 50%);
    width: 24px;
    border-top: 1px dashed rgba(22, 22, 26, 0.45);
}
.ecosystem__panel[data-side='right']::before { left: -24px; }
.ecosystem__panel[data-side='left']::before { right: -24px; }
.ecosystem__index {
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-sm);
    font-weight: 700;
    letter-spacing: 0.14em;
    color: var(--color-accent);
    font-variant-numeric: tabular-nums;
}
.ecosystem__index [data-eco-total],
.ecosystem__index-sep { color: rgba(22, 22, 26, 0.4); }
.ecosystem__role-name {
    margin-top: 0.6rem;
    font-family: var(--font-display);
    font-size: var(--fs-card-title);
    font-weight: var(--wt-display);
    letter-spacing: var(--tr-card-title);
    line-height: var(--lh-card-title);
    color: var(--ink);
}
.ecosystem__role-desc {
    margin-top: 0.7rem;
    font-family: var(--font-body);
    font-size: var(--fs-paragraph);
    line-height: var(--lh-paragraph);
    color: rgba(22, 22, 26, 0.72);
    text-wrap: pretty;
}

/* The roster is the data source + mobile/no-JS grid; hidden in 3D mode.
   TODO (Jeff, 2026-07-10): the grid is a placeholder — the responsive view
   should become a stacked ACCORDION (icons visible collapsed, expand for
   the description) so mobile keeps flair + interactivity. */
.ecosystem.is-3d .ecosystem__roles { display: none; }

/* ---- Fallback (mobile / reduced motion / no JS): stacked roster ---- */
.ecosystem:not(.is-3d) {
    overflow-x: clip;
}
.ecosystem:not(.is-3d) .ecosystem__stage {
    flex-direction: column;
    align-items: flex-start;
    min-width: 0;
    min-height: 0;
    width: 100%;
    max-width: var(--container);
    margin-inline: auto;
}
.ecosystem:not(.is-3d) .ecosystem__scene { display: none; }
.ecosystem:not(.is-3d) .ecosystem__copy {
    width: 100%;
    max-width: 40rem;
    min-width: 0;
}
.ecosystem:not(.is-3d) .ecosystem__title-line { white-space: normal; }
.ecosystem:not(.is-3d) .ecosystem__roles {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-md);
    width: 100%;
    min-width: 0;
    margin-top: var(--space-lg);
    padding: 0;
    list-style: none;
}
@media (min-width: 768px) {
    .ecosystem:not(.is-3d) .ecosystem__roles {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
    .ecosystem:not(.is-3d) .ecosystem__title-line { white-space: nowrap; }
}
.ecosystem:not(.is-3d) .ecosystem__role {
    padding-top: var(--space-sm);
    border-top: 1px solid rgba(22, 22, 26, 0.14);
}
.ecosystem:not(.is-3d) .ecosystem__role h4 {
    font-family: var(--font-display);
    font-size: var(--fs-card-title);
    letter-spacing: var(--tr-card-title);
    color: var(--ink);
}
.ecosystem:not(.is-3d) .ecosystem__role p {
    margin-top: 0.4rem;
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-sm);
    color: rgba(22, 22, 26, 0.7);
    text-wrap: pretty;
}

@media (max-width: 1023px) {
    .ecosystem:not(.is-3d) .ecosystem__stage { flex-direction: column; align-items: flex-start; }
}

/* ============================================================
   WHAT WE DO — service index (Figma 111:2127)
   Centered typographic head; uniform 3×3 grid with Figma gap-sm
   (1.25rem / 20px) between cards. Each cell keeps its own hairline
   border + radius. Icon top; name pinned to the card's bottom edge
   with the essence line stacked directly above it. Hover / focus /
   tap: blurb rises above the name; tags fade in below it; name lifts
   by the measured tag-stack height so multi-line pill wraps never
   collide with the headline (GSAP when available —
   js/modules/services.js). Mobile: full content, stacked.
   ============================================================ */
.services {
    max-width: calc(var(--container) + 2 * var(--gutter));
    margin-inline: auto;
    padding: var(--space-2xl) var(--gutter);
}
.services__head {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--gap-md);
    margin-bottom: var(--space-xl);
}
.services__headline {
    max-width: 100%;
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--fs-headline-lg);
    font-weight: var(--wt-display);
    letter-spacing: var(--tr-headline-lg);
    line-height: 1;
    color: var(--blue);
}
.services__sub {
    max-width: 34em;
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-lg);
    line-height: var(--lh-paragraph-lg);
    color: var(--muted);
    text-wrap: pretty;
}

.services__lattice {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--gap-sm);                     /* Figma gap-sm — 1.25rem / 20px */
    max-width: var(--container);
    margin-inline: auto;
}
.service {
    --pad: clamp(1.5rem, 2.2vw, 2.25rem);   /* shared so .service__tags can pin to the same inset */
    position: relative;
    display: flex;
    flex-direction: column;
    min-height: 30rem;
    padding: var(--pad);
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
}
/* Orange rule sweeps across the cell's top edge when active */
.service::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 2px;
    background: var(--orange);
    transform: scaleX(0);
    transform-origin: left;
    transition: transform var(--dur-med) var(--ease-out);
    z-index: 1;
}
/* Soft glow trailing the cursor inside the cell (JS sets --mx/--my) */
.service::after {
    content: '';
    position: absolute;
    inset: 0;
    background: radial-gradient(14rem circle at var(--mx, 50%) var(--my, 50%),
        rgba(240, 82, 35, 0.06), transparent 70%);
    opacity: 0;
    transition: opacity var(--dur-med) var(--ease-out);
    pointer-events: none;
}
.service.is-active::before,
.service:focus-within::before { transform: scaleX(1); }
.service.is-active::after,
.service:focus-within::after { opacity: 1; }

.service__heading { margin: 0; }
/* The toggle wraps only the icon now — the name moved into .service__foot
   at the bottom, so it can no longer supply the button's accessible name
   from content. aria-labelledby points at #svc-name-N instead (see index.html):
   single source of truth, no duplicated label text. */
.service__toggle {
    display: block;
    padding: 0;
    background: none;
    border: 0;
    cursor: pointer;
    color: inherit;
}
.service__icon {
    display: block;
    width: 2.6rem;
    height: 2.6rem;
    color: var(--orange);
    transition: transform var(--dur-med) var(--ease-out);
}
.service__icon svg { width: 100%; height: 100%; }
.service.is-active .service__icon,
.service:focus-within .service__icon { transform: translateY(-3px) scale(1.06); }
.service__name {
    display: block;
    max-width: 10em;
    font-family: var(--font-display);
    font-size: var(--fs-card-title);
    font-weight: var(--wt-display);
    letter-spacing: var(--tr-card-title);
    line-height: var(--lh-card-title);
    color: var(--dark-gray);
}

/* Essence ↔ detail share one grid cell so the swap never reflows. margin-top:
   auto pushes this block — and .service__foot after it — down to the card's
   bottom edge, the same trick .service__plus used to bottom-anchor itself
   alone; now the whole essence/name/plus cluster rides together. Rest state:
   icon top, everything else grouped at the bottom, essence directly above
   the name. Active state: the essence swaps for the blurb, which grows up
   from the title, while the tags fade in below it.

   align-self: end bottom-anchors both children inside the shared cell. The
   cell is tall enough for the longest of the two (the blurb), so without this
   the short essence would float at the cell's TOP — leaving a gap between it
   and the name instead of sitting directly above it. Anchoring both to the
   bottom also means the blurb expands upward off the title, which is the
   direction the reveal is meant to read. */
.service__body {
    display: grid;
    margin-top: auto;
}
.service__body > * { grid-area: 1 / 1; align-self: end; }
.service__essence {
    font-family: var(--font-body);
    font-size: var(--fs-paragraph);
    line-height: var(--lh-paragraph);
    color: var(--muted);
    transition: opacity var(--dur-med) var(--ease-out), transform var(--dur-med) var(--ease-out);
}
.service__detail {
    opacity: 0;
    /* Starts past the essence's resting position — roughly level with the
       name below — so the reveal reads as the blurb rising up from the
       title rather than a small local nudge. */
    transform: translateY(1.5rem);
    visibility: hidden;
    transition: opacity var(--dur-med) var(--ease-out), transform var(--dur-med) var(--ease-out),
        visibility 0s var(--dur-med);
}
.service__detail > p {
    font-family: var(--font-body);
    font-size: var(--fs-paragraph);
    line-height: var(--lh-paragraph);
    color: var(--muted);
    text-wrap: pretty;
}
.service.is-active .service__essence,
.service:focus-within .service__essence { opacity: 0; transform: translateY(-0.5rem); }
.service.is-active .service__detail,
.service:focus-within .service__detail {
    opacity: 1;
    transform: none;
    visibility: visible;
    transition-delay: 0.06s, 0.06s, 0s;
}

/* Tags stack BELOW the name. Pinned to the card's bottom inset and out of
   flow, so at rest they reserve no space and the name can sit flush at the
   bottom — then the name tweens up to make room as they fade in. (In flow they
   would reserve their height permanently, holding the name up even at rest.)
   services.js measures this element's height to know how far to lift the name;
   left/right pin to --pad so the wrap matches the card's text column exactly. */
.service__tags {
    position: absolute;
    left: var(--pad);
    right: var(--pad);
    bottom: var(--pad);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin: 0;
    padding: 0;
    list-style: none;
}
/* Hidden pre-state. Only under html.js — with no JS nothing can ever activate
   the card, so the tags must stay visible rather than be permanently lost.
   When GSAP drives the card (.is-tweened) it owns these props inline instead. */
html.js .services__lattice:not(.is-tweened) .service__tags {
    opacity: 0;
    visibility: hidden;
    transform: translateY(0.75rem);
    transition: opacity var(--dur-med) var(--ease-out), transform var(--dur-med) var(--ease-out),
        visibility 0s var(--dur-med);
}
html.js .services__lattice:not(.is-tweened) .service.is-active .service__tags,
html.js .services__lattice:not(.is-tweened) .service:focus-within .service__tags {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition-delay: 0.12s, 0.12s, 0s;
}
/* Non-GSAP path still uses the per-card --service-lift measured in
   services.js (tag height + gap). A fixed rem under-shoots two-row wraps and
   lets the headline collide with the pills. Fallback 3.25rem only if JS
   hasn't measured yet. */
html.js .services__lattice:not(.is-tweened) .service.is-active .service__body,
html.js .services__lattice:not(.is-tweened) .service.is-active .service__foot,
html.js .services__lattice:not(.is-tweened) .service:focus-within .service__body,
html.js .services__lattice:not(.is-tweened) .service:focus-within .service__foot {
    transform: translateY(calc(-1 * var(--service-lift, 3.25rem)));
}
html.js .services__lattice:not(.is-tweened) .service__body,
html.js .services__lattice:not(.is-tweened) .service__foot {
    transition: transform var(--dur-med) var(--ease-out);
}
.service__tags li,
.service-story__tags li,
.work-item__meta > span {
    padding: 0.3em 0.85em;
    border: 1px solid var(--line);
    border-radius: 100px;
    font-size: var(--fs-paragraph-sm);
    font-weight: 700;
    letter-spacing: 0.03em;
    color: var(--muted);
    white-space: nowrap;
}

/* "Learn more" rides in the tags row as its last pill — it inherits the same
   fade-in stagger as the capability tags (services.js treats every <li> in
   this list identically) but reads as an action, not a capability, so it
   drops the pill border and gets the orange link treatment instead. */
.service__tags li.service__tags-more {
    padding: 0;
    border: 0;
    border-radius: 0;
}
.service__more {
    display: inline-flex;
    align-items: center;
    gap: 0.3em;
    padding: 0.3em 0;
    background: none;
    border: 0;
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-sm);
    font-weight: 700;
    letter-spacing: 0.03em;
    color: var(--orange);
    cursor: pointer;
    white-space: nowrap;
}
.service__more span {
    display: inline-block;
    transition: transform var(--dur-fast) var(--ease-out);
}
.service__more:hover span,
.service__more:focus-visible span { transform: translateX(3px); }

/* Foot — name + plus, the card's fixed bottom row. The essence/detail swap
   sits directly above via .service__body's own margin, so the name is always
   the lowest line of text in the card, active or not. */
.service__foot {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-sm);
    margin-top: 0.8rem;
}

/* Plus marker — rotates to a minus when active */
.service__plus {
    position: relative;
    flex: none;
    width: 1rem;
    height: 1rem;
}
.service__plus::before,
.service__plus::after {
    content: '';
    position: absolute;
    inset: 50% 0 auto 0;
    height: 2px;
    background: var(--orange);
    transition: transform var(--dur-med) var(--ease-in-out);
}
.service__plus::after { transform: rotate(90deg); }
.service.is-active .service__plus::after,
.service:focus-within .service__plus::after { transform: rotate(0deg); }

@media (max-width: 1279px) {
    .services__lattice { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .service { min-height: 21rem; }
    .service:last-child { grid-column: 1 / -1; }
}
@media (max-width: 767px) {
    .services__lattice { grid-template-columns: 1fr; }
    .service { min-height: 0; }
    .service:last-child { grid-column: auto; }
    .service__essence { display: none; }
    .service__detail { opacity: 1; transform: none; visibility: visible; }
    .service__plus { display: none; }
    /* Stacked cards have no hover state — everything is expanded at once — so
       the bottom-anchored name has nothing to reveal from behind it and would
       just strand the title under its own blurb. Reorder to icon → name →
       blurb so each card still reads title-first. */
    .service__heading { order: 1; }
    .service__foot { order: 2; margin-top: 0.6rem; }
    .service__body { order: 3; margin-top: 0.6rem; }
    /* Tags rejoin the flow, last. services.js skips the GSAP build below this
       width so nothing sets competing inline styles here. */
    .service__tags {
        order: 4;
        position: static;
        margin-top: var(--space-sm);
    }
    html.js .services__lattice:not(.is-tweened) .service__tags {
        opacity: 1;
        visibility: visible;
        transform: none;
        transition: none;
    }
    /* Nothing to lift — the name is not bottom-anchored in the stacked card. */
    html.js .services__lattice:not(.is-tweened) .service.is-active .service__body,
    html.js .services__lattice:not(.is-tweened) .service.is-active .service__foot,
    html.js .services__lattice:not(.is-tweened) .service:focus-within .service__body,
    html.js .services__lattice:not(.is-tweened) .service:focus-within .service__foot {
        transform: none;
    }
}

/* ============================================================
   BUFFERS — kinetic section titles (Figma ghost words, performed)
   Default = the comp's static ghost-word band (mobile / reduced
   motion / no JS). buffers.js adds .is-live on capable desktops:
   the band grows to 150vh with a sticky 100svh frame and scroll
   drives an SVG CAMERA ZOOM (viewBox scrub = vector-crisp type):
   "process." rotates 90°→0 while the camera flies into a letter's
   ink until the glyph interior fills the screen in --deep — the
   letterform is the wipe into How We Work; "clients." exits
   through the letter gaps onto white. Orange glyph dust sheds at
   the climax (canvas). Scroll never locks.
   ============================================================ */
.buffer {
    position: relative;
    padding: var(--space-xl) var(--gutter);
    background: var(--paper);
    overflow: hidden;
}
.buffer__stick {
    display: grid;
    place-items: center;
}
.buffer__word {
    margin: 0;
    font-family: var(--font-display);
    font-size: clamp(6rem, 18vw, 16rem);
    font-weight: var(--wt-display);
    letter-spacing: -0.05em;
    line-height: 1;
    white-space: nowrap;
    user-select: none;
    will-change: transform, opacity;
}
.buffer--through .buffer__word {
    color: transparent;
    -webkit-text-stroke: 2px rgba(12, 93, 136, 0.3);
}
.buffer__dust,
.buffer__svg { display: none; }

/* ---- Kinetic mode (added by buffers.js on desktop + motion) ----
   The live word is the SVG camera (vector-crisp at any zoom); the
   static ghost <p> above is the fallback and hides here. */
.buffer.is-live {
    height: 100svh;      /* GSAP pins this full-viewport section; no sticky frame */
    padding: 0;
}
.buffer.is-live .buffer__stick {
    position: relative;
    height: 100svh;
    overflow: hidden;
}
.buffer.is-live .buffer__word { display: none; }

/* ── Process wipe (What We Do → How We Work) — Figma 46:769 ──────────
   Three stacked words in one solid --deep, tight negative leading so the
   lines nearly touch, bleeding off both edges. Default (no JS / mobile /
   reduced motion) is the plain static stack; buffers.js adds .is-live and
   drives the pinned choreography (outer words drift at different rates,
   middle word pans then the viewBox camera dives into its target letter).
   The middle word is SVG so the dive re-renders as vectors — a CSS scale
   would magnify a cached bitmap and go jagged. ── */
.buffer--wipe {
    /* Height-driven: three lines at --bw-lead apart span ~(2 · lead + x-height)
       ≈ 1.0 × the size below, so the stack fills the viewport and bleeds off the
       top/bottom edges like the comp. The words are then far wider than the
       screen, which is what makes them run off both sides. */
    --bw-size: 66vh;
    --bw-lead: 0.53;                  /* Figma baseline step 240/450 */
    background: transparent;          /* no fill of its own — see through to the page */
    display: grid;
    justify-items: center;
    align-content: center;
}
.buffer__line,
.buffer__zoom-text {
    font-family: var(--font-display);
    font-size: var(--bw-size);
    font-weight: var(--wt-display);
    letter-spacing: -0.05em;
    line-height: 1;
}
.buffer__line {
    margin: 0;
    color: var(--deep);
    white-space: nowrap;
    user-select: none;
}
.buffer__zoom-text { fill: var(--deep); }
.buffer__zoom {
    display: block;
    width: 100%;
    height: calc(var(--bw-size) * 1.05);   /* static fallback: one line band */
}

/* Kinetic: the lines become absolutely-placed layers (GSAP owns their
   transforms) and the SVG camera covers the whole pinned viewport so the
   flooding glyph can fill it edge to edge. Same colour throughout, so the
   overlapping lines read as one mass and z-order never shows a seam. */
.buffer--wipe.is-live { display: block; position: relative; }
.buffer--wipe.is-live .buffer__line {
    position: absolute;
    left: 50%;
    top: 50%;
    z-index: 1;
    will-change: transform;
}
.buffer--wipe.is-live .buffer__zoom {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: 2;                       /* the dive floods over the drifting words */
}

.buffer.is-live .buffer__svg {
    display: block;
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: 0;
}
.buffer__svg text {
    font-family: var(--font-display);
    font-weight: var(--wt-display);
    /* normal letter-spacing on purpose — buffers.js maps an offscreen
       canvas raster onto this text bbox-to-bbox to find the camera
       target, and canvas metrics don't apply CSS letter-spacing */
}
.buffer--through .buffer__svg text {
    fill: none;
    stroke: var(--blue);
    stroke-width: 2;
}
.buffer.is-live .buffer__dust {
    display: block;
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: 2;
    pointer-events: none;
}

/* ============================================================
   HOW WE WORK — scroll-scrubbed depth stage (no pin)
   The head (WWD system: centered kicker / headline-lg / sub)
   lives in normal flow and scrolls away; the sticky stage-wrap
   then owns the full 100svh with nav + 3D stage vertically
   centered (Jeff-approved: head may leave the screen so the
   body breathes). Default layout is the stacked fallback.
   process.js adds .is-3d on capable desktops: front card
   presented, queue receding right-and-deep, passed cards
   slipping past the viewer. Scroll never locks.
   ============================================================ */
.process {
    background: var(--deep);
    color: var(--paper);
    padding-block: var(--space-2xl);
}
.process__head {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding-inline: var(--gutter);
    margin-bottom: var(--space-xl);
}
.process__headline {
    max-width: 13em;
    margin-block: var(--space-md) var(--space-sm);
    font-family: var(--font-display);
    font-size: var(--fs-headline-lg);
    font-weight: var(--wt-display);
    letter-spacing: var(--tr-headline-lg);
    line-height: var(--lh-headline-lg);
    color: var(--paper);
    text-wrap: balance;
}
.process__sub {
    max-width: 34em;
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-lg);
    line-height: var(--lh-paragraph-lg);
    color: rgba(255, 255, 255, 0.75);
    text-wrap: pretty;
}

/* ── Process steps — the showcase compression mechanism as a CLICK accordion.
   Each panel is flex-basis:0; the open one flex-grows to the stage share while
   the rest hold a fixed spine width. The spine (number → icon → vertical label)
   stays a narrow left column; on open the description eases in to its right
   behind a dashed cut-line. Dark-section palette (labels stay white). ── */
.psteps {
    max-width: calc(var(--container) + 2 * var(--gutter));
    width: 100%;
    margin: var(--space-xl) auto 0;
    padding-inline: var(--gutter);
}
.psteps__track {
    display: flex;
    align-items: stretch;
    margin: 0;
    padding: 0;
    list-style: none;
    min-height: min(64svh, 40rem);
    border: 1px solid rgba(245, 245, 245, 0.16);
    border-radius: var(--radius);
    overflow: hidden;
}
.pstep {
    position: relative;
    display: flex;
    align-items: stretch;
    /* Fixed basis = the spine width, so a closed panel is a sliver regardless of
       its body text; the open one flex-grows to fill. (auto basis would size
       each panel to its text and break the compression.) */
    flex: 0 1 4.5rem;
    min-width: 0;
    border-right: 1px solid rgba(245, 245, 245, 0.14);
    transition: flex-grow var(--dur-slow, 0.55s) var(--ease-out, ease);
}
.pstep:last-child { border-right: 0; }
.pstep.is-open { flex-grow: 1; }       /* open = fill remaining (stage share) */

/* Spine — the always-visible column; also the accordion's button. Compressed
   when closed; widens to ~1.8x on open (GSAP animates the width; these are the
   no-GSAP fallback states). */
.pstep__spine {
    position: relative;
    flex: 0 0 auto;
    width: 4.5rem;
    transition: width var(--dur-slow, 0.55s) var(--ease-out, ease),
                background-color var(--dur-fast, 0.2s) var(--ease-out, ease);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-md) 0;
    background: none;
    border: 0;
    cursor: pointer;
    color: var(--paper);
    font: inherit;
    -webkit-tap-highlight-color: transparent;
}
.pstep__spine:focus-visible { outline: 2px solid var(--orange); outline-offset: -3px; }

/* Hover — the click affordance, on CLOSED spines only (the open one isn't a
   click target): fill orange, reverse the marks to white. */
.pstep:not(.is-open) .pstep__spine:hover { background: var(--orange); }
.pstep:not(.is-open) .pstep__spine:hover .pstep__num,
.pstep:not(.is-open) .pstep__spine:hover .pstep__icon,
.pstep:not(.is-open) .pstep__spine:hover .pstep__label { color: #fff; }

/* Active (open) — subtle brand accent, no fill: orange title + a short orange
   indicator bar at the top of the spine. */
.pstep.is-open .pstep__label { color: var(--orange); }
.pstep.is-open .pstep__spine::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 3px;
    background: var(--orange);
}
.pstep__num {
    font-family: var(--font-mono, ui-monospace, monospace);
    font-size: 0.8rem;
    letter-spacing: 0.06em;
    color: rgba(245, 245, 245, 0.45);
    font-variant-numeric: tabular-nums;
    transition: color var(--dur-fast, 0.2s) var(--ease-out, ease);
}
.pstep.is-open .pstep__num { color: rgba(245, 245, 245, 0.7); }
.pstep__icon {
    display: block;
    width: 1.9rem;
    height: 1.9rem;
    color: var(--orange);
    transition: transform var(--dur-mid, 0.45s) var(--ease-out, ease);
}
.pstep.is-open .pstep__spine { width: 8.8rem; }   /* no-GSAP fallback */
.pstep.is-open .pstep__icon { transform: scale(1.46); }
/* Outer label = bottom-anchored box + colour; GSAP scales THIS from its base
   (transform-origin bottom) so the title grows upward and its baseline stays
   inline with the closed titles. The rotation lives on the inner span so the
   scale origin doesn't fight it. */
.pstep__label {
    margin-top: auto;                  /* bottom-anchored */
    color: var(--paper);
}
.pstep__label-t {
    display: inline-block;
    writing-mode: vertical-rl;
    transform: rotate(180deg);         /* reads bottom → top */
    font-family: var(--font-display);
    font-weight: var(--wt-display);
    font-size: clamp(1.05rem, 1.3vw, 1.28rem);
    line-height: 1.1;
    letter-spacing: 0.005em;
    white-space: nowrap;
}
/* Body — the description; revealed only when the panel is open */
.pstep__body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
    overflow: hidden;
    padding: 0;
    opacity: 0;
    transition: opacity var(--dur-mid, 0.4s) var(--ease-out, ease) 0.08s;
}
.pstep.is-open .pstep__body {
    padding: var(--space-lg) var(--space-xl) var(--space-lg) var(--space-lg);
    border-left: 1px dashed rgba(245, 245, 245, 0.28);
    opacity: 1;
}
.pstep__text {
    margin: 0;
    max-width: 40em;
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-lg);
    line-height: var(--lh-paragraph-lg);
    color: rgba(245, 245, 245, 0.72);
}

/* When GSAP drives the accordion it owns these via inline styles — disable the
   CSS transitions so the two don't fight, and keep bodies rendered (the text's
   own tween handles the fade, so closed panels just carry hidden text). */
.psteps__track.is-gsap .pstep,
.psteps__track.is-gsap .pstep__body,
.psteps__track.is-gsap .pstep__text { transition: none; }
/* Spine keeps only its background fade (GSAP owns the width); icon + label keep
   only a colour fade (GSAP owns their scale) — so hover/active colours animate. */
.psteps__track.is-gsap .pstep__spine { transition: background-color var(--dur-fast, 0.2s) var(--ease-out, ease); }
.psteps__track.is-gsap .pstep__icon,
.psteps__track.is-gsap .pstep__label { transition: color var(--dur-fast, 0.2s) var(--ease-out, ease); }
.psteps__track.is-gsap .pstep__body { opacity: 1; }

/* Small screens: vertical tap accordion. Tabs stack; the open
   panel's copy expands downward under its spine. No-JS shows every
   description; with JS (html.js) it is single-open (first by default). */
@media (max-width: 1023px) {
    .psteps__track {
        flex-direction: column;
        min-height: 0;
    }
    .pstep {
        flex: 0 0 auto;
        flex-direction: column;
        width: 100%;
        border-right: 0;
        border-bottom: 1px solid rgba(245, 245, 245, 0.14);
    }
    .pstep:last-child { border-bottom: 0; }
    .pstep.is-open { flex-grow: 0; }
    .pstep__spine,
    .pstep.is-open .pstep__spine {
        flex-direction: row;
        width: 100%;
        align-items: center;
        justify-content: flex-start;
        gap: var(--space-sm);
        padding: var(--space-md);
    }
    .pstep.is-open .pstep__icon { transform: none; }
    .pstep__label { margin: 0; }
    .pstep__label-t {
        writing-mode: horizontal-tb;
        transform: none;
    }
    .pstep__body,
    .pstep.is-open .pstep__body {
        flex: 0 0 auto;
        width: 100%;
        align-items: flex-start;
        opacity: 1;
        padding: 0 var(--space-md) var(--space-md);
        border-left: 0;
        overflow: hidden;
    }
    /* Closed rows hide the copy; open rows reveal it under the tab. */
    html.js .pstep__body {
        display: grid;
        grid-template-rows: 0fr;
        padding-block: 0;
        transition: grid-template-rows 0.35s var(--ease-out, ease),
                    padding 0.35s var(--ease-out, ease);
    }
    html.js .pstep.is-open .pstep__body {
        display: grid;
        grid-template-rows: 1fr;
        padding: 0 var(--space-md) var(--space-md);
    }
    html.js .pstep__body > * {
        min-height: 0;
        overflow: hidden;
    }
}

/* ============================================================
   LOOK AT US — pinned stage/sliver showcase
   (spec: HANDOFF-LOOK-AT-US.md · comp: Figma 918:11408)
   Default layout is a native horizontal swipe row (mobile /
   reduced motion / no JS). work-showcase.js adds .is-pinned on
   capable desktops: the frame pins at 100svh and scroll morphs
   card widths continuously — active card ≈ 70% (media + docked
   panel), the rest compress to accent-tinted slivers (ghost
   numeral + rotated name) behind dashed cut-lines. JS drives
   flex-basis + --compress per card, --stage-accent + --sp on
   the frame. Media = gray placeholder + monogram for now.
   ============================================================ */
.showcase {
    padding: var(--space-2xl) 0;
    background: var(--paper);
}
.showcase__head {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding-inline: var(--gutter);
}
.showcase__headline {
    max-width: 13em;
    margin-block: var(--space-md) var(--space-sm);
    font-family: var(--font-display);
    font-size: var(--fs-headline-lg);
    font-weight: var(--wt-display);
    letter-spacing: var(--tr-headline-lg);
    line-height: var(--lh-headline-lg);
    color: var(--blue);
    text-wrap: balance;
}
.showcase__headline em { font-style: normal; color: var(--orange); }
.showcase__sub {
    max-width: 34em;
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-lg);
    line-height: var(--lh-paragraph-lg);
    color: var(--muted);
    text-wrap: pretty;
}

/* ---- Default: native swipe row ---- */
.showcase__frame { position: relative; margin-top: var(--space-xl); }
.showcase__track {
    display: flex;
    gap: var(--space-sm);
    padding-inline: var(--gutter);
    overflow-x: auto;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
}
.showcase-card {
    position: relative;
    flex: 0 0 auto;
    width: min(26rem, 82vw);
    display: flex;
    flex-direction: column;
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
    scroll-snap-align: center;
}
.showcase-card__media {
    position: relative;
    aspect-ratio: 4 / 3;
    display: grid;
    place-items: center;
    background: var(--light-gray); /* gray placeholder until real assets */
}
.showcase-card__mono {
    font-family: var(--font-display);
    font-size: clamp(2rem, 3vw, 3rem);
    font-weight: var(--wt-display);
    letter-spacing: 0.05em;
    color: color-mix(in srgb, var(--card-accent) 45%, var(--light-gray));
    user-select: none;
}
.showcase-card__panel {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    padding: 1.4rem 1.5rem 1.6rem;
}
.showcase-card__specs {
    display: flex;
    flex-wrap: wrap;
    gap: 0.45rem;
    margin: 0;
    padding: 0;
    list-style: none;
}
.showcase-card__specs li {
    padding: 0.28em 0.8em;
    border: 1px solid color-mix(in srgb, var(--card-accent) 40%, transparent);
    border-radius: 100px;
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-sm);
    font-weight: 700;
    letter-spacing: 0.03em;
    color: var(--muted);
    white-space: nowrap;
}
.showcase-card__title {
    margin-top: 1rem;
    font-family: var(--font-display);
    font-size: var(--fs-card-title);
    font-weight: var(--wt-display);
    letter-spacing: var(--tr-card-title);
    line-height: var(--lh-card-title);
    color: var(--dark-gray);
}
.showcase-card__excerpt {
    margin-top: 0.6rem;
    font-family: var(--font-body);
    font-size: var(--fs-paragraph);
    line-height: var(--lh-paragraph);
    color: var(--muted);
    text-wrap: pretty;
}
.showcase-card__more {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 1rem;
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-sm);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--orange);
    text-decoration: none;
}
.showcase-card__more-arrow { transition: transform var(--dur-fast) var(--ease-out); }
.showcase-card__more:hover .showcase-card__more-arrow,
.showcase-card__more:focus-visible .showcase-card__more-arrow { transform: translateX(0.3em); }
.showcase-card__sliver { display: none; }
.showcase__meta,
.showcase__progress { display: none; }

/* ---- Pinned stage/sliver mode (.is-pinned, desktop + motion) ---- */
.showcase.is-pinned .showcase__frame {
    height: 100svh;
    margin-top: var(--space-md);
    overflow: hidden;
}
.showcase.is-pinned .showcase__track {
    height: 100%;
    gap: 0;
    padding-inline: 0;
    overflow: visible;
    scroll-snap-type: none;
}
.showcase.is-pinned .showcase-card {
    /* JS drives flex-basis (%) + --compress (0 stage → 1 sliver) */
    flex: 0 0 auto;
    width: auto;
    min-width: 0;
    height: 100%;
    border: 0;
    border-left: 1px dashed var(--line); /* the comp's dashed cut-lines */
    border-radius: 0;
    cursor: pointer;
}
.showcase.is-pinned .showcase-card:first-child { border-left: 0; }
.showcase.is-pinned .showcase-card.is-active { cursor: default; }
.showcase.is-pinned .showcase-card__media {
    position: absolute;
    inset: 0;
    aspect-ratio: auto;
    opacity: calc(1 - var(--compress, 0));
}
.showcase.is-pinned .showcase-card__mono {
    font-size: clamp(3rem, 7vw, 6.5rem);
}
.showcase.is-pinned .showcase-card__panel {
    position: absolute;
    left: clamp(1.5rem, 3vw, 3.5rem);
    bottom: clamp(1.5rem, 3.5vh, 3rem);
    width: min(27rem, 72%);
    padding: clamp(1.4rem, 1.8vw, 2rem);
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    opacity: calc(1 - var(--compress, 0));
    transform: translateY(calc(var(--compress, 0) * 1.5rem));
}
.showcase.is-pinned .showcase-card:not(.is-active) .showcase-card__panel { pointer-events: none; }
.showcase.is-pinned .showcase-card__sliver {
    display: flex;
    position: absolute;
    inset: 0;
    flex-direction: column;
    justify-content: space-between;
    align-items: center;
    padding: 1.2rem 0 1.4rem;
    background: color-mix(in srgb, var(--card-accent) 7%, var(--paper));
    opacity: var(--compress, 0);
    pointer-events: none;
}
.showcase-card__num {
    font-family: var(--font-display);
    font-size: clamp(1.4rem, 2vw, 2rem);
    font-weight: var(--wt-display);
    color: color-mix(in srgb, var(--card-accent) 55%, var(--paper));
    font-variant-numeric: tabular-nums;
}
.showcase-card__name {
    writing-mode: vertical-rl;
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-sm);
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--dark-gray);
    opacity: 0.7;
    white-space: nowrap;
}
/* Wayfinding — wears the active client's accent (--stage-accent, JS) */
.showcase.is-pinned .showcase__meta {
    display: flex;
    align-items: baseline;
    gap: 0.35rem;
    position: absolute;
    left: var(--gutter);
    bottom: 1.1rem;
    z-index: 6;
    margin: 0;
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-sm);
    font-weight: 700;
    letter-spacing: 0.1em;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}
.showcase__meta [data-showcase-index] { color: var(--stage-accent, var(--orange)); }
.showcase__meta-client { margin-left: 0.6rem; text-transform: uppercase; color: var(--dark-gray); }
.showcase.is-pinned .showcase__progress {
    display: block;
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 2px;
    background: var(--stage-accent, var(--orange));
    transform: scaleX(var(--sp, 0));
    transform-origin: left;
    z-index: 6;
}

/* ============================================================
   WORK — editorial case list
   ============================================================ */
.work {
    max-width: calc(var(--container) + 2 * var(--gutter));
    margin-inline: auto;
    padding: var(--space-2xl) var(--gutter);
}
.work__headline {
    max-width: 14em;
    margin-block: var(--space-md) var(--space-xl);
    font-size: var(--text-display);
    letter-spacing: var(--tracking-display);
    line-height: 1.06;
    text-wrap: balance;
}
.work-item { border-top: 1px solid var(--line); }
.work-item:last-child { border-bottom: 1px solid var(--line); }
.work-item__link {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
    padding: clamp(1.4rem, 3vw, 2.4rem) 0.25rem;
    text-decoration: none;
}
.work-item__title {
    font-size: var(--text-title);
    font-weight: 900;
    letter-spacing: -0.02em;
    line-height: 1.12;
    transition: transform var(--dur-med) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.work-item__meta {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 0.5rem;
    flex-shrink: 0;
    max-width: 22rem;
}
.work-item__link:hover .work-item__title,
.work-item__link:focus-visible .work-item__title {
    color: var(--orange);
    transform: translateX(0.3em);
}

/* Cursor-following preview card (desktop pointer only, JS-driven) */
.work__preview {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 50;
    display: grid;
    place-items: center;
    width: clamp(14rem, 22vw, 20rem);
    aspect-ratio: 4 / 3;
    border-radius: 4px;
    pointer-events: none;
    opacity: 0;
    transform: scale(0.9);
    overflow: hidden;
}
.work__preview[data-accent="orange"] { background: var(--orange); }
.work__preview[data-accent="blue"] { background: var(--blue); }
.work__preview[data-accent="ink"] { background: var(--ink); }
.work__preview-text {
    padding: var(--space-sm);
    font-size: 1.05rem;
    font-weight: 900;
    letter-spacing: -0.01em;
    line-height: 1.15;
    color: var(--paper);
    text-align: center;
    text-wrap: balance;
}
@media (hover: none), (max-width: 1023px) {
    .work__preview { display: none; }
}

@media (max-width: 720px) {
    .work-item__link { flex-direction: column; gap: 0.4rem; align-items: flex-start; }
    .work-item__meta { justify-content: flex-start; max-width: none; }
}

/* ============================================================
   CLIENTS
   ============================================================ */
.clients {
    max-width: calc(var(--container) + 2 * var(--gutter));
    margin-inline: auto;
    padding: var(--space-2xl) var(--gutter);
    background: var(--paper);
}
/* Head matches the What We Do system: centered kicker + headline-lg */
.clients__head {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
}
.clients__headline {
    max-width: 13em;
    margin-block: var(--space-md) var(--space-xl);
    font-family: var(--font-display);
    font-size: var(--fs-headline-lg);
    font-weight: var(--wt-display);
    letter-spacing: var(--tr-headline-lg);
    line-height: var(--lh-headline-lg);
    color: var(--blue);
    text-wrap: balance;
}
.clients__grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: clamp(2.5rem, 6vw, 5.5rem) var(--space-lg);
    align-items: center;
}
.clients__grid li { display: grid; place-items: center; }
.clients__grid img {
    max-height: 3.4rem;
    width: auto;
    max-width: min(100%, 11rem);
    /* The source SVGs carry inconsistent intrinsic colours (several are
       light-gray gradients), so force uniform full-strength silhouettes:
       brightness(0) flattens to black, invert(0.19) lifts to #303030.
       True brand-colour hover needs recoloured source files — asset pass. */
    filter: brightness(0) invert(0.19);
    opacity: 1;
    transition: filter var(--dur-med) var(--ease-out);
}
.clients__grid li:hover img { filter: brightness(0); }

@media (max-width: 900px) { .clients__grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 600px) { .clients__grid { grid-template-columns: repeat(2, 1fr); } }

/* ------------------------------------------------------------
   CLIENTS — split variant (Variant B)
   Editorial two-column composition: intro/support paragraph on the
   left, monochrome logo grid on the right. Shares the silhouette
   logo treatment with the classic centered variant above so the
   credibility read stays identical — only the composition changes.
   ------------------------------------------------------------ */
.clients-split {
    max-width: calc(var(--container) + 2 * var(--gutter));
    margin-inline: auto;
    padding: var(--space-2xl) var(--gutter);
    background: var(--paper);
}
.clients-split__inner {
    display: grid;
    grid-template-columns: minmax(0, 6fr) minmax(0, 7fr);
    gap: clamp(2.5rem, 5vw, 5rem);
    align-items: start;
}
.clients-split__intro {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    position: sticky;
    top: calc(var(--header-h) + var(--space-md));
    overflow: visible; /* don't clip the word-reveal mask / caret */
}
.clients-split__headline {
    font-family: var(--font-display);
    /* Split variant scales its display type down from --fs-headline so all
       three structurally-broken lines fit comfortably in the narrower left
       column. Range: 32px @ mobile → 56px @ 1440. */
    font-size: clamp(2rem, 1.5rem + 2.2vw, 3.5rem);
    font-weight: var(--wt-display);
    letter-spacing: var(--tr-headline);
    line-height: var(--lh-headline);
    color: var(--blue);
    margin: 0;
}
.clients-split__headline em {
    font-style: normal;
    color: var(--orange);
}
.clients-split__lede {
    font-family: var(--font-secondary);
    font-weight: 400;
    font-size: var(--fs-paragraph-lg);
    line-height: var(--lh-paragraph-lg);
    color: var(--ink);
    max-width: 36ch;
}
.clients-split__grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: clamp(2.75rem, 6vw, 5rem) var(--space-lg);
    align-items: center;
}
.clients-split__grid li { display: grid; place-items: center; }
.clients-split__grid img {
    max-height: 3.6rem;
    width: auto;
    max-width: min(100%, 11rem);
    filter: brightness(0) invert(0.19);
    opacity: 1;
    transition: filter var(--dur-med) var(--ease-out);
}
.clients-split__grid li:hover img { filter: brightness(0); }

@media (max-width: 1100px) {
    .clients-split__inner {
        grid-template-columns: minmax(0, 4fr) minmax(0, 6fr);
        gap: clamp(2.5rem, 5vw, 4rem);
    }
}
@media (max-width: 900px) {
    .clients-split__inner {
        grid-template-columns: 1fr;
        gap: var(--space-xl);
    }
    .clients-split__intro {
        position: static;
        max-width: 42rem;
    }
    .clients-split__grid { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 600px) {
    .clients-split__grid { grid-template-columns: repeat(2, 1fr); }
}

/* ============================================================
   TESTIMONIALS
   Case-study pull-quote treatment on the homepage: display-size type,
   left-aligned, quiet attribution. Client rail under the quote is the
   slider control (accent on the active tab). Long forms live in .story.
   ============================================================ */
.quotes {
    padding: var(--space-xl) var(--gutter);
    background: var(--paper);
    color: var(--ink);
}
.quotes > * { max-width: var(--container); margin-inline: auto; }

.quotes__kicker {
    max-width: 14em;
    margin-inline: auto;
    font-family: var(--font-display);
    font-size: var(--fs-headline-lg);
    font-weight: var(--wt-display);
    letter-spacing: var(--tr-headline-lg);
    line-height: var(--lh-headline-lg);
    color: var(--blue);
    text-align: center;
    text-wrap: balance;
}

/* Stage holds one quote at a time. Its height is pinned to the tallest
   testimonial (set by JS) so the page never jumps as slides change. */
.quotes__stage {
    position: relative;
    display: grid;
    align-content: start;
    margin-top: var(--space-lg);
}
.quotes:has(.quotes__kicker[hidden]) .quotes__stage {
    margin-top: 0;
}

.quote {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-md);
    margin: 0;
    text-align: left;
}

/* Exported Figma asset. Both dimensions are explicit: the SVG carries
   preserveAspectRatio="none", so a single-axis size would distort it. */
.quote__mark {
    flex: none;
    width: 75.8px;
    height: 54.6px;
}

.quote__text {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--fs-headline);
    font-weight: var(--wt-display);
    letter-spacing: var(--tr-headline);
    line-height: 1.12;
    color: var(--blue);
    text-wrap: pretty;
}
.quote__text p { margin: 0; }
.quote__text p + p { margin-top: 1em; }

.quote__name {
    margin: 0;
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-lg);
    font-weight: var(--wt-body-medium);
    line-height: 1.4;
    letter-spacing: 0;
    color: var(--muted);
}

/* Quiet text link under the attribution — light-surface version of the
   old hairline button. */
.quote__more {
    display: inline-flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0;
    border: 0;
    background: none;
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-sm);
    font-weight: var(--wt-body-bold);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--orange);
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease-out);
}
.quote__more:hover { color: var(--blue); }
.quote__more svg { transition: transform var(--dur-fast) var(--ease-out); }
.quote__more:hover svg { transform: translateX(0.3em); }

/* ---- Attribution rail ----
   Tabs split the container evenly; the active one is full-strength while the
   rest sit back. The accent slides and resizes to the active tab. */
.quotes__rail {
    position: relative;
    display: flex;
    margin-top: var(--space-lg);
    border-top: 1px solid var(--line);
}
.quotes__rail[hidden] { display: none; }

.quotes__tab {
    flex: 1 1 0;
    min-width: 0;
    margin-top: -1px;
    padding: var(--space-sm) var(--space-xs) var(--space-sm);
    text-wrap: balance;
    border: 0;
    border-top: 1px solid transparent;
    background: none;
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-sm);
    font-weight: var(--wt-body-medium);
    line-height: 1.3;
    color: var(--ink);
    text-align: center;
    cursor: pointer;
    opacity: 0.45;
    transition: opacity var(--dur-med) var(--ease-in-out);
}
.quotes__tab:hover { opacity: 0.78; }
.quotes__tab[aria-selected="true"] { opacity: 1; }

.quotes__indicator {
    position: absolute;
    top: -1px;
    left: 0;
    height: 2px;
    background: var(--orange);
    transition: transform var(--dur-slow) var(--ease-in-out),
                width var(--dur-slow) var(--ease-in-out);
}

@media (max-width: 760px) {
    /* Below the tab row's comfortable width, the rail becomes a swipeable
       track and the quote carries its own attribution. */
    .quotes__rail {
        overflow-x: auto;
        scrollbar-width: none;
        scroll-snap-type: x proximity;
    }
    .quotes__rail::-webkit-scrollbar { display: none; }
    .quotes__tab { flex: 0 0 11rem; scroll-snap-align: center; }
}

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

/* ============================================================
   STORY — long-form testimonial panel
   Wipes up from the bottom using the same clip-path language as the overlay
   menu, so "more" reads as the site opening a drawer rather than a popup.
   ============================================================ */
.story {
    position: fixed;
    inset: 0;
    z-index: 200;
    overflow-y: auto;
    overscroll-behavior: contain;
    background: var(--deep);
    color: var(--paper);
}
.story[hidden] { display: none; }
.js .story { clip-path: inset(100% 0 0 0); }

.story__inner {
    position: relative;
    max-width: 48rem;
    margin-inline: auto;
    padding: var(--space-xl) var(--gutter) var(--space-2xl);
}

.story__close {
    position: sticky;
    top: 0;
    float: right;
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.5rem 0;
    border: 0;
    background: none;
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-sm);
    font-weight: var(--wt-body-bold);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--paper);
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease-out);
}
.story__close:hover { color: var(--orange); }

.story__label {
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-sm);
    font-weight: var(--wt-body-bold);
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--orange);
}

.story__org {
    margin-top: var(--space-xs);
    font-family: var(--font-display);
    font-size: var(--fs-h3);
    font-weight: var(--wt-display);
    line-height: var(--lh-h3);
    letter-spacing: -0.02em;
    color: var(--paper);
    text-wrap: balance;
}

.story__body {
    margin-top: var(--space-lg);
    font-family: var(--font-body);
    /* A step below the pull-quote: these run 150–270 words, and 24px turns
       that into four screens of scrolling. 17→20px keeps ~60 characters. */
    font-size: clamp(1.0625rem, 1rem + 0.35vw, 1.25rem);
    line-height: 1.65;
    color: rgba(245, 245, 245, 0.88);
    text-wrap: pretty;
}
.story__body p + p { margin-top: 1.1em; }

.story__name {
    margin-top: var(--space-lg);
    padding-top: var(--space-md);
    border-top: 1px solid var(--line-dark);
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-lg);
    font-weight: var(--wt-body-bold);
    letter-spacing: -0.02em;
    color: var(--light-gray);
}

/* ============================================================
   SERVICE STORY — "What We Do" detail panel
   Same wipe-drawer contract as .story above (clip-path, focus trap, Escape)
   but on the paper palette, and it doesn't always wipe from the bottom edge —
   js/modules/service-story.js computes the clicked card's own rect and
   animates the panel's clip-path growing out of (and, on close, collapsing
   back into) that exact position. Reads as the card opening up rather than a
   generic overlay appearing. Content is placeholder until each service has a
   real long-form write-up.
   ============================================================ */
.service-story {
    position: fixed;
    inset: 0;
    z-index: 200;
    overflow-y: auto;
    overscroll-behavior: contain;
    background: var(--paper);
    color: var(--dark-gray);
}
.service-story[hidden] { display: none; }
/* Inert baseline only — service-story.js overwrites this with the origin
   card's on-screen rect the moment the panel opens. Matching .story's
   bottom-wipe shape so a no-JS / reduced-motion flash never reads as a
   tiny centered diamond. */
.js .service-story { clip-path: inset(100% 0 0 0); }

.service-story__inner {
    position: relative;
    max-width: 44rem;
    margin-inline: auto;
    padding: var(--space-xl) var(--gutter) var(--space-2xl);
}

.service-story__close {
    position: sticky;
    top: 0;
    float: right;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.5rem 0;
    border: 0;
    background: var(--paper);   /* so sticky scroll doesn't show type through it */
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-sm);
    font-weight: var(--wt-body-bold);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--dark-gray);
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease-out);
}
.service-story__close:hover { color: var(--orange); }

.service-story__eyebrow {
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-sm);
    font-weight: var(--wt-body-bold);
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--orange);
}

.service-story__icon {
    display: block;
    width: 3.4rem;
    height: 3.4rem;
    margin-top: var(--space-md);
    color: var(--orange);
}
.service-story__icon svg { width: 100%; height: 100%; }

.service-story__name {
    margin-top: var(--space-sm);
    font-family: var(--font-display);
    font-size: var(--fs-h3);
    font-weight: var(--wt-display);
    line-height: var(--lh-h3);
    letter-spacing: -0.02em;
    color: var(--blue);
    text-wrap: balance;
}

.service-story__body {
    margin-top: var(--space-lg);
    font-family: var(--font-body);
    font-size: clamp(1.0625rem, 1rem + 0.35vw, 1.25rem);
    line-height: 1.65;
    color: var(--muted);
    text-wrap: pretty;
}
.service-story__body p + p { margin-top: 1.1em; }

.service-story__tags {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin: var(--space-lg) 0 0;
    padding: 0;
    list-style: none;
}

.service-story__case {
    margin-top: var(--space-lg);
    padding: var(--space-sm) var(--space-md);
    border: 1px dashed var(--line);
    border-radius: var(--radius);
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-sm);
    line-height: 1.6;
    color: var(--muted);
}
.service-story__case strong { color: var(--dark-gray); }
.service-story__case a { color: var(--orange); text-decoration: underline; text-underline-offset: 0.15em; }

/* Prev / Next — browse the full service set without closing the drawer.
   Wraps end-to-end (09 → 01) so the lattice reads as one continuous set. */
.service-story__nav {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--gap-sm);
    margin-top: var(--space-xl);
    padding-top: var(--space-md);
    border-top: 1px solid var(--line);
}
.service-story__dir {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    min-width: 0;
    padding: 0.6rem 0;
    border: 0;
    background: none;
    text-align: left;
    color: var(--dark-gray);
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease-out);
}
.service-story__dir--next { justify-content: flex-end; text-align: right; }
.service-story__dir:hover,
.service-story__dir:focus-visible { color: var(--orange); }
.service-story__dir-arrow {
    flex: none;
    font-size: 1.1rem;
    line-height: 1;
    transition: transform var(--dur-fast) var(--ease-out);
}
.service-story__dir:hover .service-story__dir-arrow,
.service-story__dir:focus-visible .service-story__dir-arrow {
    transform: translateX(-3px);
}
.service-story__dir--next:hover .service-story__dir-arrow,
.service-story__dir--next:focus-visible .service-story__dir-arrow {
    transform: translateX(3px);
}
.service-story__dir-meta {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;
}
.service-story__dir-label {
    font-family: var(--font-body);
    font-size: var(--fs-paragraph-sm);
    font-weight: var(--wt-body-bold);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--muted);
}
.service-story__dir:hover .service-story__dir-label,
.service-story__dir:focus-visible .service-story__dir-label { color: inherit; }
.service-story__dir-name {
    font-family: var(--font-display);
    font-size: var(--fs-paragraph);
    font-weight: var(--wt-display);
    letter-spacing: -0.01em;
    line-height: 1.25;
    color: inherit;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

@media (max-width: 575px) {
    .service-story__nav { grid-template-columns: 1fr; }
    .service-story__dir--next { justify-content: flex-start; text-align: left; }
}

/* ============================================================
   CONTACT
   ============================================================ */
.contact {
    background: var(--deep);
    color: var(--paper);
    padding: var(--space-2xl) var(--gutter) var(--space-xl);
}
.contact > * { max-width: var(--container); margin-inline: auto; }
.contact__headline {
    margin-top: var(--space-md);
    font-size: clamp(2.8rem, 8vw, 7.5rem);
    letter-spacing: var(--tracking-display);
    line-height: 1;
}
.contact__sub {
    margin-top: var(--space-sm);
    font-size: var(--text-lead);
    color: var(--muted-dark);
    max-width: var(--measure);
}

.contact-form { margin-top: var(--space-xl); }
.contact-form__hp { position: absolute; left: -9999px; }
.contact-form__grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-md) var(--space-lg);
    margin-bottom: var(--space-md);
}
.contact-form__services {
    margin: var(--space-md) 0;
    padding: 0;
    border: 0;
}
.contact-form__services legend {
    margin-bottom: var(--space-sm);
    font-size: var(--text-small);
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--muted-dark);
}
.form-field--full { margin-top: var(--space-sm); }

.contact-form__actions {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    margin-top: var(--space-lg);
}
.contact-form__status {
    display: inline-flex;
    align-items: center;
    gap: 0.65em;
    font-size: var(--text-small);
    color: var(--muted-dark);
    min-height: 1.4em;
}
.contact-form__status.is-success { color: #7fd6a4; }
.contact-form__status.is-error { color: #ff8a70; }
.contact-form__loader {
    display: none;
    width: 1.1em;
    height: 1.1em;
    flex-shrink: 0;
    border: 2px solid rgba(255, 122, 47, 0.25);
    border-top-color: var(--orange);
    border-radius: 50%;
    animation: contact-loader-spin 0.7s linear infinite;
}
.contact-form.is-sending .contact-form__loader { display: block; }
.contact-form.is-sending .contact-form__status {
    color: rgba(255, 255, 255, 0.78);
}

@keyframes contact-loader-spin {
    to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    .contact-form__loader { animation: none; border-color: var(--orange); }
}

@media (max-width: 720px) {
    .contact-form__grid { grid-template-columns: 1fr; }
    .contact-form__actions { flex-direction: column; align-items: flex-start; }
}

/* ============================================================
   FOOTER
   ============================================================ */
.site-footer {
    background: var(--deep);
    color: var(--muted-dark);
    padding: var(--space-lg) var(--gutter) var(--space-md);
    border-top: 1px solid var(--line-dark);
}
.site-footer__grid {
    display: grid;
    grid-template-columns: 1fr auto auto;
    gap: var(--space-lg);
    align-items: start;
    max-width: var(--container);
    margin-inline: auto;
}
.site-footer__logo { width: 10rem; opacity: 0.9; }
.site-footer__contact {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    font-style: normal;
    font-size: var(--text-small);
}
.site-footer__contact a { color: var(--paper); text-decoration: none; }
.site-footer__contact a:hover { color: var(--orange); }
.site-footer__email { font-weight: 700; }
.site-footer__email span { color: var(--muted-dark); }
.site-footer__nav {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    font-size: var(--text-small);
}
.site-footer__nav a { color: var(--muted-dark); text-decoration: none; }
.site-footer__nav a:hover { color: var(--paper); }
.site-footer__legal {
    max-width: var(--container);
    margin: var(--space-lg) auto 0;
    padding-top: var(--space-sm);
    border-top: 1px solid var(--line-dark);
    font-size: var(--text-label);
}

@media (max-width: 720px) {
    .site-footer__grid { grid-template-columns: 1fr; }
}
