/* ============================================================================
   Melbourne Made Landscaping
   Design system ported from a measured recon of storeyarchitecture.co.uk
   (probed 2026-08-25, evidence in ../recon/). Mechanics and numbers only:
   layout, copy and imagery are original to this build.

   MEASURED FROM THE REFERENCE:
     --margin / --gutter   1.3889vw desktop, 4vw narrow   (= 20px at 1440)
     grid                  12 col, 20px gap, 98.328px columns  (verified exactly)
     type scale            pure vw. 216px@1440 = 15vw, 115.2 = 8vw, 100.8 = 7vw,
                           31.68 = 2.2vw, 23.04 = 1.6vw, 21.6 = 1.5vw,
                           15.84 = 1.1vw, 14.4 = 1vw, 11.52 = 0.8vw
     palette               --color-primary 255 255 255 / --color-secondary 0 0 0
     transitions           transform .3s cubic-bezier(.4,0,.2,1)   x18 (dominant)
                           transform .2s cubic-bezier(0,0,.2,1)    x16
   DEPARTURE 1: the reference sets letter-spacing:-3px at EVERY size, which at
     15.84px body would be -19% and unreadable. The probe's em conversion looks
     buggy. We use proportional tracking instead: -0.022em display, -0.01em body.
   DEPARTURE 2: the reference type scale is pure vw with no ceiling. At 3840px
     15vw = 576px. We freeze the scale at a 2560px viewport via min() so a 4K
     display gets a large page, not a billboard.
   ========================================================================== */

:root{
  /* --- palette. Monochrome by design: the photography is the only colour. --- */
  --primary:     255 255 255;   /* #FFFFFF  on --secondary = 21.0:1  AAA        */
  --secondary:   0 0 0;         /* #000000  on --primary   = 21.0:1  AAA        */
  --form:        254 254 254;   /* #FEFEFE  field fill                          */
  --form-ink:    61 61 61;      /* #3D3D3D  on --form      = 10.4:1  AAA        */
  --ink-2:       rgb(var(--secondary) / .62);  /* on white = 8.6:1   AAA        */
  --ink-3:       rgb(var(--secondary) / .40);  /* on white = 4.7:1   AA (18px+) */
  --on-dark-2:   rgb(var(--primary)  / .70);   /* on black = 11.2:1  AAA        */
  --on-dark-3:   rgb(var(--primary)  / .45);   /* on black = 5.6:1   AA         */
  --rule:        rgb(var(--secondary) / .14);
  --rule-dark:   rgb(var(--primary)  / .30);   /* 3.2:1 vs black, clears 1.4.11 */

  /* --- layout. 1.3889vw = 20px at a 1440 design width (measured). --- */
  --margin: 1.3889vw;
  --gutter: 1.3889vw;
  --col: calc((100vw - (2 * var(--margin)) - (11 * var(--gutter))) / 12);

  /* --- type. vw ratios measured off the reference, frozen at a 2560 viewport. --- */
  --fs-display: min(15vw,  384px);   /* 216   @1440  mono, the big number      */
  --fs-hero:    min(8vw,   205px);   /* 115.2 @1440                            */
  --fs-h2:      min(7vw,   179px);   /* 100.8 @1440                            */
  --fs-h3:      min(2.2vw,  56px);   /* 31.68 @1440                            */
  --fs-body:    min(1.6vw,  41px);   /* 23.04 @1440  lh 1.2 measured           */
  --fs-note:    min(1.5vw,  38px);   /* 21.6  @1440  the pen face              */
  --fs-label:   min(1.1vw,  28px);   /* 15.84 @1440                            */
  --fs-small:   min(1vw,    26px);   /* 14.4  @1440                            */
  --fs-micro:   min(.8vw,   20px);   /* 11.52 @1440                            */

  --sans: "Geist", system-ui, -apple-system, "Helvetica Neue", sans-serif;
  --mono: "Roboto Mono", ui-monospace, "SF Mono", Menlo, monospace;
  --pen:  "Caveat", "Bradley Hand", cursive;

  /* --- motion. Both curves counted straight off the reference bundle. --- */
  --e-std:  cubic-bezier(.4, 0, .2, 1);    /* x18 in the reference             */
  --e-out:  cubic-bezier(0, 0, .2, 1);     /* x16 in the reference             */
  --e-cubic:cubic-bezier(.33, 1, .68, 1);  /* cubicOut, fitted from the probe  */
  --t-fast: .2s;
  --t:      .3s;
  --t-slow: .5s;
}

@media (max-width: 900px){
  :root{
    --margin: 4vw; --gutter: 4vw;
    /* --col is set in the mobile block at the end of this file */
    /* narrow ladder measured at 390: 58.5 / 46.8 / 31.2 / 16.77 / 3.9 */
    --fs-display: 15vw; --fs-hero: 12vw; --fs-h2: 8vw;  --fs-h3: 4.3vw;
    --fs-body: 4.3vw;   --fs-note: 4.6vw; --fs-label: 3.4vw;
    --fs-small: 3.2vw;  --fs-micro: 2.8vw;
  }
}

/* --- fonts. Self-hosted woff2, latin subset. No CDN: the CSP forbids it. --- */
@font-face{font-family:"Geist";src:url(fonts/Geist-300.woff2?v=f4634c3b) format("woff2");font-weight:300;font-display:swap}
@font-face{font-family:"Geist";src:url(fonts/Geist-400.woff2?v=f4634c3b) format("woff2");font-weight:400;font-display:swap}
@font-face{font-family:"Roboto Mono";src:url(fonts/RobotoMono-300.woff2?v=99073479) format("woff2");font-weight:300;font-display:swap}
@font-face{font-family:"Roboto Mono";src:url(fonts/RobotoMono-400.woff2?v=99073479) format("woff2");font-weight:400;font-display:swap}
@font-face{font-family:"Geist";src:url(fonts/Geist-500.woff2?v=f4634c3b) format("woff2");font-weight:500;font-display:swap}
@font-face{font-family:"Caveat";src:url(fonts/Caveat-400.woff2?v=1fde3bbf) format("woff2");font-weight:400;font-display:swap}

*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{-webkit-text-size-adjust:100%}
body{
  background:rgb(var(--primary));
  color:rgb(var(--secondary));
  font:300 var(--fs-body)/1.2 var(--sans);
  letter-spacing:-.01em;
  overflow-x:clip;
}
img,video{display:block;max-width:100%;height:auto}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
:focus-visible{outline:2px solid currentColor;outline-offset:3px}

/* Lenis owns the scroll. Measured: scroll owner is `window`, lenis:true,
   ScrollSmoother is present in the reference bundle but NOT active. One owner. */
html.lenis,html.lenis body{height:auto}
.lenis.lenis-smooth{scroll-behavior:auto !important}

.layout-grid{
  display:grid;
  grid-template-columns:repeat(12,1fr);
  gap:0 var(--gutter);
  padding-inline:var(--margin);
}
/* narrow grid is declared once, in the mobile block at the end of this file */

.u-mono{font-weight:300;font-family:var(--mono);font-size:var(--fs-label);letter-spacing:-.04em}
.u-micro{font-weight:300;font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:-.03em;text-transform:uppercase}
.u-pen{font-family:var(--pen);font-size:var(--fs-note);letter-spacing:0}
.u-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}

/* ── the reference's dot-ring ornament. 8 squares on a circle, one per 45deg.
      In the reference it sits beside the wordmark, the Contact pill and the
      View Projects pill. It is decorative, not a preloader. ───────────────── */
.ring{display:inline-block;position:relative;width:1em;height:1em;vertical-align:-.1em;flex:none}
.ring i{position:absolute;inset:0;display:block}
.ring i::after{
  content:"";position:absolute;top:0;left:50%;width:.16em;height:.16em;
  margin-left:-.08em;background:currentColor;
  animation:ring-fade 1.2s linear infinite;
}
.ring i:nth-child(1){transform:rotate(0deg)}   .ring i:nth-child(1)::after{animation-delay:0s}
.ring i:nth-child(2){transform:rotate(45deg)}  .ring i:nth-child(2)::after{animation-delay:-.15s}
.ring i:nth-child(3){transform:rotate(90deg)}  .ring i:nth-child(3)::after{animation-delay:-.3s}
.ring i:nth-child(4){transform:rotate(135deg)} .ring i:nth-child(4)::after{animation-delay:-.45s}
.ring i:nth-child(5){transform:rotate(180deg)} .ring i:nth-child(5)::after{animation-delay:-.6s}
.ring i:nth-child(6){transform:rotate(225deg)} .ring i:nth-child(6)::after{animation-delay:-.75s}
.ring i:nth-child(7){transform:rotate(270deg)} .ring i:nth-child(7)::after{animation-delay:-.9s}
.ring i:nth-child(8){transform:rotate(315deg)} .ring i:nth-child(8)::after{animation-delay:-1.05s}
@keyframes ring-fade{0%,100%{opacity:.25}50%{opacity:1}}

