
  /* Archivo is served from this folder, not Google Fonts (14 Sep): one fewer
     render-blocking third-party stylesheet, and the file is preloaded from
     <head> so the headings are set in Archivo at first paint instead of
     swapping in later and re-wrapping the copy beneath them. */
  @font-face{font-family:"Archivo";font-style:normal;font-display:swap;font-weight:100 900;font-stretch:62% 125%;src:url("assets/fonts/archivo-variable-latin.woff2") format("woff2")}
  @font-face{font-family:"Inter";font-style:normal;font-display:swap;font-weight:300;src:url("assets/fonts/inter-latin-300-normal.woff2") format("woff2")}
  @font-face{font-family:"Inter";font-style:normal;font-display:swap;font-weight:400;src:url("assets/fonts/inter-latin-400-normal.woff2") format("woff2")}
  @font-face{font-family:"Inter";font-style:normal;font-display:swap;font-weight:500;src:url("assets/fonts/inter-latin-500-normal.woff2") format("woff2")}
  @font-face{font-family:"Inter";font-style:normal;font-display:swap;font-weight:600;src:url("assets/fonts/inter-latin-600-normal.woff2") format("woff2")}
  @font-face{font-family:"Inter";font-style:normal;font-display:swap;font-weight:700;src:url("assets/fonts/inter-latin-700-normal.woff2") format("woff2")}

  /* ============================================================
     HIGHLANDS — V2
     A scroll-narrative build. Same brand, same photography, same
     copy as the shipped site; what changes is that one idea owns
     one screen, and the page advances at the reader's pace.

     THREE MECHANICS, and only three. Everything on this page is
     one of them, so there is one thing to learn and one thing to
     debug:
       1. PINNED CHAPTER  — a visual holds while copy steps past it
       2. SEQUENCED REVEAL — lines arrive in order, not all at once
       3. COUNTED GRID     — figures resolve as the grid enters

     ⚠ ONE rAF LOOP drives every pinned chapter on the page. Do not
     add a scroll listener: iOS defers scroll events during momentum
     scrolling, so a pinned element keeps painting at 60fps while its
     transform only updates when an event is finally delivered, and
     the result is the judder that took three attempts to find on the
     first build.

     ⚠ TWO GROUNDS. <html class="grounds-alt"> turns on the
     alternating light chapters. Every colour on this page comes from
     a token so the two variants are one codebase.
     ============================================================ */

  :root{
    /* ⚠ ONE PHOTOGRAPH HEIGHT. Daniel, 2 Sep: "the vertical size the same
       as the Run from the slab photo, that way they all maintain the same
       height." --proj-col is the projects copy column (the scrim's --cend
       as a length); --photo-h is 16:10 of the width left beside it, and
       both the project figures and the leadership photograph are exactly
       this tall on a desktop: 522px at 1440, 696px at 1920. Root em = 16px,
       so 30em here is the 30em of the copy column set in body text. */
    --proj-col:max(42vw, calc(var(--gutter) + 30em + 26px));
    --photo-h:calc((100vw - var(--proj-col)) * .625);
    --black:#000000;
    --ground:#000000;
    /* the ground at partial alpha, for the plate's scrim ramp (the
       projects scrim is #000 at .66/.28; this one has to follow the ground
       on the paper build too) */
    --ground-66:rgba(0,0,0,.66);
    --ground-28:rgba(0,0,0,.28);
    --ink-1:#ffffff;
    --ink-2:#c2c2c2;
    --ink-3:#8a8a8a;
    --line:rgba(255,255,255,.12);
    --line-strong:rgba(255,255,255,.30);

    --gutter:40px;
    --max:clamp(1280px, 74vw, 2560px);
    --stick:62px;
    --track-label:.24em;
    --track-key:.10em;

    /* one display ladder. Ranks cannot cross because the
       multipliers never do. Do not give an element its own clamp. */
    --d1:clamp(38px,5.4vw,132px);  /* hero + contact display   */
    --d2:clamp(34px,5vw,92px);    /* chapter statements. ⚠ 92 not 104: at 96px
       (1920) "CONCRETE IS PERMANENT." broke to three lines in Archivo; measured
       with the real face at 1100-2560, 92 holds two everywhere - 13 Sep */
    --d3:clamp(26px,3.6vw,68px);  /* project + service titles */
    --d4:clamp(23px,2.6vw,44px);  /* sub-heads                */
    --d5:clamp(18px,1.5vw,24px);  /* card + step labels       */

    --ease:cubic-bezier(.16,1,.3,1);
  }
  @media(max-width:860px){:root{--gutter:20px;--stick:56px}}

  *,*::before,*::after{box-sizing:border-box}
  html{scrollbar-gutter:stable;overflow-x:clip}
  @media(prefers-reduced-motion:no-preference){html{scroll-behavior:smooth}}
  body{
    margin:0;background:var(--black);color:var(--ink-2);
    font-family:'Inter',system-ui,-apple-system,sans-serif;
    font-weight:300;line-height:1.65;
    -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
    overflow-x:clip;
  }
  img,video{display:block;max-width:100%}
  a{color:inherit}
  ::selection{background:#fff;color:#000}
  .vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
      clip:rect(0 0 0 0);white-space:nowrap;border:0}

  a:focus-visible,button:focus-visible{outline:2px solid currentColor;outline-offset:3px}
  .skip{position:absolute;left:-9999px;top:0;z-index:2000;background:#fff;color:#000;
    padding:14px 22px;font-size:12px;font-weight:600;letter-spacing:var(--track-key);text-transform:uppercase}
  .skip:focus{left:0}

  /* ---------- ground tokens ---------------------------------- */
  section{position:relative;background:var(--ground);color:var(--ink-2)}
  html.grounds-alt section[data-ground="light"]{
    --ground:#000000;
    --ground-66:rgba(0,0,0,.66);
    --ground-28:rgba(0,0,0,.28);
    --ink-1:#ffffff;
    --ink-2:#d8d8d8;
    --ink-3:#8e8e8e;
    --line:rgba(255,255,255,.13);
    --line-strong:rgba(255,255,255,.28);
  }
  /* the transition between grounds is a hard cut, deliberately.
     A fade between two full-bleed grounds reads as a rendering
     fault on a slow scroll. */

  .wrap{max-width:var(--max);margin:0 auto;padding:clamp(72px,7vw,180px) var(--gutter)}

  /* ---------- type ------------------------------------------- */
  h1,h2,h3,h4{margin:0;color:var(--ink-1);font-weight:700;line-height:1.04;
    letter-spacing:-.015em;text-transform:uppercase}
  p{margin:0}
  .kicker{
    display:block;font-size:11px;font-weight:400;letter-spacing:var(--track-label);
    text-transform:uppercase;color:var(--ink-3);margin-bottom:clamp(20px,2vw,34px);
  }
  .lede{font-size:clamp(16px,1.15vw,20px);line-height:1.75;color:var(--ink-2);max-width:32em}
  .body{font-size:clamp(15px,1vw,18px);line-height:1.8;color:var(--ink-2);max-width:30em}

  /* ============================================================
     MECHANIC 2 — SEQUENCED REVEAL
     Lines are authored as <span class="ln"> in the markup, never
     split by script. A script that splits text nodes breaks the
     accessible name, breaks copy-paste, and breaks entirely when it
     does not run. Authoring the lines costs nothing and degrades
     to plain text.
     ⚠ The stagger is a TRANSITION DELAY on a custom property, not
     an animation. An animation with fill:forwards keeps the element
     composited for the life of the page.
     ============================================================ */
  .ln{display:block}
  html.js .rv .ln,
  html.js .rv .rv-i{
    opacity:0;transform:translateY(.42em);
    transition:opacity .9s var(--ease) calc(var(--i,0) * .085s),
               transform .9s var(--ease) calc(var(--i,0) * .085s);
  }
  html.js .rv.in .ln,
  html.js .rv.in .rv-i{opacity:1;transform:none}
  html.js .rv-fade{opacity:0;transition:opacity 1s var(--ease) calc(var(--i,0) * .085s)}
  html.js .rv.in .rv-fade,html.js .rv-fade.in{opacity:1}

  /* ---------- nav -------------------------------------------- */
  nav{
    position:fixed;top:0;left:0;right:0;z-index:1000;
    display:flex;align-items:center;justify-content:space-between;
    padding:20px var(--gutter);
    transition:background .4s,border-color .4s,padding .35s;
    border-bottom:1px solid transparent;
  }
  nav::before{content:"";position:absolute;inset:0;pointer-events:none;
    background:linear-gradient(180deg,rgba(0,0,0,.82) 0%,rgba(0,0,0,.40) 62%,rgba(0,0,0,0) 100%)}
  nav.stuck::before{opacity:0;transition:opacity .4s}
  nav.stuck{background:rgba(0,0,0,.86);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);
    border-bottom-color:rgba(255,255,255,.12);padding-block:13px}
  nav > *{position:relative}
  nav .logo{display:block;line-height:0}
  /* ⚠ the MARK on a phone, the WORDMARK on a desktop. The wordmark is
     9.65:1, so at 26px tall it is 251px wide - 64% of a 393px screen,
     which pushed the burger off the bar entirely. One mark per
     breakpoint, never both. */
  nav .logo svg{width:auto;fill:#fff;transition:height .35s}
  nav .logo-mark{display:none;height:22px}
  nav .logo-word{display:block;height:26px}
  nav.stuck .logo-word{height:23px}
  nav.stuck .logo-mark{height:20px}
  @media(max-width:860px){
    nav .logo-mark{display:block}
    nav .logo-word{display:none}
  }
  nav ul{display:flex;align-items:center;gap:4px;list-style:none;margin:0;padding:0}
  nav a{
    display:block;padding:14px 18px;font-size:11px;font-weight:500;
    letter-spacing:var(--track-label);text-transform:uppercase;text-decoration:none;color:#eaeaea;
    transition:color .3s;
  }
  nav a:hover{color:#fff}
  nav .cta{background:#fff;color:#000;padding:15px 24px}
  nav .cta:hover{color:#000;background:#e2e2e2}
  nav .burger{display:none;background:none;border:0;cursor:pointer;padding:15px 12px}
  nav .burger span{display:block;width:22px;height:1.5px;background:#fff;margin:5px 0;transition:transform .35s var(--ease),opacity .2s}
  nav.menu-open .burger span:nth-child(1){transform:translateY(6.5px) rotate(45deg)}
  nav.menu-open .burger span:nth-child(2){opacity:0}
  nav.menu-open .burger span:nth-child(3){transform:translateY(-6.5px) rotate(-45deg)}
  @media(max-width:860px){
    nav ul li:not(.cta-li):not(.burger-li){display:none}
    nav .burger{display:block}
    nav .cta{padding:13px 16px;font-size:10px}
  }
  .mobile-menu{
    position:fixed;inset:0;z-index:990;background:#000;
    display:flex;flex-direction:column;justify-content:center;gap:6px;padding:0 var(--gutter);
    opacity:0;visibility:hidden;transition:opacity .38s var(--ease),visibility .38s;
  }
  .mobile-menu.open{opacity:1;visibility:visible}
  .mobile-menu a{
    display:block;padding:18px 0;font-size:clamp(30px,9vw,52px);font-weight:700;
    text-transform:uppercase;text-decoration:none;color:#fff;letter-spacing:-.015em;
    border-bottom:1px solid rgba(255,255,255,.12);
  }

  /* ---------- hero ------------------------------------------- */
  .hero{height:100svh;min-height:520px;position:relative;overflow:hidden;background:#000}
  .hero video,.hero .poster{
    position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0;
  }
  .hero::after{
    content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
    background:
      /* with no copy over the film the scrim has two jobs left: keep the
         nav legible at the top, and hand off to the black below */
      linear-gradient(180deg,rgba(0,0,0,.58) 0%,rgba(0,0,0,.06) 30%,rgba(0,0,0,.10) 68%,rgba(0,0,0,.62) 100%);
  }
  .hero .lede{margin-top:clamp(22px,2.2vw,36px);color:#d6d6d6;max-width:34em}
  .hero-cue{
    position:absolute;left:50%;transform:translateX(-50%);bottom:34px;z-index:3;
    display:flex;flex-direction:column;align-items:center;gap:12px;
    font-size:10px;letter-spacing:var(--track-label);text-transform:uppercase;color:rgba(255,255,255,.62);
  }
  .hero-cue i{display:block;width:1px;height:42px;background:linear-gradient(180deg,rgba(255,255,255,.7),rgba(255,255,255,0));
    transform-origin:top;animation:cue 2.4s var(--ease) infinite}
  @keyframes cue{0%{transform:scaleY(0);opacity:0}30%{opacity:1}100%{transform:scaleY(1);opacity:0}}
  .hero-ctl{position:absolute;left:var(--gutter);bottom:34px;z-index:3;display:flex;gap:10px}
  .hero-ctl button{
    display:inline-flex;align-items:center;gap:9px;cursor:pointer;
    background:rgba(0,0,0,.68);border:1px solid rgba(255,255,255,.45);color:#c9c9c9;
    padding:12px 16px;font-family:inherit;font-size:10px;letter-spacing:var(--track-label);
    text-transform:uppercase;transition:color .3s,border-color .3s;
  }
  .hero-ctl button:hover{color:#fff;border-color:#fff}
  .hero-ctl .gl{display:inline-flex;gap:3px;align-items:center;height:11px}
  .hero-ctl .gl i{width:2.5px;height:11px;background:currentColor;display:block}
  .hero-ctl button[aria-pressed="true"] .gl i:first-child{
    width:0;height:0;background:none;border-left:8px solid currentColor;
    border-top:5.5px solid transparent;border-bottom:5.5px solid transparent}
  .hero-ctl button[aria-pressed="true"] .gl i:last-child{display:none}
  .hero-ctl .bars i{width:2.5px;background:currentColor;height:4px;transition:height .3s}
  .hero-ctl button.on .bars i:nth-child(1){height:7px}
  .hero-ctl button.on .bars i:nth-child(2){height:11px}
  .hero-ctl button.on .bars i:nth-child(3){height:5px}
  @media(max-width:860px){.hero-cue{display:none}}

  /* ============================================================
     MECHANIC 1 — PINNED CHAPTER
     .pin is a tall spacer. .pin-vp is the screen that sticks to it.
     Steps are stacked in the same place and cross-faded by a class
     the script sets; only one is ever `.on`.
     ⚠ --pin-run is written by the script from the step count, so the
     CSS and the JS can never disagree about how long the chapter is.
     ⚠ The base state (no JS, reduced motion) is a plain stack. Build
     it the other way round and a script failure leaves an empty box.
     ============================================================ */
  .pin-vp{position:relative}
  .pin-step + .pin-step{margin-top:clamp(40px,5vw,72px)}
  html.js.pin-on .pin{height:calc(100vh + var(--pin-run,240vh))}
  html.js.pin-on .pin-vp{
    /* ⚠ min(520px, 100vh), NOT a flat 520px. A landscape phone is 393px
       tall, so a flat floor made every pinned frame 127px taller than the
       window - the frame could never fully show, and the progress rail,
       which is the only thing telling you how long a pinned chapter is,
       sat permanently below the fold. All four chapters read as broken
       pages in landscape. */
    position:sticky;top:0;height:100vh;min-height:min(520px,100vh);overflow:hidden;
    display:grid;align-items:center;
  }
  /* ⚠ the steps are STACKED IN ONE GRID CELL, not absolutely positioned.
     Absolute positioning needed the first step left in flow to give the
     container a height, and that one exception was a bug: the first step
     sized to its own content while every other step stretched to the box,
     so justify-content aligned step 1 differently from steps 2..n. In
     services, where the copy sits at the foot of a photograph, the first
     service sat halfway up it.
     A single grid cell fixes both ends - the cell grows to the TALLEST
     step, so nothing can overflow the way an absolute step would, and
     every step stretches to the same box, so they all align alike. */
  /* ⚠⚠ THE 72px. .pin-step + .pin-step carries a margin-top for the
     no-JS stacked layout, and it was never cancelled when the steps were
     stacked. Under absolute positioning that margin made every step after
     the first 72px SHORTER and 72px LOWER than step 1, so with
     justify-content:center the copy jumped 36px down on the very first
     swap of every chapter and then sat still for the rest. That lurch is
     what looked wrong in the About chapter. It is cancelled here. */
  html.js.pin-on .pin-step + .pin-step{margin-top:0}
  html.js.pin-on .pin-steps{position:relative;display:grid}
  html.js.pin-on .pin-step{
    grid-area:1/1;display:flex;flex-direction:column;justify-content:center;
    opacity:0;visibility:hidden;transform:translateY(-26px);
    transition:opacity .36s var(--ease),transform .36s var(--ease),visibility .36s;
  }
  html.js.pin-on .pin-step.on{opacity:1;visibility:visible;transform:none;
    transition:opacity .62s var(--ease) .08s,transform .62s var(--ease) .08s,visibility 0s}
  /* steps still ahead of you wait BELOW; the base rule above is the
     already-passed state and exits upward. Same specificity order as the
     .on rule, and a general sibling combinator can never match .on
     itself, so the two never fight. */
  html.js.pin-on .pin-step.on ~ .pin-step{transform:translateY(26px)}

  /* chapter progress rail — the only thing that tells you how long
     a pinned chapter is. Without it a pin reads as a broken page. */
  .rail{position:absolute;left:var(--gutter);right:var(--gutter);bottom:38px;z-index:5;
    display:none;align-items:center;gap:14px}
  html.js.pin-on .pin .rail{display:flex}
  .rail-track{position:relative;flex:1;height:1px;background:var(--line)}
  .rail-track i{position:absolute;left:0;top:0;bottom:0;width:0;background:var(--ink-1);transition:width .18s linear}
  .rail-n{font-size:10px;letter-spacing:var(--track-label);color:var(--ink-3);
    font-variant-numeric:tabular-nums;white-space:nowrap}

  /* ---------- chapter: the statement ------------------------- */
  .statement .pin-vp{align-content:center}
  .statement .inner{max-width:var(--max);margin:0 auto;padding-inline:var(--gutter);width:100%}
  .statement h2{font-size:var(--d2);max-width:16em}  /* 14em broke the first line at 1280/1536, real Archivo - 13 Sep */
  .statement h2 .dim{color:var(--ink-3)}
  .statement .pin-steps{margin-top:clamp(30px,3.4vw,58px);min-height:14em}
  /* Keep statement copy still while the active paragraph fades. */
  html.js.pin-on .statement .pin-step{translate:none}
  @media(min-width:860.02px){.statement .pin-steps{min-height:11em}}

  /* ============================================================
     MECHANIC 3 — COUNTED GRID
     ⚠ Peter's 35 years and Daniel's 17 years must NEVER both appear
     in a stats band. They sit on different axes (depth in a trade
     against breadth across three domains) and a band invites the
     reader to compare them directly. Neither is in this grid, and
     neither should be added to it.
     ============================================================ */
  /* ⚠ THREE COLUMNS, NOT FOUR. Daniel, 9 Sep: the "1 contract" and "2 leaders"
     tiles are gone, so the grid holds SIX figures. Six in a four-column grid is
     a full row and then a row of two, and the two empty cells paint the grid's
     own hairline colour - two grey rectangles where figures used to be. Three
     columns keeps the two-row shape the chapter was built for. If a figure is
     ever added or removed again, this number has to move with it: 6 -> 3, 8 -> 4.
     The phone is unaffected (two columns, three rows). */
  .capgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--line);
    border-block:1px solid var(--line)}
  .capgrid > div{background:var(--ground);padding:clamp(30px,3vw,54px) clamp(22px,2vw,38px);
    min-height:clamp(180px,15vw,260px);display:flex;flex-direction:column;justify-content:flex-end}
  .capgrid b{display:block;font-size:clamp(40px,5vw,96px);font-weight:700;color:var(--ink-1);
    line-height:1;letter-spacing:-.025em;font-variant-numeric:tabular-nums}
  /* ⚠ the unit must not change the height of the figure's line box, or
     "1,800 m2" sits higher than "2009" beside it. vertical-align:super did
     exactly that. Raised with a relative offset and a zero line-height
     instead, so the box is the figure's own 1em everywhere. */
  .capgrid b sup{font-size:.36em;font-weight:500;letter-spacing:0;
    vertical-align:baseline;position:relative;top:-1.1em;line-height:0}
  /* ⚠ `> div >` matters. `.capgrid span` also matches .num, which is the
     figure itself, and it was being set to 12px uppercase grey - so every
     number rendered as a caption and its unit rendered as the number. */
  /* ⚠ FIGURES ON ONE LINE ACROSS THE ROW. The tiles are bottom-aligned
     and the labels run one, two or three lines, so every figure used to
     sit at a different height - 2009 lower than 1,800, 5 and 4 lower than
     2 and 1 (Daniel, 2 Sep). The label now reserves three lines whatever
     it uses, so the figure above it lands at the same height in every
     tile of a row. */
  .capgrid > div > span{display:block;margin-top:16px;font-size:12px;letter-spacing:var(--track-label);
    text-transform:uppercase;color:var(--ink-3);line-height:1.6;max-width:15em;min-height:4.8em}
  .capgrid .num{font:inherit;letter-spacing:inherit;color:inherit;
    text-transform:none;display:inline;margin:0}
  @media(max-width:1080px){.capgrid{grid-template-columns:repeat(2,1fr)}}
  @media(max-width:520px){.capgrid{grid-template-columns:1fr}}
  /* the numbers chapter pins only where the whole grid fits one screen:
     four columns, two rows. Two columns are four rows and one column is
     eight - taller than any phone - so the pin is released there and the
     frame is an ordinary block. Same release the slab uses on short
     landscape screens. */
  @media (min-width:860.02px) and (max-width:1080px), (min-width:860.02px) and (max-height:640px){
    html.js.pin-on .numbers{height:auto}
    html.js.pin-on .numbers .pin-vp{position:static;height:auto;min-height:0;overflow:visible;display:block}
    html.js.pin-on .numbers .rail{display:none}
  }
  /* the desktop holds the grid without stepping, so its rail has no count */
  @media(min-width:860.02px){.numbers [data-count]{display:none}}
  /* ---------- chapter: section through a slab ---------------- */
  /* ⚠ THE EXPLODE IS ONE LINE OF CSS. Every layer carries --k, its index
     from the bottom of the stack, and translates by (3 - k) spreads, so
     the middle of the stack holds still and everything else opens away
     from it symmetrically. --pp is the chapter's own progress, already
     written by the pin engine for the About drift, so this costs nothing
     extra. min(1, --pp * 1.7) means the drawing is fully apart by 59% of
     the chapter and then HOLDS, rather than still creeping while the last
     three callouts are being read. */
  .slab .pin-vp{align-content:center;
    /* ⚠ the nav sits over the top of this frame and the rail over the
       bottom. The chapter carries a head, a wide drawing and a paragraph,
       which together want more than 100vh unless the frame is told what
       it may not use. Without this the heading ran under the wordmark. */
    padding-block:clamp(72px,8.4vh,112px) clamp(62px,7.6vh,96px)}
  .slab .inner{max-width:var(--max);margin:0 auto;padding-inline:var(--gutter);width:100%}
  .slab-head{display:grid;grid-template-columns:minmax(0,6fr) minmax(0,5fr);
    gap:clamp(24px,4vw,80px);align-items:end;margin-bottom:0}
  /* ⚠ 16em, not 12em. At 12em the first sentence wrapped, so the two
     deliberate lines became three and the head grew 43px - which is
     what pushed the whole chapter under the nav at 900px tall. */
  .slab-head h2{font-size:var(--d4);max-width:18em}
  .slab-head p{color:var(--ink-2);font-size:clamp(13px,.92vw,16px);line-height:1.7;max-width:36em}
  /* ⚠ THE DRAWING IS SEVEN PHOTOGRAPHS. Each layer of the floor is a Blender
     render of that layer alone, in one fixed orthographic frame, so the
     seven <img>s stack pixel-for-pixel and a translateY on one of them is
     exactly that layer moving in the scene. The page explodes the stack
     around layer 3 with the chapter's own progress (--pp), lifts and
     brightens the active one, and dims the rest - the same choreography
     the line drawing had. Percentages, not px: 4.686% of the image height
     is the 0.26 m gap the composite was rendered with, 3.965% the lift.
     Daniel, 3 Sep: "more realism… make the finished surface darker like
     burnish… glass looking" - the surface layer is a dark mirror reflecting
     rows of warehouse lights; the earth and rock carry the real ground from
     the mining-site photograph. Regenerate via claude/v2-slab-render-blender.md. */
  .slab-row{display:grid;grid-template-columns:minmax(0,1.6fr) minmax(0,1fr);gap:clamp(8px,1.5vw,28px);align-items:center;margin-top:4px}
  /* ⚠ the stack is 1.257:1 and the chapter must fit a 900px screen with its
     head and rail: the width is capped by the HEIGHT left over, not by the
     column. 300px = head + pin padding at 1440x900, measured. */
  .slab-fig{margin:0;width:100%;max-width:min(100%, calc((100vh - 300px) * 1.257))}
  .slab-stack{position:relative;width:100%;aspect-ratio:1213/965}
  .slab-stack img{position:absolute;inset:0;width:100%;height:100%;display:block;
    transform-origin:50% 50%;opacity:.5;
    transform:translateY(calc((3 - var(--k)) * 4.686% * min(1, var(--pp,0) * 1.7)));
    transition:opacity .55s var(--ease),transform .3s linear}
  .slab-stack img.on{opacity:1;
    transform:translateY(calc((3 - var(--k)) * 4.686% * min(1, var(--pp,0) * 1.7) - 3.965%))}
  /* ⚠ :not(.pin-on), NOT :not(.js): under prefers-reduced-motion the JS
     runs and sets html.js but never adds .pin-on, so a :not(.js) guard would
     leave every layer dimmed forever. .pin-on means "a chapter is stepping". */
  /* ⚠ phone: the layers follow the finger. Daniel, 3 Sep: "a bit slow to
     respond". The .3s transform glide and .55s fade were tuned for a
     wheel; under a thumb they read as lag, and each step was 82vh of
     travel. Shorter transitions here, and the hold is .45 (67vh a step). */
  @media(max-width:860px){
    .slab-stack img{transition:opacity .3s var(--ease),transform .12s linear}
  }
  html:not(.pin-on) .slab-stack img{opacity:1;transform:none}
  /* no pin (no JS, reduced motion, released sizes): the assembled floor first,
     the seven steps as a list under it - not a grid that would centre the
     drawing halfway down a 1,900px column */
  html:not(.pin-on) .slab-row{display:block}
  html:not(.pin-on) .slab-fig{margin:0 auto clamp(28px,4vw,48px);max-width:min(100%,900px)}

  .slab-steps{position:relative;margin:0;min-height:0;text-align:left;max-width:none}
  .slab-steps .pin-step{align-items:flex-start}
  .slab-steps em{font-style:normal;font-size:11px;letter-spacing:var(--track-label);
    text-transform:uppercase;color:var(--ink-3);margin-bottom:8px;display:block}
  .slab-steps h3{font-size:var(--d4);text-transform:uppercase;letter-spacing:-.01em;
    margin-bottom:clamp(8px,.8vw,14px)}
  .slab-steps p{color:var(--ink-2);font-size:clamp(14px,1.02vw,17px);line-height:1.75;max-width:28em}

  @media(max-width:860px){
    .slab .pin-vp{padding-block:clamp(72px,9vh,96px) clamp(50px,6.4vh,80px)}
    .slab-head{grid-template-columns:1fr;gap:12px;align-items:start}
    .slab-head h2{max-width:none}
    .slab-head p{font-size:13.5px;line-height:1.58}
    /* the drawing full width, the scene paragraph gone (it is in the hidden
       list), the copy below; a short phone narrows the drawing to keep the
       copy above the rail */
    .slab-row{display:block}
    .slab-fig{max-width:100%;margin:2px 0 10px}
    .slab-head p{display:none}
    .slab-steps{min-height:0;text-align:left;max-width:none}
    .slab-steps .pin-step{align-items:flex-start}
    .slab-steps h3{font-size:var(--d5)}
  }
  @media(max-width:860px) and (max-height:700px){
    .slab-fig{max-width:78%;margin-inline:auto}
  }
  /* ⚠ SHORT PHONES: the step copy has to clear the progress rail. Step 04 grew
     on 9 Sep (Daniel: steel fibres are the other route to mesh or bar) and at
     62 words it ran under the rail at 393x660 and 375x667, and past the clipped
     frame entirely on an iPhone SE with both bars (375x553), where the longest
     step was already touching the rail before the edit. 13.5px is the same
     floor the projects chapter uses on a short screen; the drawing gives back
     a few percent so the copy keeps its measure. Nothing changes above 780px
     tall, which is every phone with the bars collapsed. */
  @media(max-width:860px) and (max-height:780px){
    .slab .pin-vp{padding-block:84px clamp(44px,5.6vh,64px)}
    .slab-fig{max-width:64%;margin:0 auto 8px}
    .slab-head h2{font-size:clamp(19px,5.2vw,24px)}
    .slab-steps h3{margin-bottom:6px}
    .slab-steps p{font-size:13px;line-height:1.5}
    .slab-steps em{margin-bottom:5px}
  }
  /* an iPhone SE with both Safari bars is 553px tall and the chapter has a
     head, a drawing, a step and a rail to fit. The longest step already ran
     under the rail here before 9 Sep. The drawing takes the cut, because a
     smaller drawing is still the drawing while clipped copy is lost content. */
  @media(max-width:860px) and (max-height:600px){
    /* the fixed header is ~76px: the kicker must start below it, so the top
       padding is a floor, not a fraction of a short screen */
    .slab .pin-vp{padding-block:84px clamp(38px,4.8vh,50px)}
    .slab-fig{max-width:44%;margin:0 auto 6px}
    .slab-head h2{font-size:clamp(17px,4.6vw,20px)}
    .slab-steps h3{font-size:clamp(17px,4.8vw,20px);margin-bottom:5px}
    .slab-steps p{font-size:13px;line-height:1.45}
  }
  /* ⚠ this is a HEIGHT problem, not a width one. On a short phone the
     scene-setting paragraph is what pushes the head under the wordmark
     and the last line of the step copy through the rail; on a tall phone
     of the same width everything fits with room to spare. It stays in the
     visually-hidden list either way, so nothing is lost to a reader who
     needs it. */
  @media(max-width:860px) and (max-height:740px){
    .slab-head p{display:none}
  }
  /* ⚠ A LANDSCAPE PHONE IS UNDER 430px TALL, and a head, a wide section
     drawing and a paragraph do not fit inside a 100vh frame there at any
     drawing size that stays legible. Measured at 874x393 the content
     wanted 521px in a 393px frame. So the chapter stops pinning and
     becomes a plain stack: the drawing assembled at full width, then the
     seven steps as a list. Same content, same drawing, no broken frame -
     the same decision the plant filmstrip makes at this size. */
  @media (max-height:430px){
    html.js.pin-on .slab{height:auto}
    html.js.pin-on .slab .pin-vp{position:static;height:auto;overflow:visible;
      padding-block:clamp(46px,7vw,86px)}
    html.js.pin-on .slab .rail{display:none}
    html.js.pin-on .slab .pin-steps{display:block;min-height:0;max-width:none;text-align:left}
    html.js.pin-on .slab .pin-step{opacity:1;visibility:visible;transform:none;
      translate:none;display:block}
    html.js.pin-on .slab .slab-stack img{opacity:1;transform:none}
    .slab-head{margin-bottom:14px}
    .slab-fig{max-width:100%}
  }
  @media (max-height:430px) and (min-width:700px){
    html.js.pin-on .slab .pin-steps{columns:2;column-gap:clamp(24px,3vw,52px)}
    html.js.pin-on .slab .pin-step{break-inside:avoid;padding-block:clamp(8px,1.3vw,15px)}
  }

  /* ---------- chapter: services ------------------------------ */
  .svc .pin-vp{align-content:center}
  .svc .inner{max-width:var(--max);margin:0 auto;padding-inline:var(--gutter);width:100%;
    display:grid;grid-template-columns:minmax(0,4fr) minmax(0,6fr);gap:clamp(48px,6vw,120px);align-items:center}
  .svc-index{list-style:none;margin:0;padding:0;counter-reset:s}
  .svc-index li{
    counter-increment:s;display:flex;gap:18px;align-items:baseline;
    padding:clamp(11px,1vw,16px) 0;border-bottom:1px solid var(--line);
    color:var(--ink-3);transition:color .45s var(--ease);
  }
  .svc-index li::before{content:counter(s,decimal-leading-zero);font-size:11px;
    letter-spacing:var(--track-key);color:var(--ink-3);transition:color .45s}
  .svc-index li b{font-size:var(--d5);font-weight:600;text-transform:uppercase;letter-spacing:.01em}
  .svc-index li.on{color:var(--ink-1)}
  .svc-index li.on::before{color:var(--ink-1)}
  /* ⚠ the copy in this panel sits ON a photograph, so the panel declares
     its own light-on-dark tokens rather than inheriting the section's.
     That is what lets one markup serve both the all-black build and the
     alternating one - on the light ground the panel stays dark and the
     copy stays white, instead of turning black-on-photograph. */
  .svc-panel{position:relative;overflow:hidden;background:#000;
    padding:clamp(30px,3.2vw,58px);
    --ground:#000;
    --ink-1:#ffffff; --ink-2:#dedede; --ink-3:rgba(255,255,255,.66);
    --line:rgba(255,255,255,.20)}
  /* the icon sits top-left over an undimmed photograph now; a soft shadow
     keeps it legible on the bright ones without darkening anything */
  .svc-panel .svc-icon{color:#fff;filter:drop-shadow(0 1px 6px rgba(0,0,0,.85))}
  .svc-panel .pin-steps{position:relative;z-index:2;min-height:min(62vh,32em)}
  /* ⚠ copy at the FOOT of the panel, icon at its head. margin-bottom:auto
     on the icon is what splits them - the auto margin eats the free space
     in the column, so the two ends stay pinned however long the copy is.
     Text sitting low on a photograph is the only position that reads as
     deliberate; centred, it looks like it landed there by accident. */
  .svc-panel .pin-step{justify-content:flex-end}
  .svc-panel .svc-icon{margin-bottom:auto}
  .svc-panel .pin-step h3{font-size:var(--d3);margin-bottom:clamp(14px,1.4vw,24px)}
  .svc-media{position:absolute;inset:0;z-index:0}
  /* ⚠ THE WHOLE PHOTOGRAPH, NOT A ZOOMED SLICE. Daniel, 2 Sep: "the
     services photos, no need to zoom in so much. Keep the same photo real
     estate, but don't zoom in." The panel is nearly square on a laptop
     (668x604 at 1440) and the photographs are 16:10, so object-fit:cover
     was showing 69% of each picture at 1440, 73% at 1920 and 46% on a
     phone, enlarged to fill. The figure is now its own 16:10 box pinned to
     the top of the panel, so the picture is shown entire at the panel's
     full width, and the black below it is where the copy sits. max-height
     is the only clamp: on a panel wider than 16:10 (2560 and up) the box
     fills the height and cover trims a percent or two off the sides.
     The panel keeps its size - the real estate is unchanged. */
  .svc-media figure{position:absolute;left:0;right:0;top:0;margin:0;opacity:0;
    aspect-ratio:16/10;max-height:100%;overflow:hidden;
    transition:opacity .8s var(--ease)}
  .svc-media figure.on{opacity:1}
  /* the foot of the PICTURE dissolves into the black beneath it. This sits
     on the figure so it tracks the picture's own bottom edge at every
     panel aspect, where a gradient on the panel could not. */
  .svc-media figure::after{content:"";position:absolute;inset:0;pointer-events:none;
    background:linear-gradient(180deg, rgba(0,0,0,0) 52%, rgba(0,0,0,.72) 82%, #000 100%)}
  .svc-media img{width:100%;height:100%;object-fit:cover;display:block;
    transform:scale(1.06);transition:transform 8s linear}
  .svc-media figure.on img{transform:scale(1)}
  /* ⚠ NOT A FILM. Nothing here dims the photograph evenly - its light
     parts stay light. The edges bleed into black the way the project
     photographs do, and the bleed is heaviest at the foot, which is where
     the copy sits. The copy therefore reads on black, not on a darkened
     photograph. Measured with the glyphs hidden against sv-05b, the
     near-white burnished slab, which is the worst case in the set. */
  .svc-edge{position:absolute;inset:0;z-index:1;pointer-events:none;
    background:
      linear-gradient(180deg,
        #000 0, rgba(0,0,0,.55) 9%, rgba(0,0,0,0) 24%),
      linear-gradient(90deg,
        #000 0, rgba(0,0,0,0) 15%, rgba(0,0,0,0) 89%, #000 100%)}
  .svc-h2{display:none}
  .svc-icon{width:46px;height:46px;color:var(--ink-3);margin-bottom:clamp(14px,1.6vw,26px)}
  .svc-icon svg{width:100%;height:100%;display:block;
    fill:none;stroke:currentColor;stroke-width:1.7;
    stroke-linecap:square;stroke-linejoin:miter;vector-effect:non-scaling-stroke}
  @media(max-width:860px){
    .svc .inner{grid-template-columns:1fr;gap:34px}
    .svc-index{display:none}
    .svc-panel{padding:clamp(24px,6vw,40px)}
    .svc-panel .pin-steps{min-height:27em}
    /* a phone has no width to spare for a side bleed, so the edges are
       tighter and the foot carries the copy on its own */
    /* the picture is 232px tall in a 480px panel here, so the top bleed
       is shortened in step: 20% of the panel would have dimmed the top
       two-fifths of the picture */
    .svc-edge{background:
      linear-gradient(180deg,
        #000 0, rgba(0,0,0,.5) 5%, rgba(0,0,0,0) 13%),
      linear-gradient(90deg,
        #000 0, rgba(0,0,0,0) 8%, rgba(0,0,0,0) 92%, #000 100%)}
  }

  /* ⚠ SHORT VIEWPORTS. A landscape phone is 360-430px tall and the
     pinned frame is now exactly that. The service icon plus a 32em copy
     floor overflowed it by 88px at 740x360 - the copy ran below the fold
     inside an overflow:hidden frame, so it was simply gone. On a screen
     this short the copy IS the content: the icon goes, the padding
     tightens, and every step floor is released so the grid row sizes to
     the tallest step instead of to a number chosen for a tall window. */
  @media (max-height:430px){
    .svc-panel{padding:clamp(16px,2.2vw,32px)}
    .svc-panel .svc-icon{display:none}
    .svc-panel .pin-steps{min-height:0}
    .proj-steps{min-height:0}
    .statement .pin-steps{min-height:0;margin-top:clamp(16px,2vw,26px)}
  }

  /* ---------- chapter: process ------------------------------- */
  .proc .inner{max-width:var(--max);margin:0 auto;padding-inline:var(--gutter);width:100%}
  /* ⚠ the QC line sits BESIDE the heading, not under the track. It is the
     one claim on this site a competitor would have to change how they work
     to match, and as a footnote below five columns nobody reached it. */
  .proc-head{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
    gap:clamp(40px,5vw,110px);align-items:end;margin-bottom:clamp(38px,4.4vw,80px)}
  .proc-head h2{font-size:min(var(--d2),64px)}  /* ⚠ 64px cap: the heading shares its row with the
     numeral so its column grows slower than --d2; above ~1366 "ONE STANDARD." wrapped and left
     ONE orphaned. Tuned with the real Archivo at 1100-2560 - 13 Sep */
  .proc-track{display:grid;grid-template-columns:repeat(5,1fr);gap:1px;background:var(--line)}
  .proc-track > div{background:var(--ground);padding:clamp(26px,2.4vw,44px) clamp(18px,1.6vw,30px);
    min-height:clamp(190px,17vw,280px);display:flex;flex-direction:column;
    color:var(--ink-3);transition:color .5s var(--ease)}
  .proc-track > div.on{color:var(--ink-2)}
  .proc-track em{font-style:normal;font-size:11px;letter-spacing:var(--track-key);color:var(--ink-3)}
  .proc-track b{display:block;margin:16px 0 12px;font-size:var(--d5);font-weight:600;
    text-transform:uppercase;color:var(--ink-3);transition:color .5s var(--ease)}
  .proc-track > div.on b{color:var(--ink-1)}
  .proc-track p{font-size:14px;line-height:1.7}
  /* the QC line sits UNDER the heading now; the right column is the numeral */
  .proc-qc{margin:clamp(16px,1.6vw,26px) 0 0;max-width:34em;font-size:clamp(13px,.92vw,16px);
    line-height:1.7;color:var(--ink-2);padding-left:16px;
    border-left:1px solid var(--line-strong)}
  .proc-qc strong{display:block;color:var(--ink-1);font-weight:600;margin-bottom:.35em}
  /* ⚠ THE NUMERAL. Daniel, 3 Sep: "five steps one standard, can we do better?"
     - the flattest section on the site: nothing moved but a highlight. A
     01-05 counter the height of the heading steps with the track, so the
     strip reads as a countdown through the job. No script: :has() reads
     which column is .on. Gated on :has() support and on the chapter being
     pinned (static page = no counter). */
  .proc-num{display:none;flex-direction:column;align-items:flex-end;justify-content:flex-end;text-align:right;line-height:.82}
  @supports selector(:has(a)){ html.pin-on .proc-num{display:flex} }
  .proc-n{position:relative;font-size:clamp(140px,17vw,260px);font-weight:700;letter-spacing:-.05em;
    color:#fff;font-variant-numeric:tabular-nums;width:1.25em;height:.84em}
  .proc-name{position:relative;font-size:clamp(16px,1.4vw,24px);font-weight:600;letter-spacing:.04em;
    text-transform:uppercase;color:#fff;margin-top:10px;width:6em;height:1em}
  .proc-of{font-size:clamp(14px,1.2vw,20px);letter-spacing:.18em;color:var(--ink-3);margin-top:14px}
  .proc-n i,.proc-name i{position:absolute;right:0;top:0;font-style:normal;opacity:0;
    transform:translateY(.12em);transition:opacity .45s var(--ease),transform .45s var(--ease)}
  .proc:has(.proc-track > div:nth-child(1).on) .proc-num i[data-n="1"],
  .proc:has(.proc-track > div:nth-child(2).on) .proc-num i[data-n="2"],
  .proc:has(.proc-track > div:nth-child(3).on) .proc-num i[data-n="3"],
  .proc:has(.proc-track > div:nth-child(4).on) .proc-num i[data-n="4"],
  .proc:has(.proc-track > div:nth-child(5).on) .proc-num i[data-n="5"]{opacity:1;transform:none}
  /* the four columns not in play sit back */
  html.pin-on .proc-track > div{transition:opacity .5s var(--ease),color .5s var(--ease)}
  html.pin-on .proc-track > div:not(.on){opacity:.42}
  .proc-track .short{display:none}
  @media(max-width:900px){
    .proc-head{grid-template-columns:1fr;gap:clamp(26px,4vw,44px);align-items:start}
    .proc-qc{max-width:32em}
    .proc-num{display:none!important}
  }
  @media(max-width:980px){.proc-track{grid-template-columns:repeat(2,1fr)}}
  @media(max-width:560px){.proc-track{grid-template-columns:1fr}
    .proc-track > div{min-height:0}}
  /* ---------- chapter: projects ------------------------------ */
  /* ⚠ --cend is the END OF THE COPY COLUMN and the START OF THE PICTURE.
     Declared on the frame so the scrim and the media box read one value.
     Daniel tried A2 (a 26em column, picture at ~64%) and then asked for A:
     the column at its original 30em / 42%, the whole picture in the 58%
     that remains (2 Sep). */
  .proj .pin-vp{display:block;padding:0;
    --cend:max(42%, calc(var(--gutter) + 30em + 26px))}
  /* ⚠ overflow:hidden. Every picture arrives at scale(1.06) and settles
     to 1 over seven seconds. On a phone the media box is a band, not the
     whole frame, and for those seven seconds the extra 3% hung below the
     band, outside the scrim - a thin strip of photograph under the black
     that shrank to nothing. Daniel: "a line that appears then disappears
     through the black bleed". Clipped at the box. */
  /* ⚠ THE WHOLE PHOTOGRAPH, BESIDE THE COPY (Daniel, 2 Sep: "I don't like
     how blown up the photos are" - a full-bleed picture with 42% under the
     copy column showed a screen-scale slice of each one). The media box
     runs from the end of the copy column to the screen's right edge, full
     height; the picture is CONTAINED in it, pinned to the right edge and
     centred vertically, so every photograph is complete: 62-64% of the
     width on a laptop, sharper than before because 2560px is no longer
     stretched across a retina screen. Its left edge sits exactly on --cend,
     under the scrim's ramp, so it dissolves into the black column the way
     the full-bleed picture did ("keep the edge dissolving"). The 1.06
     settle stays; the box clips it. --px is no longer read: there is
     nothing to crop. */
  .proj-media{position:absolute;top:0;bottom:0;left:var(--cend);right:0;z-index:0;background:#000;overflow:hidden}
  /* ⚠ ONE BOX FOR EVERY PHOTOGRAPH. Daniel, 2 Sep: "they're all kind of
     different sizes... I want them all to have even black space above and
     below." The files are not one aspect (most 16:10, two 16:9, the data
     centre 1.53), so a contained picture sat at a different height in each
     row. The figure is now a fixed 16:10 box, the full width of the media
     column, centred vertically; the picture COVERS it, which trims the
     16:9 stills by 3% a side and the data centre by 2% top and bottom -
     the only cropping left in the chapter, and the same black above and
     below on every row (189px at 1440x900). max-height guards a very wide,
     short window: there the box is the column's full height and cover
     trims the sides instead. */
  .proj-media figure{position:absolute;left:0;right:0;top:calc(50% - var(--photo-h) / 2);margin:0;opacity:0;
    width:100%;height:var(--photo-h);max-height:100%;overflow:hidden;
    transition:opacity .85s var(--ease)}
  .proj-media figure.on{opacity:1}
  /* ⚠ NO MASK, and --px is capped at the scrim's opaque stop (42%) on
     every photograph. A figure edge that never leaves the black never
     needs feathering, and the scrim's ramp is then the ONE bleed - the
     same start, length and curve on all ten photographs. A per-figure
     mask used to double up with the ramp on the four photographs pushed
     past it, and the bleed read heavier on those than on the rest. */
  .proj-media figure{translate:none}
  .proj-media img{width:100%;height:100%;object-fit:cover;object-position:50% 50%;
    transform:scale(1.06);transition:transform 7s linear}
  .proj-media figure.on img{transform:scale(1)}
  /* ⚠ FULLY OPAQUE #000 ACROSS THE COPY COLUMN, not a 90% wash.
     At .90 over a photograph the copy side composited to rgb(24,24,24) -
     measurably a lighter shade of black, and it sat directly above
     sections painting a true rgb(0,0,0). On the shipped site this half
     of a project row was a real black panel, so the two disagreed.
     The stop stays ahead of .proj-steps' own max-width at every width,
     and the photograph is released from there. */
  /* ⚠ --cend is the END OF THE COPY, not a round number. The opaque
     stretch has to clear the text column at EVERY width, and 46% did not:
     at 1024 the copy runs to 50.8% of the viewport and at 874 to 59.5%,
     because .proj-steps is a fixed 30em that stops shrinking while the
     screen keeps doing so. max() of the two expressions tracks whichever
     is wider - the percentage on a desktop, the copy's own measure on a
     small landscape screen - so one gradient serves both.
     Everything after --cend is expressed RELATIVE to it, so the ramp
     keeps its shape wherever the copy ends.
     The photograph now reaches FULL brightness from --cend + 30% instead
     of being held at 84% even at the far edge, which is what was making
     the chapter read as more black than photograph. */
  .proj-scrim{position:absolute;inset:0;z-index:1;pointer-events:none;
    background:
      /* ⚠ THE RAIL SITS HERE. The progress counter is 10px type at the
         bottom right, directly over the brightest corner of every
         photograph. Measured on the previous scrim it ran as low as
         1.96:1 against a 4.5:1 requirement - a real failure, already
         shipping, that lightening the right-hand side would have made
         worse. This band is what pays for it. */
      linear-gradient(0deg, rgba(0,0,0,.80) 0, rgba(0,0,0,.80) 9%,
                             rgba(0,0,0,0) 22%),
      linear-gradient(90deg,
        #000 0, #000 var(--cend),
        rgba(0,0,0,.66) calc(var(--cend) + 4%),
        rgba(0,0,0,.28) calc(var(--cend) + 10%),
        rgba(0,0,0,0)   calc(var(--cend) + 18%))}
  /* history: a contained figure for two drone rows only was rejected on 2 Sep
     ("their real estate should be like the other photos"); the same idea for
     ALL eleven rows is what Daniel chose later that day, once he had seen
     the full-bleed slices side by side with whole pictures. */
  @media(max-width:860px){
    /* ⚠ PHONE: THE WHOLE PHOTOGRAPH, THEN THE COPY. Daniel, 2 Sep, from
       his phone: "the text is right in the centre of it, you basically
       can't see the photos... the text needs to be on the bottom of each
       photo with a black bleed up. We need to see as much of the photos
       as possible." The old phone scrim ran a black band down the middle
       of a cover-cropped portrait slice - 29% of the picture, as two dim
       strips. Now the media box is a 16:10 band the full width of the
       frame, sat under the nav, the picture whole; the scrim is the same
       box and only dissolves the picture's foot into black; the copy
       starts on that black just below. Still a pinned chapter - same
       crossfade, same settle, same rail - only the geometry changes. */
    .proj .pin-vp{--pt:clamp(64px, 13vh, 150px)}
    .proj-media{top:var(--pt);bottom:auto;height:62.5vw}
    .proj-scrim{top:var(--pt);bottom:auto;height:62.5vw;
      background:linear-gradient(180deg, rgba(0,0,0,0) 50%, rgba(0,0,0,.55) 78%, rgba(0,0,0,.92) 94%, #000 100%)}
    .proj-media figure{translate:none}
    /* 16:9 drone stills lose 3% a side to the 16:10 band; --op says which side */
    .proj-media img{object-position:var(--op,50%) 50%}
    /* ⚠ .proj .proj-copy, not .proj-copy: the base rule (inset:0, centred)
       is written after this block and would win on equal specificity */
    .proj .proj-copy{top:calc(var(--pt) + 62.5vw + 6px);bottom:70px;align-content:start}
    .proj .proj-steps{min-height:0}
  }
  .proj-copy{position:absolute;inset:0;z-index:2;display:grid;align-content:center}
  /* ⚠ NO-JS FALLBACK. Without the script nothing pins and no figure is
     ever .on, so this chapter rendered as a zero-height frame with eleven
     copies overflowing into the plant section below (and, once the media
     box had a height, a 1.06-scaled picture 12px wider than the screen).
     Static instead: the first photograph, then the eleven rows in a list.
     Keyed on :not(.pin-on), not :not(.js): a reduced-motion reader has the
     script but no pins, and needs the same static chapter. */
  html:not(.pin-on) .proj .pin-vp{padding:clamp(48px,6vw,96px) 0}
  html:not(.pin-on) .proj-media{position:static;height:auto;aspect-ratio:16/10}
  html:not(.pin-on) .proj-media figure{display:none}
  html:not(.pin-on) .proj-media figure:first-child{display:block;position:static;opacity:1;translate:none;height:100%}
  html:not(.pin-on) .proj-media img{transform:none}
  html:not(.pin-on) .proj-scrim{display:none}
  html:not(.pin-on) .proj .proj-copy{position:static;display:block;padding-top:clamp(28px,4vw,48px)}
  html:not(.pin-on) .proj .proj-steps{min-height:0;max-width:none}
  .proj-copy .inner{max-width:var(--max);margin:0 auto;padding-inline:var(--gutter);width:100%}
  .proj-head{margin-bottom:clamp(26px,3vw,48px);max-width:30em}
  /* quieter than the project titles (--d3): the chapter is the label, the
     rows are the stars */
  .proj-head h2{font-size:clamp(20px,1.9vw,30px)}
  .proj-head .kicker{margin-bottom:clamp(14px,1.4vw,22px)}
  .proj-steps{position:relative;min-height:17em;max-width:30em}
  .proj-steps h3{font-size:var(--d3);color:#fff;margin-bottom:clamp(16px,1.6vw,26px)}
  .proj-steps p{color:#d2d2d2;font-size:clamp(15px,1vw,18px);line-height:1.8;max-width:28em}
  .proj .rail-track{background:rgba(255,255,255,.18)}
  .proj .rail-track i{background:#fff}
  /* ⚠ .78, not .62. This label is the only text on the site set over an
     unscrimmed part of a photograph, and it needs the extra alpha even
     with the band above. */
  .proj .rail-n{color:rgba(255,255,255,.78)}

  /* ---------- plant & fleet : the plate ---------------------- */
  /* ONE photograph, deliberately, and NO pin.
     What was here was a five-up horizontal filmstrip scrubbing sideways
     across a pinned band. It read as a carousel rather than as a
     statement, and it was the FOURTH pinned chapter on a page that already
     pins for services, process and projects. This section holds still.
     ⚠ deliberately NO script: nothing in this block reads or writes
     layout on a frame. */
  /* ⚠ THE PROJECTS LAYOUT, MIRRORED (Daniel, 2 Sep, on the centred
     version: "that looks a bit weird in the middle like that... copy what
     we did in the projects but flip it: photo on the left, text on the
     right, the black bleed coming into the photo only from the right").
     - The section is one screen tall (min-height:100vh), like a projects
       frame, so the photograph sits with the same black above and below
       as every project row (189px at 1440x900).
     - The picture column is everything LEFT of --proj-col, the same
       token that sizes the projects copy column; --photo-h is 16:10 of
       it, so the box here is the project box, flush left instead of
       flush right: 835x522 at 1440.
     - The 4:3 photograph COVERS the box. object-position 50% 100% puts
       whatever cover trims on the ceiling side: the machine spans rows
       26-91% of the file (seat top to skirt), so the skirt always keeps
       its floor. Daniel's line on this photograph is the whole machine,
       never the ceiling. Centred (50% 50%) in a 16:10 box the cut ran
       through the skirt. With --plate-gap (below) the box is narrower
       than 16:10 and the trim is 18px at 1440, not a sixth.
     - The scrim is the projects scrim run from the RIGHT: opaque ground
       across the copy column, then .66 / .28 / 0 over the next 18%, so
       the picture's right edge dissolves into the copy exactly as a
       project picture's left edge does. No top, bottom or left bleed.
     - The copy keeps its own scale ("keep the size of the text... it's
       its own section"): kicker, the chapter statement at --d2, the two
       paragraphs at body size. The statement wraps to two lines per
       sentence in the column - four lines, white pair then dim pair -
       and is capped so "MACHINES." always holds on one line (7.7em in
       Inter 700 caps at -.015em). The copy sits on the page gutter, not
       the --max container, so the column is 539px at 1440 and the cap
       only bites below ~1330px.
     Phone (base rules, no media query): the stacked block - head
     centred above, the whole photograph 4:3 at full width, bleed from
     the top only, foot on the rule. Unchanged from the 2 Sep phone build. */
  .plate{--cend:var(--proj-col)}
  .plate-head{max-width:var(--max);margin:0 auto;text-align:center;
    padding:clamp(72px,7vw,180px) var(--gutter) clamp(40px,4.2vw,78px)}
  /* the two sentences are the structure - each .ln is a line and the
     second is dimmed; on the phone the size is capped so neither wraps
     ("Our own machines." is 14.0em at these settings) */
  .plate-head h2{margin-inline:auto;max-width:14em;
    font-size:min(var(--d2), calc((100vw - 2 * var(--gutter)) / 14.3))}
  .plate-head .body{margin:clamp(20px,2vw,32px) auto 0;max-width:40em}
  .plate-steps p + p{margin-top:clamp(10px,1vw,16px)}
  /* ⚠ NO CAPTION. Daniel, 2 Sep: "remove the text about the photo from
     underneath the Allen". On the phone the rule the photograph sat on
     stays as the figure's bottom edge. */
  .plate-fig{position:relative;margin:0 0 clamp(56px,6vw,118px);border-bottom:1px solid var(--line)}
  .plate-frame{position:relative;overflow:hidden;background:#000;
    width:100%;height:min(78vh,75vw);min-height:220px}
  .plate-frame img{position:absolute;inset:0;width:100%;height:100%;
    object-fit:cover;object-position:50% 100%;display:block}
  /* ⚠ Daniel, 4 Sep: the black must bleed at the BOTTOM as well, so the
     machine sits in the page instead of on a hard cut edge. */
  .plate-feather{position:absolute;inset:0;pointer-events:none;
    background:linear-gradient(180deg,
      var(--ground) 0,
      var(--ground-66) 6%,
      rgba(0,0,0,0) 26%,
      rgba(0,0,0,0) 74%,
      var(--ground-66) 94%,
      var(--ground) 100%)}

  @media (min-width:860.02px){
    .plate{min-height:100vh;display:grid;align-items:center;isolation:isolate}
    /* the picture column: from the left edge to the copy column */
    .plate-fig{position:absolute;top:0;bottom:0;left:0;right:var(--cend);
      margin:0;border:0;overflow:hidden;background:var(--ground);z-index:0}
    /* ⚠ --plate-gap: black between the picture and the copy column.
       Daniel, 3 Sep, on the box run to the column edge: "the photo looks a
       bit squished against the text... a bit more black in between." The
       box is the project box less this gap, still flush left and still
       --photo-h tall. Because the file is 4:3 and the box is now narrower
       than 16:10, cover scales it to the WIDTH: the photograph shrinks
       (720px wide at 1440, was 835) and loses only 18px of ceiling instead
       of a sixth of its height - the machine is whole and a little smaller,
       nothing is cut from its left. The cap keeps the box wider than 4:3
       (0.267 x h is the limit) so cover never trims the sides. Black
       between picture and copy at 1440: 115px gap + 26px air. */
    .plate{--plate-gap:min(8vw, calc(var(--photo-h) * .26))}
    /* the project box less the gap, flush left, --photo-h tall, centred;
       max-height guards a very wide, short window */
    .plate-frame{position:absolute;left:0;right:var(--plate-gap);top:calc(50% - var(--photo-h) / 2);
      width:auto;height:var(--photo-h);max-height:100%;min-height:0}
    /* (object-position 50% 100% is the base rule above: whatever cover
       trims comes off the ceiling) */
    /* ⚠ the scrim lives INSIDE the box, so it is the projects ramp with
       the opaque stretch removed: the box's right edge IS --cend, and the
       copy column beside it is the section's own ground. 270deg = stops
       measured from the RIGHT edge; vw, not %, because % here would be of
       the box, and the projects ramp is 4 / 10 / 18 hundredths of the
       screen. Same start, same length, same curve as a project picture. */
    .plate-feather{z-index:1;
      background:linear-gradient(270deg,
        var(--ground) 0,
        var(--ground-66) 4vw,
        var(--ground-28) 10vw,
        rgba(0,0,0,0) 18vw)}
    /* the copy column: --proj-col wide, on the right, 26px of air from the
       picture (the same 26px the projects put between copy and picture),
       the page gutter on the outside, centred vertically in the screen */
    .plate-head{position:relative;z-index:2;margin:0 0 0 auto;width:var(--cend);max-width:none;
      text-align:left;display:flex;flex-direction:column;justify-content:center;
      padding:clamp(96px,14vh,160px) var(--gutter) clamp(72px,10vh,120px) 26px}
    .plate-head h2{margin-inline:0;max-width:none;
      font-size:min(var(--d2), calc((var(--cend) - 26px - var(--gutter)) / 7.7))}
    .plate-head .body{margin-inline:0}
  }
  /* ---------- leadership ------------------------------------- */
  .lead-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);align-items:stretch;
    border-block:1px solid var(--line)}
  /* the same height as a project photograph (see --photo-h at :root); the
     grid row still stretches to the copy where the copy is taller, so the
     photograph centres itself in its cell rather than growing */
  .lead-visual{position:relative;overflow:hidden;min-height:0;height:var(--photo-h);align-self:center}
  .lead-visual img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
  .lead-visual::after{content:"";position:absolute;inset:0;pointer-events:none;
    background:
      linear-gradient(90deg,rgba(0,0,0,.35) 0,transparent 12%,transparent 58%,
        rgba(0,0,0,.55) 82%,#000 100%),
      linear-gradient(180deg,rgba(0,0,0,.5) 0,transparent 14%,transparent 86%,rgba(0,0,0,.5) 100%)}
  .lead-info{display:flex;flex-direction:column;justify-content:center;
    padding:clamp(48px,5vw,96px) clamp(36px,5vw,110px)}
  .lead-info h2{font-size:var(--d4);margin-bottom:clamp(20px,2vw,32px)}
  /* the three sentences step in one cell; the cell is as tall as the
     longest, so the titles below never move between steps. The floor is
     for the un-pinned stack, where the base margin between steps applies. */
  .lead-steps{position:relative}
  .lead-steps .pin-step p{margin:0}
  html.js.pin-on .lead-steps{min-height:6.5em}
  /* the chapter pins only where the grid fits its frame: a short window
     (a landscape phone, a small laptop with the bar) gets the static
     block, exactly like the slab's release */
  /* a short phone (iPhone SE class, 667 tall) keeps the lock but gives
     the frame room: shorter photograph, tighter padding, so the grid
     clears the nav above and the rail below */
  @media (max-width:860px) and (max-height:720px){
    html.js.pin-on .lead .lead-visual{height:46vw}
    html.js.pin-on .lead .lead-info{padding-block:22px}
    html.js.pin-on .lead-steps{min-height:5.5em}
  }
  @media (max-height:600px){
    html.js.pin-on .lead{height:auto}
    html.js.pin-on .lead .pin-vp{position:static;height:auto;min-height:0;overflow:visible;display:block}
    html.js.pin-on .lead .rail{display:none}
    html.js.pin-on .lead .pin-steps{display:block}
    html.js.pin-on .lead .pin-step{opacity:1;visibility:visible;transform:none;position:static}
    html.js.pin-on .lead .pin-step + .pin-step{margin-top:22px}
  }
  .lead-meta{display:flex;gap:clamp(34px,4vw,72px);margin-top:clamp(30px,3vw,52px);
    padding-top:clamp(22px,2vw,34px);border-top:1px solid var(--line)}
  .lead-meta dt{font-size:11px;letter-spacing:var(--track-label);text-transform:uppercase;color:var(--ink-3)}
  .lead-meta dd{margin:6px 0 0;font-size:13px;font-weight:500;letter-spacing:var(--track-key);
    text-transform:uppercase;color:var(--ink-1)}
  .lead-meta dl,.lead-meta div{margin:0}
  @media(max-width:860px){
    .lead-grid{grid-template-columns:1fr}
    /* ⚠ an explicit height, not aspect-ratio alone. The photograph is
       absolutely positioned, so the box has no in-flow content; as a
       stretched grid item WebKit has collapsed aspect-ratio boxes like
       this to nothing, and Daniel saw exactly that - "the leadership
       photo is missing" - on his phone while Chromium drew it. 62.5vw is
       16:10 of the viewport. */
    .lead-visual{min-height:0;height:62.5vw;aspect-ratio:auto;align-self:start}
    /* stacked on a phone, the copy is BELOW the photograph, so the bleed
       comes up from the bottom instead of in from the right */
    .lead-visual::after{background:
      linear-gradient(180deg,rgba(0,0,0,.4) 0,transparent 14%,transparent 56%,
        rgba(0,0,0,.6) 82%,#000 100%),
      linear-gradient(90deg,rgba(0,0,0,.45) 0,transparent 10%,transparent 90%,rgba(0,0,0,.45) 100%)}
    .lead-info{padding:clamp(34px,6vw,56px) var(--gutter)}
  }

  /* ---------- contact ---------------------------------------- */
  .contact .inner{max-width:var(--max);margin:0 auto;padding:clamp(96px,10vw,220px) var(--gutter) clamp(120px,9vw,200px)}
  .contact h2{font-size:min(var(--d1),100px);max-width:14em}  /* was 11em: "SOMETHING PERMANENT." broke
     at every desktop width; 14em + a 100px cap holds two lines 1100-2560, real Archivo - 13 Sep */
  .contact h2 .dim{color:var(--ink-3)}
  .contact-row{display:flex;flex-wrap:wrap;align-items:flex-end;
    gap:clamp(40px,6vw,120px);margin-top:clamp(44px,4.5vw,84px)}
  .email{font-size:clamp(18px,2vw,46px);font-weight:500;color:var(--ink-1);text-decoration:none;
    border-bottom:1px solid var(--line-strong);padding-bottom:8px;letter-spacing:.01em;
    transition:border-color .3s}
  .email:hover{border-color:var(--ink-1)}
  .cmeta{display:flex;flex-wrap:wrap;gap:clamp(28px,3vw,56px);margin:0}
  .cmeta div{margin:0}
  .cmeta dt{font-size:11px;letter-spacing:var(--track-label);text-transform:uppercase;color:var(--ink-3);line-height:1.6}
  .cmeta dd{margin:5px 0 0;font-size:13px;font-weight:500;letter-spacing:var(--track-key);
    text-transform:uppercase;color:var(--ink-1)}

  /* ---------- footer ----------------------------------------- */
  footer{border-top:1px solid rgba(255,255,255,.12);background:#000;color:#9a9a9a;
    display:flex;align-items:center;justify-content:space-between;gap:24px;
    padding:26px var(--gutter)}
  footer .fmark svg{height:22px;width:auto;fill:#fff;display:block}
  /* the wordmark on a phone, the H on a desktop (Daniel, 2 Sep: "put the
     Highlands watermark instead of the H, mobile only"). One per screen. */
  footer .fmark svg.fmark-word{display:none}
  @media(max-width:860px){
    footer .fmark svg.fmark-h{display:none}
    footer .fmark svg.fmark-word{display:block;height:20px}
  }
  footer p{font-size:clamp(9px,.58vw,11px);letter-spacing:.1em;text-transform:uppercase;
    white-space:nowrap;line-height:1.5}
  footer .fsoc{display:flex;gap:6px}
  footer .fsoc a{display:block;padding:11px;line-height:0;color:#9a9a9a;transition:color .3s}
  footer .fsoc a:hover{color:#fff}
  footer .fsoc svg{width:17px;height:17px;display:block;fill:currentColor}
  @media(max-width:1080px){footer p{font-size:8px;letter-spacing:.06em}}
  @media(max-width:700px){
    footer{flex-direction:column;text-align:center;gap:18px;padding-block:34px}
    footer p{white-space:normal;font-size:9px;letter-spacing:.1em}
    /* Daniel, 2 Sep: on the phone the ABN and the licence share one line */
    footer .f-sep{display:none}
    footer .f-co,footer .f-reg{display:block;white-space:nowrap}
    footer .f-reg{margin-top:4px}
  }


  /* ============================================================
     PHONE: SNAPPING BLOCKS, NO PINS  (html.stack, set by the script)
     Daniel's testers, 2 Sep: on a phone the pinned chapters needed "so
     many swipes" or "went past two or three sections". A scroll-scrubbed
     chapter is a wheel idiom; a thumb flicks 1,000-2,000px with momentum.
     So under 860px the script does not pin. Every step becomes a .blk:
     a screen-sized block (100svh) that is a scroll-snap area with
     scroll-snap-stop:always, so one swipe lands on the next thing and a
     flick cannot clear two. The visuals are the desktop's: the photograph
     whole with its foot dissolving into black, the copy rising in as the
     block arrives (.in, set by an IntersectionObserver), the figures
     counting on entry, and a rail-style label at the block's foot.
     ⚠ Snapping is mandatory on the root, so EVERY section is a snap area;
     a section taller than the screen (About, Leadership) may rest at any
     position inside it (that is how the spec treats an area larger than
     the snapport), and the footer snaps by its end so the page bottom is
     reachable. Desktop (>860px) has none of this.
     ============================================================ */
  @media(max-width:860px){
    html.stack{scroll-snap-type:y mandatory}
    html.stack main > section{scroll-snap-align:start}
    html.stack footer{scroll-snap-align:end}
    /* ⚠ every pinned chapter is released EXCEPT one that carries
       data-pin-phone (the slab): the engine runs for that one alone, so
       html.pin-on is set on the phone too, and these releases must be
       scoped or the pinned-state rules (steps stacked in one cell, hidden)
       would swallow the blocks. */
    html.stack .pin:not([data-pin-phone]){height:auto}
    html.stack .pin:not([data-pin-phone]) .pin-vp{position:static;height:auto;min-height:0;overflow:visible;display:block}
    html.stack .pin:not([data-pin-phone]) .rail{display:none}
    html.stack .pin:not([data-pin-phone]) .pin-steps{display:block;min-height:0}
    /* ⚠ position:relative, NOT static. The release must undo the pinned
       state's absolute/grid placement, but each block is the containing
       block for its own rail; with static here every project rail in the
       chapter stacked at the chapter's foot and only the eleventh showed
       (Daniel, from his phone). */
    html.stack .pin:not([data-pin-phone]) .pin-step{opacity:1;visibility:visible;transform:none;grid-area:auto;
      position:relative;display:flex;flex-direction:column;transition:none}
    html.stack .pin:not([data-pin-phone]) .pin-step + .pin-step{margin-top:0}
    /* the slab keeps its pinned phone layout; the section is the anchor for
       the snap markers the script drops at each step boundary, so a swipe
       is one layer and the explode glides between them */
    html.stack .slab{position:relative}
    html.stack .slab .snapmark{position:absolute;left:0;width:1px;height:1px;pointer-events:none;
      scroll-snap-align:start;scroll-snap-stop:always}
    /* the block */
    /* ⚠ 100lvh, NOT 100svh. Daniel, from his phone: "on Civil & External
       Works I can see the picture for Architectural Finishes already
       coming through at the bottom." A block sized to the SMALL viewport
       (toolbar showing) is ~100px shorter than the screen once Safari's
       bar collapses on the first swipe, and the next block's photograph
       showed in the gap. Sized to the LARGE viewport the block always fills
       the screen; with the bar showing, its rail sits just under the bar
       until the next swipe. Belt and braces: every photograph also fades in
       from black at its top (figure::before), so whatever might peek is
       black, not picture. */
    /* ⚠ 100svh, NOT lvh/dvh (Daniel, 4 Sep, from his phone: "sometimes it
       will go to the next project but only half the screen. I want it to
       snap"). A snap area TALLER than the snapport may legally rest at any
       position inside it, so a block sized to the large viewport (bars
       showing) or to the dynamic viewport (which changes mid-flick as the
       bar collapses, moving the snap points during the browser's own snap
       animation) lets a swipe settle part-way. The small viewport is the
       one height that is never taller than what is visible and never moves,
       so mandatory + stop:always is exact: one gesture, one whole screen.
       Whatever the collapsing bar exposes below the block is covered by the
       next photograph's solid black guard band (figure::before). */
    html.stack .blk{position:relative;box-sizing:border-box;display:flex;flex-direction:column;
      min-height:100vh;min-height:100svh;padding:64px 0 76px;
      scroll-snap-align:start;scroll-snap-stop:always}
    /* the rail is pinned to the block's foot; the content above it is
       free to centre (numbers, process, the slab) or to start at the top
       under the photograph (projects, services) */
    html.stack .blk-rail{position:absolute;left:0;right:0;bottom:0;display:flex;align-items:center;gap:14px;
      padding:26px var(--gutter) 30px;font-size:10px;letter-spacing:var(--track-label);
      color:var(--ink-3);font-variant-numeric:tabular-nums;white-space:nowrap}
    html.stack .blk-rail i{flex:1;height:1px;background:var(--line);position:relative;display:block}
    html.stack .blk-rail i::after{content:"";position:absolute;left:0;top:0;bottom:0;background:var(--ink-1);
      width:var(--w,0%)}
    /* the arrival: photograph settles, copy rises - the pinned chapters' timings */
    /* ⚠ the settle scales the IMAGE inside a clipped figure, not the figure:
       a scaled figure hung 2% past the screen's edge before arrival and let
       the page scroll sideways (overflowX 9 at 393, caught by the suite) */
    html.stack .blk > figure{opacity:0;overflow:hidden;transition:opacity .85s var(--ease)}
    html.stack .blk > figure img{transform:scale(1.04);transition:transform 1.4s var(--ease)}
    html.stack .blk.in > figure{opacity:1}
    html.stack .blk.in > figure img{transform:none}
    html.stack .blk .blk-copy{opacity:0;transform:translateY(26px);
      transition:opacity .62s var(--ease) .12s,transform .62s var(--ease) .12s}
    html.stack .blk.in .blk-copy{opacity:1;transform:none}
    /* the photograph: whole, edge to edge, foot dissolving into black */
    html.stack .blk > figure{position:relative;margin:0;background:#000;line-height:0;translate:none}
    html.stack .blk > figure img{display:block;width:100%;height:auto;object-fit:fill}
    html.stack .blk > figure::after{content:"";position:absolute;inset:0;pointer-events:none;
      background:linear-gradient(180deg,rgba(0,0,0,0) 56%,rgba(0,0,0,.62) 86%,#000 100%)}
    /* the guard band: solid black for the strip the collapsing browser bar
       can expose (large minus small viewport), then the usual fade, so a
       block that is one small-viewport tall never lets the NEXT picture
       peek - only black */
    html.stack .blk > figure::before{content:"";position:absolute;left:0;right:0;top:0;
      height:96px;height:calc(100lvh - 100svh + 96px);z-index:1;pointer-events:none;
      background:linear-gradient(180deg,#000 0,#000 calc(100lvh - 100svh),
        rgba(0,0,0,.55) calc(100lvh - 100svh + 38px),rgba(0,0,0,0) 100%)}
    html.stack .blk-copy{padding:clamp(18px,3.4vw,26px) var(--gutter) 0}
    /* ⚠ PROJECTS AND SERVICES SCROLL FREELY (Daniel, 3 Sep: "I want all of
       the projects to scroll freely like it did on the original version...
       likewise for the services"). Their blocks are natural height with no
       snap stop: photograph, copy, a small index line, a gap, the next. The
       section itself still snaps at its start, and because a section taller
       than the screen is a snap area larger than the snapport, the browser
       lets the scroll rest anywhere inside it - no yank back to the top.
       The arrival (photograph settles, copy rises) is unchanged. */
    /* ⚠ ONE PROJECT PER SWIPE (Daniel, 3 and 4 Sep, from his phone: "every
       click should be a full page"; "sometimes you'll scroll to the next
       project, and then it will go to the next project, but only half the
       screen. I want it to snap"). Cause of the half-screen stop: dvh (and
       lvh) change or exceed the visible screen while Safari's bar collapses,
       and a snap area taller than the snapport may legally rest ANYWHERE
       inside it - so a flick settled part-way. 100svh is the only height
       that is both stable and never taller than the screen, so mandatory +
       stop:always is exact. vh is the fallback where svh is unknown. */
    html.stack .proj .blk{height:100vh;height:100svh;min-height:0;padding:0;overflow:hidden;
      scroll-snap-align:start;scroll-snap-stop:always}
    /* the photograph takes the height the page can spare: never shorter than
       the whole-width picture (62.5vw), never taller than 48% of the screen,
       and otherwise whatever is left after ~340px of title, copy and rail -
       so a tall phone shows more picture instead of more black */
    html.stack .proj .blk > figure{height:clamp(min(62.5vw, 36%), calc(100% - 340px), 48%);flex:none}
    html.stack .proj .blk > figure img{height:100%;object-fit:cover}
    html.stack .proj .blk .blk-copy{flex:1;min-height:0;padding-bottom:64px}
    html.stack .proj .blk .blk-rail{position:absolute;left:0;right:0;bottom:0;padding:18px var(--gutter) 26px}
    /* the chapter head rides on the first project's page, compact */
    html.stack .proj .blk:first-child{padding-top:0}
    /* ---- projects ---- */
    html.stack .proj-media,html.stack .proj-scrim{display:none}
    html.stack .proj-copy{position:static;display:block}
    html.stack .proj-copy .inner{padding:0;max-width:none}
    /* the head is a slim band at the top of the first page (a page of its
       own would be the black void Daniel keeps rejecting) */
    html.stack .proj-head{padding:98px var(--gutter) 14px;margin:0;max-width:none}
    html.stack .proj-head .kicker{margin:0 0 6px}
    html.stack .proj-head h2{font-size:clamp(17px,4.8vw,20px);letter-spacing:0}
    html.stack .proj-head h2 .ln{display:inline}
    html.stack .proj-head h2 .ln.dim{margin-left:.35em}
    html.stack .proj .pin-vp{padding-top:0}
    /* the section's own snap point is page 1 (head + first project): a swipe out of the floor cannot pass it */
    html.stack section.proj{scroll-snap-stop:always}
    /* the first page is head + block: the block gives back the head's height */
    html.stack .proj .proj-steps > .blk:first-child{height:calc(100vh - var(--proj-head-h,118px));height:calc(100svh - var(--proj-head-h,118px));scroll-snap-align:none}
    /* an iPhone SE with both Safari bars is 553px: drop the statement from
       the head band, shorten the photograph, tighten the copy */
    @media (max-height:600px){
      html.stack .proj-head{padding:68px var(--gutter) 8px}
      html.stack .proj-head h2{display:none}
      html.stack .proj .blk > figure{height:clamp(30%, calc(100% - 300px), 36%)}
      html.stack .proj .blk .blk-copy{padding-top:14px}
      html.stack .proj-steps h3{font-size:clamp(20px,5.6vw,24px);margin-bottom:8px}
      html.stack .proj .blk p{font-size:13.5px;line-height:1.5}
    }
    html.stack .proj-steps{min-height:0;max-width:none}
    html.stack .proj-steps h3{margin-bottom:clamp(12px,2.4vw,18px)}
    html.stack .proj-steps p{max-width:none}
    /* ---- services: TILES, copy over the photograph (Daniel, 3 Sep: "place
       the text over the photos... a point of difference compared to the
       project photos... very close to each other, almost tiled, still keep
       the bleed"). Each service is an 8:7 tile, photograph covering it, the
       icon top-left, a small index top-right, title and line at the foot on
       a bleed that runs to black; 3px between tiles. The head above them is
       the kicker plus a phone-only statement. ---- */
    html.stack .svc .inner{display:block;padding:0}
    html.stack .svc .inner > div:first-child{display:block;padding:88px var(--gutter) 26px}
    html.stack .svc .inner > div:first-child .kicker{margin-bottom:14px}
    html.stack .svc-h2{display:block;font-size:clamp(26px,7.6vw,34px)}
    html.stack .svc-index{display:none}
    html.stack .svc-media,html.stack .svc-edge{display:none}
    html.stack .svc-panel{padding:0;overflow:visible;background:transparent}
    html.stack .svc-panel .pin-steps{min-height:0;counter-reset:svc}
    html.stack .svc .blk{padding:0;margin:0 0 3px;min-height:0;background:#000;counter-increment:svc;
      scroll-snap-align:none;scroll-snap-stop:normal}
    html.stack .svc .blk:last-child{margin-bottom:0;padding-bottom:0}
    html.stack .svc .blk > figure{aspect-ratio:8/7}
    html.stack .svc .blk > figure img{height:100%;object-fit:cover}
    /* copy CENTRED on the tile (Daniel, 3 Sep: "whether it's in the centre...
       yeah, centre is good, so they're like tiles"): the photograph is
       darkened evenly with a heavier band through the middle where the
       words sit, the icon above the title, the index top-right */
    html.stack .svc .blk > figure::before{display:none}
    html.stack .svc .blk > figure::after{background:linear-gradient(180deg,rgba(0,0,0,.40) 0,rgba(0,0,0,.72) 36%,rgba(0,0,0,.72) 64%,rgba(0,0,0,.40) 100%)}
    html.stack .svc .blk .blk-copy{position:absolute;inset:0;z-index:2;display:flex;flex-direction:column;
      justify-content:center;align-items:center;text-align:center;padding:24px clamp(22px,7vw,34px)}
    html.stack .svc .blk .blk-copy::before{content:"0" counter(svc);position:absolute;top:22px;right:var(--gutter);
      font-size:11px;letter-spacing:.18em;color:rgba(255,255,255,.72);font-variant-numeric:tabular-nums}
    html.stack .svc-panel .svc-icon{position:static;margin:0 0 14px;width:38px;height:38px;
      color:#fff;filter:drop-shadow(0 1px 6px rgba(0,0,0,.85))}
    html.stack .svc .blk h3{color:#fff;font-size:clamp(21px,6vw,26px);margin:0 0 10px}
    html.stack .svc .blk p{color:#ececec;font-size:14px;line-height:1.55;max-width:26em;margin:0}
    html.stack .svc .blk .blk-rail{display:none}
    /* ---- by the numbers: ONE screen, all eight (Daniel, 3 Sep) ---- */
    html.stack .numbers .wrap{padding:72px var(--gutter) 60px;max-width:none;min-height:100vh;min-height:100lvh;
      box-sizing:border-box;display:flex;flex-direction:column;justify-content:center;
      scroll-snap-align:start;scroll-snap-stop:always}
    html.stack .numbers .wrap > .kicker{display:block;margin-bottom:18px}
    html.stack .capgrid{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--line);border:1px solid var(--line)}
    html.stack .capgrid > div{background:var(--ground);padding:16px 14px 14px;min-height:0;
      opacity:0;transform:translateY(14px);
      transition:opacity .55s var(--ease) calc(var(--i,0) * .07s),transform .55s var(--ease) calc(var(--i,0) * .07s)}
    html.stack .numbers .blk.in .capgrid > div{opacity:1;transform:none}
    html.stack .capgrid > div b{font-size:clamp(34px,10.5vw,46px);line-height:1}
    html.stack .capgrid > div b sup{font-size:.36em;top:-1.2em}
    html.stack .capgrid > div > span{display:block;margin-top:10px;font-size:10.5px;letter-spacing:.12em;
      line-height:1.45;min-height:0;max-width:none;color:var(--ink-3)}
    @media (max-height:700px){
      html.stack .numbers .wrap{padding:60px var(--gutter) 40px}
      html.stack .capgrid > div{padding:11px 12px 10px}
      html.stack .capgrid > div b{font-size:clamp(28px,8.6vw,36px)}
      html.stack .capgrid > div > span{margin-top:6px;font-size:10px}
    }
    /* ---- process: the head as one block, the five steps as ONE block
       (Daniel: a step a screen was "a waste of space... swiping forever"),
       arriving in a stagger ---- */
    /* ⚠ ONE SCREEN. Daniel, 3 Sep, on the phone: block 1 was a full screen
       of black with the heading floating in it, block 2 a plain list. The
       heading and the five steps now share one snapping screen: the QC
       line is dropped here (it is on the desktop and in the list), each
       row is number, name, one line. He rejected five phone blocks for this
       chapter on 2 Sep ("swiping there forever"); do not re-propose. */
    html.stack .proc .inner{min-height:100vh;min-height:100lvh;display:flex;flex-direction:column;justify-content:center;
      scroll-snap-align:start;scroll-snap-stop:always;padding:72px var(--gutter) 64px;box-sizing:border-box;max-width:none}
    html.stack .proc-head{min-height:0;margin:0;padding:0 0 18px;scroll-snap-align:none;display:block}
    html.stack .proc-head h2{font-size:clamp(28px,8.4vw,36px)}
    html.stack .proc-qc{display:none}
    html.stack .proc-track.blk{display:block;padding:0;min-height:0;gap:0;background:none}
    html.stack .proc-track > div{min-height:0;padding:11px 0;border-top:1px solid var(--line);
      display:grid;grid-template-columns:30px 1fr;column-gap:8px;align-items:baseline;
      color:var(--ink-2);opacity:0;transform:translateY(14px);
      transition:opacity .55s var(--ease) calc(var(--i,0) * .09s),transform .55s var(--ease) calc(var(--i,0) * .09s)}
    html.stack .proc-track.blk.in > div{opacity:1;transform:none}
    html.stack .proc-track > div:last-child{border-bottom:1px solid var(--line)}
    html.stack .proc-track em{font-size:11px;color:var(--ink-3);grid-row:1/3}
    html.stack .proc-track b{margin:0;font-size:17px;display:block;color:var(--ink-1)}
    html.stack .proc-track p{margin:2px 0 0;font-size:14px;line-height:1.5;grid-column:2;max-width:none}
    html.stack .proc-track .long{display:none}
    html.stack .proc-track .short{display:inline}
    @media (max-height:700px){
      html.stack .proc .inner{padding:58px var(--gutter) 40px}
      html.stack .proc-head{padding-bottom:10px}
      html.stack .proc-track > div{padding:8px 0}
    }
    /* ---- the natural sections: their own arrival keeps working (.rv) ---- */
    html.stack .statement .pin-vp{padding-top:clamp(84px,12vh,120px)}
    html.stack .lead .pin-vp{padding-top:64px}
    html.stack .plate-head{padding-top:clamp(88px,12vh,120px)}
    html.stack .statement .pin-steps{margin-top:clamp(22px,4vw,34px)}
    html.stack .statement .pin-step + .pin-step{margin-top:clamp(28px,5vw,40px)}
    html.stack .lead-steps .pin-step + .pin-step{margin-top:18px}
    html.stack .plate-steps p + p{margin-top:12px}
  }