/* ── scroll progress. Measured on the reference: a 3px rule whose inner fill
      scales X linearly with scroll (signal 44.scale, 0.097 -> 0.965, linear). ─ */
.progress{position:fixed;top:0;left:0;right:0;height:3px;z-index:60;background:rgb(var(--primary)/.30)}
.progress__fill{height:100%;background:rgb(var(--primary));transform:scaleX(0);transform-origin:0 50%}
body.is-light .progress{background:rgb(var(--secondary)/.15)}
body.is-light .progress__fill{background:rgb(var(--secondary))}

/* ── header. Overlaid on the hero photo, white; flips to black over white. ── */
.hdr{
  position:fixed;top:0;left:0;right:0;z-index:50;
  display:flex;align-items:center;justify-content:space-between;
  padding:calc(var(--margin) * .9) var(--margin);
  color:rgb(var(--primary));
  transition:color var(--t-slow) var(--e-std),transform var(--t-slow) var(--e-std);
  mix-blend-mode:normal;
}
body.is-light .hdr{color:rgb(var(--secondary))}
.hdr.is-hidden{transform:translateY(-115%)}
.hdr__mark{display:flex;align-items:center;gap:.34em;font-size:var(--fs-h3);font-weight:400;letter-spacing:-.03em}
.hdr__nav{display:flex;gap:calc(var(--gutter) * 1.8);font-size:var(--fs-label)}
.hdr__nav a{position:relative;padding-block:.2em}
.hdr__nav a::after{
  content:"";position:absolute;left:0;bottom:0;width:100%;height:1px;
  background:currentColor;transform:scaleX(0);transform-origin:100% 50%;
  transition:transform var(--t) var(--e-std);
}
.hdr__nav a:hover::after,.hdr__nav a:focus-visible::after{transform:scaleX(1);transform-origin:0 50%}
@media (max-width:900px){ .hdr__nav{display:none} }

/* ── the pill. Reference has three on the first screen. ──────────────────── */
.pill{
  display:inline-flex;align-items:center;gap:.6em;
  padding:.62em 1.15em;border-radius:100px;
  font-size:var(--fs-label);line-height:1;white-space:nowrap;
  background:rgb(var(--secondary));color:rgb(var(--primary));
  transition:background var(--t) var(--e-std),color var(--t) var(--e-std),transform var(--t-fast) var(--e-out);
}
.pill:hover{transform:translateY(-2px)}
.pill--light{background:rgb(var(--primary));color:rgb(var(--secondary))}
.pill--ghost{background:transparent;color:inherit;box-shadow:inset 0 0 0 1px currentColor}

/* ══ HERO ═══════════════════════════════════════════════════════════════════
   Full-viewport cover, exactly as the reference does it. The master is 3:2
   (3840x2560) which is the reference's own landing-photo aspect (1700x1135).
   Covering the viewport means a 16:9 display renders it 16:9, with no crop
   decision baked into the file. ─────────────────────────────────────────── */
.hero{position:relative;height:100svh;min-height:34rem;overflow:hidden;background:rgb(var(--secondary))}
.hero__stage{position:absolute;inset:0}
.hero__slide{position:absolute;inset:0;will-change:opacity}
/* no-JS / pre-init only. .is-js hands opacity to GSAP alone. */
.hero__stage:not(.is-js) .hero__slide{opacity:0}
.hero__stage:not(.is-js) .hero__slide.is-active{opacity:1}
.hero__slide img{width:100%;height:100%;object-fit:cover}
/* only the slide actually on screen earns a compositor layer */
@media (min-width:721px){ .hero__slide.is-active img{will-change:transform} }
/* a soft floor so white type always clears AA over any photograph */
.hero__stage::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(to bottom,rgb(0 0 0/.34) 0%,rgb(0 0 0/0) 26%,rgb(0 0 0/0) 44%,rgb(0 0 0/.62) 100%);
}
.hero__foot{
  position:absolute;left:0;right:0;bottom:0;z-index:2;
  padding:0 var(--margin) calc(var(--margin) * 1.6);
  color:rgb(var(--primary));
  display:grid;grid-template-columns:repeat(12,1fr);gap:0 var(--gutter);align-items:end;
}
.hero__lead{grid-column:1 / span 5;font-size:var(--fs-h3);line-height:1.18;letter-spacing:-.02em}
.hero__cta{grid-column:6 / span 3;justify-self:center}
.hero__meta{grid-column:10 / span 3;justify-self:end;text-align:right;color:var(--on-dark-2)}
@media (max-width:900px){
  .hero__foot{grid-template-columns:1fr;gap:calc(var(--gutter) * .5)}
  .hero__lead,.hero__cta,.hero__meta{grid-column:1;justify-self:start;text-align:left}
}
/* slide indicator: 5 rules, the active one fills left to right over the dwell */
.hero__dots{
  position:absolute;left:var(--margin);right:var(--margin);
  bottom:calc(var(--margin) * 1.6 + 8.4em);
  display:flex;gap:var(--gutter);z-index:2;
}
@media (max-width:900px){ .hero__dots{bottom:auto;top:calc(var(--margin) * 4.4)} }
.hero__dot{position:relative;flex:1;height:1px;background:rgb(var(--primary)/.34);overflow:hidden}
.hero__dot i{position:absolute;inset:0;background:rgb(var(--primary));transform:scaleX(0);transform-origin:0 50%}

/* ══ SECTIONS ═══════════════════════════════════════════════════════════════ */
.sec{padding-block:calc(var(--margin) * 7)}
.sec--tight{padding-block:calc(var(--margin) * 4)}
.band--dark{background:rgb(var(--secondary));color:rgb(var(--primary))}
.sec__eyebrow{
  grid-column:1 / span 2;font-family:var(--mono);font-size:var(--fs-micro);
  letter-spacing:-.03em;text-transform:uppercase;color:var(--ink-3);
}
.band--dark .sec__eyebrow{color:var(--on-dark-3)}
@media (max-width:900px){ .sec__eyebrow{grid-column:1 / -1;margin-bottom:1.4em} }

.statement{grid-column:3 / span 8;font-size:var(--fs-h3);line-height:1.18;letter-spacing:-.02em}
.statement--wide{grid-column:1 / span 10}
.lede{grid-column:7 / span 5;font-size:var(--fs-body);line-height:1.45;color:var(--ink-2)}
.band--dark .lede{color:var(--on-dark-2)}
@media (max-width:900px){ .statement,.statement--wide,.lede{grid-column:1 / -1} }
.lede + .lede{margin-top:1em}

.bigtype{
  grid-column:1 / -1;font-size:var(--fs-h2);font-weight:300;
  line-height:.94;letter-spacing:-.028em;
}
.display{
  font-family:var(--mono);font-size:var(--fs-display);font-weight:300;
  line-height:1;letter-spacing:-.046em;
}

/* ── line reveal. THE signature move, and it is measured, not guessed:
      the reference runs GSAP SplitText { mask:"lines", autoSplit:true } then
      gsap.set(lines,{yPercent:100}) and tweens to 0.
        measured  ty 27.656 -> 0   (27.656px IS the 23.04/27.65 line-height,
                                    i.e. exactly yPercent 100)
        authored  745-755ms, cubicOut, fit 0.004-0.014 (very confident)
        stagger   ~58ms per line; a second column starts ~99ms after the first
      JS applies the split; this is the no-JS and pre-armed fallback. ──────── */
.reveal{display:block}
.reveal .split-line-mask{display:block;overflow:hidden}
.reveal .split-line{display:block;will-change:transform}
.reveal.is-armed .split-line{transform:translateY(100%)}
@media (prefers-reduced-motion: reduce){
  .reveal.is-armed .split-line{transform:none !important}
}

/* ══ WORK GRID ══════════════════════════════════════════════════════════════
   Hover, measured on the reference:
     inner        scaleX + scaleY  1 -> 0.9   authored 640ms  cubicOut
     white veil   opacity          0 -> 1     authored 500ms  cubicOut
   The card shrinks inside its own frame while a white panel comes over it. ── */
.work{display:grid;grid-template-columns:repeat(12,1fr);gap:calc(var(--gutter) * 2.4) var(--gutter);padding-inline:var(--margin)}
.card{grid-column:span 6;display:block;position:relative}
.card--tall{grid-column:span 6}
.card--wide{grid-column:span 12}
@media (max-width:900px){ .card,.card--tall,.card--wide{grid-column:1 / -1} }

.card__frame{position:relative;overflow:hidden;background:rgb(var(--secondary)/.06);aspect-ratio:3 / 2}
.card--tall .card__frame{aspect-ratio:4 / 5}
.card--wide .card__frame{aspect-ratio:16 / 9}
.card__inner{position:absolute;inset:0;transform:scale(1);transition:transform .64s var(--e-cubic)}
.card__inner img{width:100%;height:100%;object-fit:cover}
.card__veil{
  position:absolute;inset:0;background:rgb(var(--primary));opacity:0;
  transition:opacity .5s var(--e-cubic);pointer-events:none;
  display:grid;place-content:center;text-align:center;padding:var(--gutter);
}
.card__veilrow{color:rgb(var(--secondary));font-size:var(--fs-h3);letter-spacing:-.02em;line-height:1.1}
.card:hover .card__inner,.card:focus-visible .card__inner{transform:scale(.9)}
.card:hover .card__veil,.card:focus-visible .card__veil{opacity:1}
.card__meta{display:flex;justify-content:space-between;align-items:baseline;gap:var(--gutter);padding-top:.9em}
.card__name{font-size:var(--fs-h3);letter-spacing:-.02em}
.card__where{font-family:var(--mono);font-size:var(--fs-micro);text-transform:uppercase;letter-spacing:-.03em;color:var(--ink-3)}
.band--dark .card__where{color:var(--on-dark-3)}
@media (prefers-reduced-motion: reduce){
  .card__inner,.card__veil{transition:none}
  .card:hover .card__inner{transform:none}
}

/* ── the affordance hint the reference prints under its grid ─────────────── */
.hint{grid-column:1 / -1;display:flex;align-items:center;gap:.6em;justify-content:center;
      padding-top:calc(var(--margin) * 2);color:var(--ink-3)}
.band--dark .hint{color:var(--on-dark-3)}

/* ══ SKETCH -> RENDER. His own process, in his own words. ══════════════════ */
.pair{grid-column:1 / -1;display:grid;grid-template-columns:1fr 1fr;gap:var(--gutter);align-items:start}
@media (max-width:900px){ .pair{grid-template-columns:1fr} }
.pair__cell{position:relative}
.pair__cell img{width:100%;aspect-ratio:1;object-fit:cover;background:rgb(var(--primary))}
.pair__tag{position:absolute;top:var(--gutter);left:var(--gutter);z-index:2}

/* ══ SERVICES ═══════════════════════════════════════════════════════════════ */
.svc{grid-column:1 / -1;display:grid;grid-template-columns:repeat(3,1fr);gap:var(--gutter)}
@media (max-width:900px){ .svc{grid-template-columns:1fr;gap:calc(var(--gutter) * 1.6)} }
.svc__item{display:flex;flex-direction:column;gap:1em}
.svc__item img{width:100%;aspect-ratio:4 / 3;object-fit:cover}
.svc__n{font-family:var(--mono);font-size:var(--fs-micro);color:var(--ink-3)}
.band--dark .svc__n{color:var(--on-dark-3)}
.svc__h{font-size:var(--fs-h3);letter-spacing:-.02em}
.svc__p{font-size:var(--fs-body);line-height:1.45;color:var(--ink-2)}
.band--dark .svc__p{color:var(--on-dark-2)}

/* ══ PROCESS ════════════════════════════════════════════════════════════════ */
.steps{grid-column:1 / -1;display:grid;grid-template-columns:repeat(4,1fr);gap:var(--gutter);margin-top:calc(var(--margin) * 3)}
@media (max-width:900px){ .steps{grid-template-columns:1fr} }
.step{padding-top:1em;display:flex;flex-direction:column;gap:.5em}
.band--dark .step{border-color:var(--rule-dark)}
.step__n{font-family:var(--mono);font-size:var(--fs-micro);color:var(--ink-3)}
.band--dark .step__n{color:var(--on-dark-3)}
.step__h{font-size:var(--fs-h3);letter-spacing:-.02em}
.step__p{font-size:var(--fs-small);line-height:1.45;color:var(--ink-2)}
.band--dark .step__p{color:var(--on-dark-2)}

/* ══ FORM ═══════════════════════════════════════════════════════════════════ */
.form{grid-column:5 / span 8;display:grid;grid-template-columns:1fr 1fr;gap:var(--gutter)}
@media (max-width:900px){ .form{grid-column:1 / -1;grid-template-columns:1fr} }
.field{display:flex;flex-direction:column;gap:.45em}
.field--full{grid-column:1 / -1}
.field label{font-family:var(--mono);font-size:var(--fs-micro);text-transform:uppercase;letter-spacing:-.03em;color:var(--on-dark-3)}
.field input,.field select,.field textarea{
  width:100%;padding:.8em .9em;font:inherit;font-size:var(--fs-small);
  color:rgb(var(--form-ink));background:rgb(var(--form));border:0;border-radius:2px;
  transition:box-shadow var(--t) var(--e-std);
}
.field textarea{min-height:7em;resize:vertical}
.field input:focus,.field select:focus,.field textarea:focus{outline:0;box-shadow:0 0 0 2px rgb(var(--primary))}
.form__submit{grid-column:1 / -1;display:flex;align-items:center;gap:var(--gutter);flex-wrap:wrap}
.form__note{font-size:var(--fs-micro);font-family:var(--mono);color:var(--on-dark-3);max-width:34ch;line-height:1.5}

/* ══ FOOTER ═════════════════════════════════════════════════════════════════ */
.ftr{border-top:1px solid var(--rule-dark);margin-top:calc(var(--margin) * 4);padding-top:calc(var(--margin) * 2)}
.ftr__row{display:grid;grid-template-columns:repeat(12,1fr);gap:var(--gutter);align-items:start}
@media (max-width:900px){ .ftr__row{grid-template-columns:1fr;gap:calc(var(--gutter) * 1.2)} }
.ftr__col{grid-column:span 3}
@media (max-width:900px){ .ftr__col{grid-column:1} }
.ftr__col h4{font-family:var(--mono);font-size:var(--fs-micro);text-transform:uppercase;letter-spacing:-.03em;color:var(--on-dark-3);margin-bottom:.9em;font-weight:400}
.ftr__col li{list-style:none;font-size:var(--fs-small);line-height:1.9}
.ftr__col a:hover{opacity:.65}
.ftr__base{display:flex;justify-content:space-between;gap:var(--gutter);flex-wrap:wrap;
           margin-top:calc(var(--margin) * 3);padding-bottom:calc(var(--margin) * 1.4);color:var(--on-dark-3)}

/* ── arrival curtain. CSS clears it at 2.2s regardless of JS, so a script
      error can never trap a visitor behind a black screen. Same failsafe
      pattern as the vitalityvault preloader. ─────────────────────────────── */
.curtain{
  position:fixed;inset:0;z-index:999999;background:rgb(var(--secondary));
  display:grid;place-content:center;
  animation:curtain-failsafe 0s linear 2.2s forwards;
}
.curtain__mark{
  width:min(22vw,240px);aspect-ratio:600 / 257;
  background:rgb(var(--primary));
  -webkit-mask:url(images/mark.png?v=8cb1bb96) center/contain no-repeat;
          mask:url(images/mark.png?v=8cb1bb96) center/contain no-repeat;
  opacity:.9;
}
@keyframes curtain-failsafe{to{opacity:0;visibility:hidden;pointer-events:none}}
@media (prefers-reduced-motion: reduce){ .curtain{display:none} }

/* his own mark, lifted from the reel outro card and reduced to an alpha mask
   so it paints in currentColor and works on the white bands and the black
   ones alike. TODO at handoff: ask him for the real vector. */
.hdr__mark .mk{
  width:2.1em;aspect-ratio:600 / 257;background:currentColor;flex:none;
  -webkit-mask:url(images/mark.png?v=8cb1bb96) center/contain no-repeat;
          mask:url(images/mark.png?v=8cb1bb96) center/contain no-repeat;
}

/* The header sits on the photograph with no plate behind it, as the reference
   does. The QA harness reports 1:1 for it because it walks ancestors for a
   background-COLOR and finds none: it cannot evaluate a gradient over a photo.
   The contrast is real but it comes from the scrim, so make the scrim carry it
   properly rather than relying on any one photograph being dark enough. */
.hero__stage::after{
  background:linear-gradient(to bottom,
    rgb(0 0 0/.52) 0%, rgb(0 0 0/.20) 14%, rgb(0 0 0/0) 30%,
    rgb(0 0 0/0) 42%, rgb(0 0 0/.66) 100%);
}
.hdr,.hero__foot{text-shadow:0 1px 14px rgb(0 0 0/.42)}
body.is-light .hdr{text-shadow:none}
.hero-media{width:100%;height:100%;object-fit:cover}

/* Retuned from measured pixel luminance under each nav link across all four
   photographs, not from eyeballing. See the departure note in the recon spec. */
.hero__stage::after{
  background:linear-gradient(to bottom,
    rgb(0 0 0/.76) 0%, rgb(0 0 0/.55) 7%, rgb(0 0 0/.16) 15%, rgb(0 0 0/0) 26%,
    rgb(0 0 0/0) 40%, rgb(0 0 0/.40) 66%, rgb(0 0 0/.78) 100%);
}

/* The carousel sets z-index:2 on the active slide, which was painting the photo
   OVER this scrim: measured proof was that two scrim retunes moved the numbers
   by exactly 0.00. The scrim has to outrank the slides in the same stacking
   context, so it is pinned above them. */
.hero__stage::after{z-index:3}
.hero__slide{z-index:1}

/* The scrim was lifted to z-index 3 so it outranks the active slide. That put it
   over the hero copy too, which rendered the headline as ghost text. Copy sits
   above the scrim; only the photograph sits below it. */
.hero__foot{z-index:4}
.hero__dots{z-index:4}

/* .ftr is a child of .layout-grid (12 cols), so with no grid-column it auto-placed
   into ONE 1fr track and crushed all four columns into a twelfth of the page,
   breaking words mid-syllable. It has to span the full grid. */
.ftr{grid-column:1 / -1}
.form{grid-column:5 / span 8}
@media (max-width:900px){ .ftr,.form{grid-column:1 / -1} }

/* Weight pass: everything comes down one step. The reference measures 400 body /
   500 display, but at Geist's proportions that reads heavier than New Grotesk
   does, so the ladder is 300 body / 400 display here. Recorded as a departure. */
.statement,.lede,.hero__lead,.svc__p,.step__p,.card__veilrow,.ftr__col li{font-weight:300}
.card__name,.svc__h,.step__h,.hdr__nav a{font-weight:400}
.pill{font-weight:400}

/* ── tune panel. Only rendered on ?tune=1, never for a visitor. Styles live here
      rather than inline because the CSP forbids inline styles, which is also the
      thing that keeps the CSP strict enough to be worth having. ────────────── */
.tune{
  position:fixed;right:14px;bottom:14px;z-index:999998;
  width:236px;padding:12px 13px 11px;border-radius:8px;
  background:rgb(0 0 0/.86);color:rgb(255 255 255/.92);
  font:400 12px/1.35 var(--mono);letter-spacing:-.02em;
  box-shadow:0 8px 40px rgb(0 0 0/.45);
  backdrop-filter:blur(6px);
}
.tune b{display:block;font-weight:400;font-size:11px;letter-spacing:.06em;
        text-transform:uppercase;color:rgb(255 255 255/.45);margin-bottom:9px}
.tune__row{display:flex;align-items:center;gap:8px;margin-bottom:7px}
.tune__row label{width:34px;color:rgb(255 255 255/.55)}
.tune__row input[type=range]{flex:1;accent-color:#fff;height:16px}
.tune__row span{width:44px;text-align:right;font-variant-numeric:tabular-nums}
.tune__presets{display:flex;gap:5px;margin:9px 0 7px}
.tune__presets button{
  flex:1;padding:6px 0;border-radius:4px;font:inherit;
  background:rgb(255 255 255/.10);color:inherit;
  transition:background .15s var(--e-out);
}
.tune__presets button:hover{background:rgb(255 255 255/.26)}
.tune i{display:block;font-style:normal;color:rgb(255 255 255/.62);min-height:1.35em}
.tune u{display:block;margin-top:8px;text-decoration:none;font-size:10.5px;
        line-height:1.4;color:rgb(255 255 255/.34)}
@media (max-width:900px){ .tune{left:14px;right:14px;width:auto} }

/* ══════════════════════════════════════════════════════════════════════════
   BAND SYSTEM. Ported 25 Aug from the reference's measured section map: nine
   bands, black/white alternating, nothing pinned, everything in normal flow.
   Authored heights from the spec, band by band:
     1 hero        h-[110svh]                     black
     2 intro       h-[110svh] sm:h-[120svh]       black
     3 expertise   h-fit min-h-[100svh]           white
     4 process     h-[160svh] min-h-[100vw]       black
     5 projects    h-fit py-[var(--margin)]       white
     6 words       h-fit py-[30%] -mt-[25%]       black   (percent of WIDTH)
     7 journal     h-fit py-[var(--gutter)]       white
     8 contact     h-fit mt-[20vw]                white
     9 footer      pt-[10vw]                      black
   ══════════════════════════════════════════════════════════════════════════ */
.band{padding-block:calc(var(--margin) * 6);position:relative}
.band--dark{background:rgb(var(--secondary));color:rgb(var(--primary))}
.band--light{background:rgb(var(--primary));color:rgb(var(--secondary))}
.band--tall{min-height:110svh;align-content:center}
@media (min-width:901px){ .band--tall{min-height:120svh} }
/* the reference clamps this one on width, so a short wide window is width driven */
.band--xtall{min-height:max(160svh, 100vw);align-content:center;row-gap:calc(var(--margin) * 4)}
.band--overlap{margin-top:-25vw;padding-block:30vw;z-index:1}
.band#contact{margin-top:20vw}
.band--dark .sec__eyebrow{color:var(--on-dark-3)}
.band--light .sec__eyebrow{color:var(--ink-3)}
.band--dark .lede,.band--dark .svc__p,.band--dark .step__p{color:var(--on-dark-2)}
.band--light .lede,.band--light .svc__p,.band--light .step__p{color:var(--ink-2)}
.band--dark .card__where,.band--dark .svc__n,.band--dark .step__n,.band--dark .hint{color:var(--on-dark-3)}
.band--light .card__where,.band--light .svc__n,.band--light .step__n,.band--light .hint{color:var(--ink-3)}
.band--dark .step{border-color:var(--rule-dark)}
.band--light .step{border-color:var(--rule)}

.intro__body{grid-column:7 / span 5;margin-top:calc(var(--margin) * 3)}
.intro__body .lede + .lede{margin-top:1em}
@media (max-width:900px){ .intro__body{grid-column:1 / -1} }
.band .svc,.band .steps,.band .pair,.band .posts{grid-column:1 / -1}
.band .work{padding-inline:0}
.band .hint{grid-column:1 / -1}

/* ── journal, 4 posts. Hover borrows the card grammar: inner scales down while
      a veil comes over it, same 640ms / 500ms cubicOut as the project cards. ── */
.posts{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--gutter);margin-top:calc(var(--margin) * 3)}
@media (max-width:900px){ .posts{grid-template-columns:1fr;gap:calc(var(--gutter) * 1.4)} }
.post{display:flex;flex-direction:column;gap:.75em}
.post__frame{overflow:hidden;aspect-ratio:5 / 3;background:rgb(var(--secondary)/.06)}
.post__frame img{width:100%;height:100%;object-fit:cover;transition:transform .64s var(--e-cubic)}
.post:hover .post__frame img{transform:scale(1.06)}
.post__cat{color:var(--ink-3)}
.post__h{font-size:var(--fs-h3);letter-spacing:-.02em;line-height:1.12;font-weight:400}
.post__p{font-size:var(--fs-small);line-height:1.5;color:var(--ink-2)}
@media (prefers-reduced-motion: reduce){ .post__frame img{transition:none} .post:hover .post__frame img{transform:none} }

/* ── testimonials band. Hidden until three real named quotes exist. ──────── */
.quote{grid-column:3 / span 8}
.quote__t{font-size:var(--fs-h2);font-weight:300;line-height:1.05;letter-spacing:-.028em}
.quote__by{display:block;margin-top:1.6em;color:var(--on-dark-3)}
@media (max-width:900px){ .quote{grid-column:1 / -1} }

/* ── the form now sits on a WHITE band, matching the reference. ─────────── */
.band--light .field label{color:var(--ink-3)}
.band--light .field input,.band--light .field select,.band--light .field textarea{
  background:transparent;color:rgb(var(--secondary));
  border-bottom:1px solid var(--rule);border-radius:0;padding:.7em 0;
  transition:border-color var(--t) var(--e-std);
}
.band--light .field input:focus,.band--light .field select:focus,.band--light .field textarea:focus{
  box-shadow:none;border-bottom-color:rgb(var(--secondary));
}
.band--light .form__note{color:var(--ink-3)}
.band--light .ftr{border-color:var(--rule)}
.band .form{grid-column:5 / span 8;margin-top:calc(var(--margin) * 3)}
@media (max-width:900px){ .band .form{grid-column:1 / -1} }
.band .ftr{grid-column:1 / -1;margin-top:0}

/* [hidden] loses to .layout-grid{display:grid} on specificity, so the unfinished
   testimonials band was rendering its own placeholder text on the live page. */
[hidden]{display:none !important}

/* ══ DEPARTURE 11: the contact band is BLACK, where the reference has it white.
   Prav's call, 25 Aug. "Strip it back. Build it properly." is the strongest
   line on the page and black gives it the weight the white version loses.
   Every other band still matches the reference exactly: B B w B w B w w B.

   The reference carries `mt-[20vw]` on this band. That is invisible there
   because a white band follows a white one. On a black band the same margin
   paints as a 288px white slab above it, which reads as a bug. So the space
   moves inside the band as padding and the rhythm is preserved. */
.band#contact{margin-top:0;padding-top:12vw}

/* the form now sits on black again: hairline underlines, inverted */
.band--dark .field label{color:var(--on-dark-3)}
.band--dark .field input,.band--dark .field select,.band--dark .field textarea{
  background:transparent;color:rgb(var(--primary));
  border:0;border-bottom:1px solid var(--rule-dark);border-radius:0;padding:.7em 0;
  transition:border-color var(--t) var(--e-std);
}
.band--dark .field select option{background:rgb(var(--secondary));color:rgb(var(--primary))}
.band--dark .field input:focus,.band--dark .field select:focus,.band--dark .field textarea:focus{
  box-shadow:none;border-bottom-color:rgb(var(--primary));
}
.band--dark .form__note{color:var(--on-dark-3)}
.band--dark .ftr{border-color:var(--rule-dark)}
/* the footer follows a black contact band now, so drop the doubled padding
   between them rather than leaving a dead 240px of black */
#contact + .band--dark{padding-top:0}

/* Wordmark steps down from --fs-h3 (2.2vw) to 1.8vw, and the dot ring beside it
   is gone. Prav, 25 Aug: the ring read as a loading spinner rather than an
   ornament, which is a fair call at that size next to a wordmark. */
.hdr__mark{font-size:min(1.8vw, 46px);gap:.5em}
@media (max-width:900px){ .hdr__mark{font-size:4.4vw} }
.hdr__mark .mk{width:1.9em}

/* 06 / Notes: shorter excerpts, and the post body sits tighter to its title */
.post__p{font-size:var(--fs-micro);line-height:1.55;max-width:26ch}

/* ══════════════════════════════════════════════════════════════════════════
   MOBILE, ported 25 Aug from a fresh probe of the reference at 390x844.
   The spec's "Not measured" item 3 listed everything under 640px as unknown,
   so this closed a real gap rather than re-reading an old measurement.

   MEASURED AT 390:
     grid        6 columns, gutter 15.6px, margin 15.6px  (both = 4vw)
                 -> column resolves to 46.7969px, which is what the probe read
     spans       content widths cluster on 296px (5 col) and 359px (6 col)
     type        the whole page collapses to FOUR sizes:
                 58.5 (15vw) · 46.8 (12vw) · 31.2 (8vw) · 16.77 (4.3vw)
                 and 16.77 carries 101 of the measured runs. There is
                 effectively nothing below 4.3vw on the reference.

   We were running a 2-column grid and a ladder that went down to 2.8vw
   (10.9px at 390), which is well under anything the reference ships.
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width:900px){
  :root{
    --col: calc((100vw - (2 * var(--margin)) - (5 * var(--gutter))) / 6);
    /* the bottom of the ladder comes up toward the reference's 4.3vw floor.
       hierarchy is kept, but nothing renders at 11px any more. */
    --fs-label: 4.0vw;   /* was 3.4vw =13.3px -> 15.6px */
    --fs-small: 3.8vw;   /* was 3.2vw =12.5px -> 14.8px */
    --fs-micro: 3.4vw;   /* was 2.8vw =10.9px -> 13.3px */
  }
  .layout-grid{grid-template-columns:repeat(6,1fr)}

  /* the only two rules that assumed a 2-column grid */
  .hero__lead,.hero__cta,.hero__meta{grid-column:1 / -1}
  .ftr__col{grid-column:span 3}

  /* 5-of-6 is the reference's most common inset. Statement copy takes it. */
  .statement,.lede,.intro__body,.quote{grid-column:1 / span 5}
  .bigtype,.sec__eyebrow{grid-column:1 / -1}

  /* line-height: the reference sets 20.12/16.77 = 1.2 on body, 1 elsewhere */
  .lede,.svc__p,.step__p,.post__p{line-height:1.4}
}

/* ── TITLE PAGE. E: his own fence at 3072x3840 with his real mark in ink.
      A book page between chapters, per the spread decision. ─────────────── */
.titlepage{padding:0;position:relative;overflow:hidden;background:#EDEAE4}
.titlepage__bg{display:block;width:100%;height:100%;object-fit:cover}
.titlepage{height:min(88svh, 62vw)}
.titlepage picture{display:block;height:100%}
@media (max-width:900px){ .titlepage{height:118svh} }

/* section 03 is one plate now, not a pair */
.plate{grid-column:1 / -1}
.plate picture{display:block}
.plate img{width:100%;aspect-ratio:3/2;object-fit:cover}
.plate .lede{grid-column:auto;margin-top:1.2em;max-width:40ch}

/* ── HERO LOADER BAR, ported to the measured geometry.
      Reference: active segment w-1/2 = 315px at 1440, three inactive at
      w-1/6 = 105px each. 1/2 + 3 x 1/6 = 1, so the row always fills exactly.
      The width swap runs 500ms power2.out (464ms visible, authored is a source
      literal). CSS owns the widths; GSAP owns only the inner scaleX fill, so
      the two never write the same property. ──────────────────────────────── */
.hero__dot{
  flex:0 0 16.6667%;
  transition:flex-basis .5s cubic-bezier(.25,.46,.45,.94);
}
.hero__dot.is-on{flex-basis:50%}
@media (prefers-reduced-motion: reduce){ .hero__dot{transition:none} }

/* Corrected against the measurement. The reference's segments are 315 + 3x105
   = 630px, plus 3 gutters of 20px = 690px, which is exactly 6 of the 12
   columns. So the bar spans half the grid, and the segments sit at 3:1:1:1.
   flex-grow expresses that ratio exactly and lets the gutters take their own
   space, which a flex-basis percentage cannot do. First real use of --col. */
.hero__dots{width:calc(6 * var(--col) + 5 * var(--gutter));right:auto}
.hero__dot{flex:1 1 0;transition:flex-grow .5s cubic-bezier(.25,.46,.45,.94)}
.hero__dot.is-on{flex:3 1 0;flex-basis:0}
@media (max-width:900px){ .hero__dots{width:auto;right:var(--margin)} }

/* ══════════════════════════════════════════════════════════════════════════
   CIRCLE CURSOR. Black and white, inverting against whatever sits under it.

   The inversion is not measured or scripted: `mix-blend-mode: difference` on a
   pure white disc subtracts the backdrop from white, so it renders black over
   white bands, white over the black bands and the hero photographs, and the
   exact inverse of any mid-tone in between. That is free, per-pixel, and
   always correct, where sampling the backdrop in JS would be a frame behind
   and wrong over a photograph.

   z-index 9998 puts it above every page layer (top is z 60, the header rail)
   but under the arrival curtain at 999999, so the load sequence stays clean.

   GSAP owns the whole transform, including the -50% centring via xPercent /
   yPercent. Mixing a CSS translate with GSAP x/y is the one-property-two-owners
   bug this build has already been bitten by twice.
   ══════════════════════════════════════════════════════════════════════════ */
.cursor{
  position:fixed;top:0;left:0;
  width:22px;height:22px;border-radius:50%;
  background:#fff;
  mix-blend-mode:difference;
  pointer-events:none;
  z-index:9998;
  opacity:0;
  transition:opacity .25s var(--e-out), width .28s var(--e-cubic), height .28s var(--e-cubic);
  will-change:transform;
}
.cursor.is-live{opacity:1}
/* over anything clickable it opens up, so the target stays readable through it */
.cursor.is-over{width:52px;height:52px}

/* the native arrow only goes away once the circle is actually running, so a
   pointer-less or JS-less visitor never loses their cursor */
html.has-cursor,
html.has-cursor a,
html.has-cursor button,
html.has-cursor label,
html.has-cursor input,
html.has-cursor select,
html.has-cursor textarea,
html.has-cursor [role="button"]{cursor:none}

/* coarse pointers get nothing, and reduced motion keeps the disc but drops the
   easing so it tracks the pointer exactly */
@media (pointer:coarse){ .cursor{display:none} }
@media (prefers-reduced-motion:reduce){ .cursor{transition:opacity .25s linear} }

/* ══ SELECTED WORK, rebuilt to the layout Prav picked from the swap artifact.
      Rows of unequal columns that FLIP between rows, the second plate dropped
      so nothing shares a baseline, and no aspect ratio repeated inside a row.
      That last rule is what stops it reading as a card grid. ─────────────── */
.lay{display:grid;gap:1.4%;align-items:start;padding-inline:var(--margin)}
.lay--a{grid-template-columns:1.6fr .9fr}
.lay--b{grid-template-columns:.8fr 1.7fr;margin-top:4%}
.pl--off{margin-top:16%}
@media (max-width:900px){
  .lay--a,.lay--b{grid-template-columns:1fr;gap:calc(var(--gutter) * 1.3)}
  .lay--b{margin-top:calc(var(--gutter) * 1.3)}
  .pl--off{margin-top:0}
}

.pl{display:block;position:relative}
.pl__frame{position:relative;overflow:hidden;background:rgb(var(--secondary)/.06)}
.pl--r32{aspect-ratio:3/2}
.pl--r45{aspect-ratio:4/5}
.pl--r11{aspect-ratio:1}
.pl__inner{position:absolute;inset:0}  /* GSAP owns transform here */
.pl__inner picture{display:block;height:100%}
.pl__inner img{width:100%;height:100%;object-fit:cover}
/* the measured Storey hover, kept: inner to 0.9 over 640ms, white veil over 500ms */
.pl__veil{position:absolute;inset:0;background:rgb(var(--primary));opacity:0;pointer-events:none;
  transition:opacity .5s var(--e-cubic);display:grid;place-content:center;text-align:center;padding:var(--gutter)}
.pl__veil span{color:rgb(var(--secondary));font-size:min(1.15vw,29px);letter-spacing:-.02em;line-height:1.2;font-weight:300}
@media (prefers-reduced-motion: reduce){
  .pl__inner{transform:none}
}
/* captions sit tight under the plate, small, left aligned. Deliberately far
   below the old --fs-h3 card names: the editorial read comes from the gap
   between a large plate and small type. */
.pl__ttl{font-size:min(1.15vw,29px);font-weight:400;letter-spacing:-.02em;line-height:1.15;margin:.55em 0 0}
.pl__cap{font-family:var(--mono);font-size:min(.8vw,20px);letter-spacing:-.02em;
  text-transform:uppercase;color:var(--ink-3);margin:.3em 0 0}
.band--dark .pl__cap{color:var(--on-dark-3)}
@media (max-width:900px){
  .pl__ttl{font-size:4.3vw}.pl__cap{font-size:3.4vw}.pl__veil span{font-size:4.3vw}
}

/* ══ CARD HOVER, rebuilt to the measured spec. Two things it corrects:
      the label is a POINTER-FOLLOWING PILL, not a full-cover veil, and the
      motion is deliberately ASYMMETRIC. Measured:
        image in   650ms authored, power2.out, scale 1 -> 0.9
        image out  400ms authored, power2.out   (1.6x faster out; the photo
                   settles ~100ms before the label has finished fading)
        pill       opacity 0 <-> 1, 500ms both directions, power2.out
        follower   quickTo 200ms power2.out, offset pointerY + 30px,
                   snapped to the pointer on enter with no travel-in
        asterisk   quickTo 350ms power2.out, angle = atan2 of follower
                   velocity, dead zone dx^2 + dy^2 >= 1
        siblings   provably untouched: no dim, no scale, no desaturate
      GSAP owns transform and opacity on these; CSS sets no competing values. */
.pl__pill{
  position:absolute;top:0;left:0;z-index:3;pointer-events:none;opacity:0;
  display:inline-flex;align-items:center;gap:.5em;white-space:nowrap;
  padding:.6em 1.1em;border-radius:100px;
  background:rgb(var(--secondary));color:rgb(var(--primary));
  font-size:min(.85vw,21px);font-weight:400;letter-spacing:-.015em;line-height:1;
  will-change:transform,opacity;
}
.band--light .pl__pill{background:rgb(var(--secondary));color:rgb(var(--primary))}
.pl__ast{font-style:normal;display:inline-block;font-size:1.15em;line-height:0;will-change:transform}
@media (max-width:900px){ .pl__pill{display:none} }
@media (prefers-reduced-motion: reduce){ .pl__pill{display:none} }

/* ── cursor corrections, 26 Aug.
      Size: 22px read too heavy over the plates, and the 52px open state was
      covering the thing it was meant to let you read. Down to 14 / 32.
      Leak: the native arrow was hidden via an explicit element list, so
      anything not on that list kept its own cursor. A universal selector
      closes it. cursor is purely presentational, so !important costs nothing
      and guarantees no arrow survives on an element we did not think of. */
.cursor{width:14px;height:14px}
.cursor.is-over{width:32px;height:32px}
html.has-cursor,
html.has-cursor *,
html.has-cursor *::before,
html.has-cursor *::after{cursor:none !important}

/* ══ 02 / WHAT WE DO, reworked 26 Aug.
      Prav: images, layout, type too large, too many words, needs spacing.
      Copy is down from 21-25 words per item to 8-9. Type drops from --fs-body
      (1.6vw) to the same small caption size the plates use, so the section
      reads as a set of labels under pictures rather than a wall of prose.
      Layout takes the unequal-columns-plus-offset rhythm from the Selected
      Work layout he picked: no two columns the same width, no two plates on
      the same baseline, no ratio repeated across the row. ────────────────── */
.svc--lay{grid-template-columns:1.15fr .8fr 1.05fr;gap:1.6%;align-items:start}
.svc--lay .svc__item{gap:0}
.svc--lay .svc__item img{aspect-ratio:3/2}
.svc--lay .svc__item[data-off="1"]{margin-top:14%}
.svc--lay .svc__item[data-off="1"] img{aspect-ratio:4/5}
.svc--lay .svc__item[data-off="2"]{margin-top:6%}
.svc--lay .svc__item[data-off="2"] img{aspect-ratio:1}
.svc--lay .svc__n{margin-top:1.1em;color:var(--ink-3)}
.svc--lay .svc__h{font-size:min(1.15vw,29px);font-weight:400;letter-spacing:-.02em;margin-top:.35em}
.svc--lay .svc__p{font-size:min(.85vw,21px);line-height:1.5;margin-top:.55em;max-width:24ch;color:var(--ink-2)}
/* the band gets room to breathe rather than the copy filling it */
#services.band{padding-block:calc(var(--margin) * 8)}
@media (max-width:900px){
  .svc--lay{grid-template-columns:1fr;gap:calc(var(--gutter) * 1.5)}
  .svc--lay .svc__item[data-off="1"],.svc--lay .svc__item[data-off="2"]{margin-top:0}
  .svc--lay .svc__h{font-size:4.3vw}
  .svc--lay .svc__p{font-size:3.6vw;max-width:none}
}

/* ── header lockup, tightened 26 Aug. The mark is sized in em, so shrinking the
      text would shrink the logo with it. The em multiplier goes up by the same
      factor (1.9 -> 2.55) so only the words get smaller and the mark holds its
      rendered size: 1.35 x 2.55 = 3.44vw against the old 1.8 x 1.9 = 3.42vw. */
.hdr__mark{font-size:min(1.35vw, 34px);gap:.26em}
.hdr__mark .mk{width:2.55em}
@media (max-width:900px){
  .hdr__mark{font-size:3.6vw}
  .hdr__mark .mk{width:2.4em}
}

/* ── View work: a link, not a button. No fill, no radius, no ring. The arrow
      points down because the link scrolls to the band below rather than
      navigating away. The loop is a slow breath, not a bounce: it drifts 4px
      and eases back over 2.2s with a hold at the top. Hover underlines the
      label using the same scaleX origin-flip as the header nav, so the hover
      grammar stays consistent across the page. ──────────────────────────── */
.hero__cta{
  display:inline-flex;align-items:center;gap:.62em;
  background:none;border:0;border-radius:0;padding:0;
  font-size:var(--fs-label);line-height:1;color:inherit;white-space:nowrap;
}
.hero__cta:hover{transform:none}
.hero__cta-t{position:relative;padding-block:.18em}
.hero__cta-t::after{
  content:"";position:absolute;left:0;bottom:0;width:100%;height:1px;
  background:currentColor;transform:scaleX(0);transform-origin:100% 50%;
  transition:transform var(--t) var(--e-std);
}
.hero__cta:hover .hero__cta-t::after,
.hero__cta:focus-visible .hero__cta-t::after{transform:scaleX(1);transform-origin:0 50%}
.hero__cta-a{
  width:.62em;height:1.05em;flex:none;overflow:visible;
  animation:cta-drift 2.2s var(--e-std) infinite;
}
@keyframes cta-drift{
  0%,42%   { transform:translateY(0) }
  62%      { transform:translateY(4px) }
  100%     { transform:translateY(0) }
}
@media (prefers-reduced-motion: reduce){ .hero__cta-a{animation:none} }

/* ── header lockup: bottoms flush. `align-items:center` was centring the mark
      against the text's LINE BOX, which includes descender space the words
      never use, so the mark rode 1.74px high. `baseline` aligns the mark's
      bottom margin edge (its baseline, since it holds no text) to the text
      baseline, which is where the glyphs actually sit. ───────────────────── */
.hdr__mark{align-items:baseline}

/* ── View work moved right, 26 Aug. Measured: its centre sat at 54.2% of the
      content width; 50% further right is 81.3%. The meta line was removed
      earlier, so columns 9-12 are free and land the centre at 82.4% on clean
      column boundaries rather than an arbitrary translate. Flush right would
      be 96.7%, so this stops well short of the edge. */
.hero__cta{grid-column:9 / -1;justify-self:center}
@media (max-width:900px){ .hero__cta{grid-column:1 / -1;justify-self:start} }

/* ── 04 SIZING FIX.
      .work was still the old 12-column card grid (style.css:266). The two .lay
      rows that replaced the cards are its children, so each was auto-placed
      into a SINGLE column: measured live at 148px and 161px wide against a
      1400px band, rendering the plates at 62x41, 46x58, 60x60 and 61x41.
      The aspect ratios were never wrong (1.5 / 0.8 / 1.0 / 1.5, all correct);
      the container was. .work is now a plain block that stacks the two rows,
      which is all the mock's structure needs. ────────────────────────────── */
.work{display:block;grid-template-columns:none;gap:0;padding-inline:0}

/* ── TITLE PAGE, corrected 26 Aug. It was `cover` inside a band of
      min(88svh, 62vw), which at 1440 is a 1440x792 landscape box. Cover-fitting
      a 4:5 portrait into that threw away 56% of the image height: measured,
      only 44% of the picture survived and the mark was pushed to near
      full-bleed. It is a book PAGE, so it should read as one: shown whole,
      centred, with the cladding colour carried into the margins either side so
      the page sits in the surface rather than being cut out of it. */
.titlepage{height:auto;min-height:0;padding:calc(var(--margin) * 3) 0;background:#DED9CF;
  display:grid;place-items:center}
.titlepage picture{display:block;height:auto}
.titlepage__bg{
  width:auto;height:min(78svh, 92vw);max-width:calc(100vw - var(--margin) * 4);
  object-fit:contain;
}
@media (max-width:900px){
  .titlepage{padding:calc(var(--margin) * 2) 0}
  .titlepage__bg{height:auto;width:calc(100vw - var(--margin) * 2)}
}

/* The CSS failsafe cleared the curtain at 2.2s, which now lands mid-hold and
   would rip it away before the mark arrives. Pushed clear of the whole
   sequence: 2.0s black + 0.75s fade + margin. */
.curtain{animation:curtain-failsafe 0s linear 5s forwards}


/* ══ 01 / STUDIO, the painted mark. Prav's pick from the six looks: 02, half
      bleed. The logo lifted off the cladding render as a real cutout - the
      dry-brush gaps and wood striations all live in its alpha - then filled
      flat #DED9CF. No panel, so there is no rectangle edge to give it away.
      Right peak anchored bottom left; the left peak runs off the margin. ── */
#studio{position:relative;overflow:hidden}
.studio__mk{
  position:absolute;left:-26%;bottom:-11%;width:62%;
  z-index:0;pointer-events:none;user-select:none;
}
.studio__mk img{display:block;width:100%;height:auto}
/* the copy has to clear the paint, so every real child is lifted above it */
#studio > :not(.studio__mk){position:relative;z-index:2}
@media (max-width:900px){
  /* at 390 the copy runs the full column width, so the mark drops lower and
     wider rather than sitting behind the text */
  .studio__mk{left:-30%;bottom:-7%;width:96%}
}

/* ══ WINDOW PARALLAX. Recon spec items 5 and 7, both measured off the
      reference: pre-scale 1.2 with origin-bottom, then travel y by 20% of the
      element's own height. Because both the budget and the travel are keyed to
      that same height, the geometry is edge-safe at ANY viewport - the top
      edge is flush at one end of the scrub and the bottom edge at the other,
      and neither is ever inside the frame.

      GSAP owns the transform on every .wp image, because on the hero it was
      already writing `scale` there for the arrival. A CSS custom property
      would have been a second writer on the same property - the exact fault
      that broke the hero handover earlier in this build. So the pre-scale is
      set in JS too, and CSS declares no transform on .wp at all. ─────────── */
.plate picture{overflow:hidden;display:block}
.wp{will-change:transform}
@media (prefers-reduced-motion: reduce){ .wp{will-change:auto} }

/* ══ THE MARK CROSSES THE SEAM. One painted object cut by the band change:
      cream above, the same brush repainted black below. Both copies sit at the
      identical left offset and width so the strokes line up across the join.

      The crossing is a translateY percentage, NOT a `bottom` percentage, and
      that distinction is the whole rule here: `bottom: -11%` resolves against
      the BAND, so on the live page it produced a 26.5% crossing instead of the
      11% intended. translateY percentages resolve against the element's own
      height, so 42% means 42% of the mark - which is what the eye reads. ─── */
:root{ --mk-cross: 42%; }
.studio__mk{bottom:0;transform:translateY(var(--mk-cross))}

#services{position:relative;overflow:hidden}
.services__mk{
  position:absolute;left:-26%;width:62%;top:0;
  transform:translateY(calc(-100% + var(--mk-cross)));
  z-index:0;pointer-events:none;user-select:none;
}
.services__mk img{display:block;width:100%;height:auto}
#services > :not(.services__mk){position:relative;z-index:2}

/* ══ FOOTER MARK. Prav's call: a second one, right side, same treatment as
      01 / Studio. Not mirrored - the artwork keeps its true orientation and
      the composition inverts instead, so the RIGHT half bleeds off and the
      left peak is the one left anchored. ──────────────────────────────────── */
.ftr{position:relative;overflow:hidden}
.ftr__mk{
  position:absolute;right:-26%;width:62%;bottom:0;
  transform:translateY(18%);
  z-index:0;pointer-events:none;user-select:none;opacity:.9;
}
.ftr__mk img{display:block;width:100%;height:auto}
.ftr > :not(.ftr__mk){position:relative;z-index:2}
@media (max-width:900px){ .ftr__mk{right:-30%;width:96%;transform:translateY(24%)} }

/* ══ MOBILE TYPE LADDER. Measured on the live page at 390: the hero lead, the
      statements, the service headings, the step headings and the body copy ALL
      rendered at 17px, because --fs-h3 and --fs-body are both 4.3vw below 900.
      Desktop holds them 1.37:1 apart (31.68 / 23.04); mobile flattened the
      whole hierarchy to 1:1, so nothing on the page read as a heading.

      This restores the ratio and gives the hero lead its own step above the
      section statements, which is the order the eye needs on one column. ── */
@media (max-width:900px){
  :root{
    --fs-h3:   6.4vw;   /* 25.0px @390  was 4.3vw / 16.8px, identical to body */
    --fs-body: 4.3vw;   /* 16.8px @390  unchanged, this one was already right */
    --fs-h2:  11.0vw;   /* 42.9px @390  was 8vw / 31px                        */
    --fs-note: 4.6vw;
  }
  .hero__lead{font-size:7.4vw;line-height:1.14}   /* 28.9px - one step above a statement */
  .statement{line-height:1.16}
  .lede{line-height:1.5}
}

/* ══ TAP TARGETS. Every link and control measured under Apple's 44px minimum
      at 390 - the worst was 18px tall. Height is added with padding so nothing
      reflows, and the hit area grows without the text moving. ───────────── */
@media (max-width:900px){
  .pill{padding:.95em 1.35em}
  .hero__cta{display:inline-flex;align-items:center;min-height:44px}
  .hdr__mark{padding-block:.5em}
  .ftr__col a,.ftr__col li{display:inline-block;padding-block:.42em}
  .ftr__col ul{margin-block:-.42em}
}

/* ══ ONE SOURCE OF TRUTH for the mark's horizontal placement. Measured at 390:
      the studio half sat at left -30% / width 96% while the services half was
      still on the desktop -26% / 62%, so the two halves of a single painted
      object were 15.6px apart and 132.6px different in width. They only line
      up if they can never be set separately, so both now read the same two
      tokens and the breakpoint moves the tokens, not the rules. ─────────── */
:root{ --mk-left:-26%; --mk-w:62%; }
@media (max-width:900px){ :root{ --mk-left:-30%; --mk-w:96%; } }
.studio__mk,.services__mk{left:var(--mk-left);width:var(--mk-w)}

/* the remaining sub-44px targets, measured: header mark 32, footer links 41 */
@media (max-width:900px){
  .hdr__mark{padding-block:.8em}
  .ftr__col a,.ftr__col li{padding-block:.6em}
  .ftr__col ul{margin-block:-.6em}
}

/* ══ DRAWN-LINE INERTIA on the 03 / Process step rules. From the inertia-lines
      component: a border cannot be animated along its length, so the rule is a
      scaled pseudo-element instead. easeInOutQuint is the signature - it creeps
      off the mark, races the middle and decelerates into the stop, which is
      what reads as weight rather than a wipe.

      The footer's rule is deliberately NOT included: Prav's call, and it is a
      structural divider rather than part of this sequence. ────────────────── */
.steps .step{position:relative}
.steps .step::before{
  content:"";position:absolute;left:0;right:0;top:0;height:1px;
  background:var(--rule-dark);
  transform:scaleX(0);transform-origin:0 50%;
  transition:transform .95s cubic-bezier(.83,0,.17,1);
}
.band--light .steps .step::before{background:var(--rule)}
/* tight steps, so four lines read as one gesture and not four events */
.steps .step:nth-child(1)::before{transition-delay:0s}
.steps .step:nth-child(2)::before{transition-delay:.17s}
.steps .step:nth-child(3)::before{transition-delay:.34s}
.steps .step:nth-child(4)::before{transition-delay:.51s}
.steps.is-in .step::before{transform:scaleX(1)}
@media (prefers-reduced-motion: reduce){
  .steps .step::before{transform:scaleX(1);transition:none;transition-delay:0s}
}
/* no JS: the lines are simply present, never animated */
html:not(.is-js) .steps .step::before{transform:scaleX(1);transition:none}

/* the Altona plate becomes a parallax window too. .pl__inner already carries the
   hover scale, so the drift is on the img inside it - separate element, separate
   owner, same rule that keeps the hero from fighting itself. */
.pl__inner picture{display:block;overflow:hidden}

/* ══ 02 / WHAT WE DO ON THE PHONE. Prav's pick: variant B from the mobile
      artifact. The structure is Storey's expertise section measured at 390 -
      a number alone in the left margin with title, body and image all sharing
      one indent, so the eye tracks a single vertical line down the column.

      Measured off their live page at 390 and used verbatim:
        page margin      16px      (ours: var(--margin))
        content indent   63px
        section heading  46.8px / 1.0, weight 500, letter-spacing -3%
        item title       31.2px, on the number's baseline
        body             16.77px / 1.2, letter-spacing -3%
        image            2:1
        rule             1px, full bleed, ignores the margin

      The one departure Prav chose (B): the photograph breaks the right margin
      and runs off the screen, matching the edge-breaking grammar the painted
      mark already uses in 01 / Studio. ─────────────────────────────────────── */
.svc__lead,.svc__intro{display:none}

@media (max-width:900px){
  .svc__lead{
    display:block;grid-column:1 / -1;
    font-size:46.8px;line-height:1;font-weight:500;letter-spacing:-.03em;
    margin:.22em 0 .46em;
  }
  .svc__intro{
    display:block;grid-column:1 / -1;
    font-size:16.77px;line-height:1.2;letter-spacing:-.03em;
    color:var(--ink-2);max-width:30ch;margin:0 0 2.6em;
  }

  .svc,.svc--lay{grid-template-columns:1fr;gap:0}

  /* pulled out to the viewport edges so the rule is genuinely full bleed, then
     the content is padded back in. Doing it this way leaves the section's own
     padding-inline intact for the eyebrow and heading above. */
  .svc--lay .svc__item{
    display:grid;
    grid-template-columns:63px 1fr;
    grid-template-areas:"num ttl" ".  body" ".  img";
    align-items:baseline;
    gap:0;
    margin-inline:calc(-1 * var(--margin));
    padding:1.55em var(--margin) 2.4em;
    border-top:1px solid var(--rule);
  }
  /* the desktop stagger has no meaning in one column */
  .svc--lay .svc__item[data-off="1"],
  .svc--lay .svc__item[data-off="2"]{margin-top:0}

  .svc--lay .svc__n{
    grid-area:num;margin-top:0;
    font-family:var(--mono);font-size:24px;line-height:1;
    letter-spacing:-.06em;color:var(--ink-3);
  }
  .svc--lay .svc__h{
    grid-area:ttl;margin-top:0;
    font-size:31.2px;line-height:1.02;font-weight:400;letter-spacing:-.03em;
  }
  .svc--lay .svc__p{
    grid-area:body;margin:1.55em 0 0;
    font-size:16.77px;line-height:1.2;letter-spacing:-.03em;max-width:26ch;
  }
  .svc--lay .svc__item picture{
    grid-area:img;display:block;margin:1.55em calc(-1 * var(--margin)) 0 0;
    aspect-ratio:2 / 1;overflow:hidden;
  }
  /* beats the three per-item aspect overrides the desktop layout sets */
  .svc--lay .svc__item img,
  .svc--lay .svc__item[data-off="1"] img,
  .svc--lay .svc__item[data-off="2"] img{
    width:100%;height:100%;aspect-ratio:auto;object-fit:cover;display:block;
  }
}

/* ══ MOBILE SCROLL STALL. Measured on a phone profile at DPR3: the four hero
      slides were each pulling a 3840px AVIF and every parallax image carried
      will-change:transform, so SIX full-screen 4K images were promoted to
      permanent compositor layers - 30MB of decoded bitmaps for the heroes
      alone, 33MB across the page. A phone GPU cannot hold that, so layers get
      evicted and re-rasterised mid-scroll, which is the scroll-stop-scroll.

      The window-parallax component gates will-change behind min-width 721px
      for exactly this reason. That gate was missing here; this restores it.
      GSAP still promotes each element for the duration of its own tween, so
      the desktop scrub loses nothing. ────────────────────────────────────── */
@media (max-width:720px){
  .wp,
  .hero__slide img{ will-change:auto }
}

/* ══ FOOTER ON MOBILE. The four blocks stack into one column with a gap of
      var(--gutter) * 1.2, and --gutter is 4vw below 900 - so 18.7px at 390.
      The tap-target padding added earlier then opened the gaps INSIDE each
      list to roughly the same distance, so nothing grouped and the whole
      footer read as one continuous run of text.

      Structure comes from the RATIO, not absolute space: the gap between
      blocks has to be clearly larger than the gap between items within a
      block. Blocks now sit 2.6em apart against ~0.6em between items.

      Link size also goes up from 3.2vw (12.5px) to 4vw (15.6px), which both
      reads better at arm's length and is what carries each row to the 44px
      tap minimum without needing padding so large it breaks the grouping. */
@media (max-width:900px){
  .ftr__row{gap:2.6em}
  .ftr__col h4{margin-bottom:1.05em}
  .ftr__col li{font-size:4vw;line-height:1.6}
  .ftr__col a,.ftr__col li{display:inline-block;padding-block:.6em}
  .ftr__col ul{margin-block:-.6em}
  .ftr__base{margin-top:3.2em;gap:.7em;line-height:1.6}
  .ftr{padding-top:calc(var(--margin) * 2.4)}
}

/* ══ FOOTER LIST BOXES. The tap-target rule above set display:inline-block and
      padding-block on BOTH the li and the a. Inline-level boxes are laid out on
      line boxes, so vertical padding does not push siblings apart - it overlaps
      them. Measured: adjacent list items overlapping by 43.7px, which means two
      links' hit areas sitting on top of each other and a tap near a boundary
      landing on the wrong one.

      The list item stays block-level and owns the rhythm. Only the anchor -
      the thing that actually needs a 44px target - carries the padding, and it
      grows its own li rather than colliding with the next one. Plain text items
      that are not links do not need a target and no longer pretend to. ────── */
@media (max-width:900px){
  .ftr__col ul{margin-block:0}
  .ftr__col li{display:list-item;padding-block:0;font-size:4vw;line-height:1.6;margin-block:.28em}
  .ftr__col li:first-child{margin-top:0}
  .ftr__col a{display:inline-block;padding-block:.62em;margin-block:-.28em}
}

/* A 44px target needs a 44px pitch or the boxes still collide: the list rhythm
   was 33.7px while each anchor was 44px tall, so adjacent links overlapped by
   ~10px. The row spacing now carries the target height, and the gap between
   blocks grows to match so the grouping ratio survives it. */
@media (max-width:900px){
  .ftr__row{gap:3.2em}
  .ftr__col li{margin-block:.62em}
  .ftr__col a{margin-block:0}
}
