/* ═══════════════════════════════════════════════════════════
   TENTHIRTY v2 — 01 · NAVIGATION BAR
   Desktop built from "navigation bar.psd" (2870 × 184 @2x → 1435 × 92).
   Mobile has no board; see MEASUREMENTS.md for what was decided and why.
   ═══════════════════════════════════════════════════════════ */

@font-face{
  font-family:'BDO Grotesk';
  src:url('../fonts/BDOGrotesk-Regular.otf') format('opentype');
  font-weight:400; font-style:normal; font-display:swap;
}
@font-face{
  font-family:'BDO Grotesk';
  src:url('../fonts/BDOGrotesk-Bold.otf') format('opentype');
  font-weight:700; font-style:normal; font-display:swap;
}
/* MISSING WEIGHT. "what we offer.psd" sets every paragraph on the page in BDO
   Grotesk *Medium*; only Regular and Bold were supplied. Body copy is therefore
   asked for at 500 and the browser falls back to Regular — a little lighter than
   the board, and narrow enough that a wrapped column reaches one line less than
   the PSD does. Drop BDOGrotesk-Medium.otf into assets/fonts, uncomment the
   block below, and every 500 on the site becomes the real thing with no other
   edit. (Left commented rather than live so a missing file is not a 404 on
   every page load.)

@font-face{
  font-family:'BDO Grotesk';
  src:url('../fonts/BDOGrotesk-Medium.otf') format('opentype');
  font-weight:500; font-style:normal; font-display:swap;
}
*/

/* STAND-IN. The studio's serif is Fenway Banner Variable Compressed Light —
   projects.psd names it outright. It is not on this machine and there is no
   web licence in the project, so project names (which are data, and cannot be
   traced) fall back to Meno. At a matched cap height Meno renders ~80% wider
   and much heavier, so this does not look right; it is a placeholder, not a
   choice. Drop FenwayBanner into assets/fonts, point this rule at it, and the
   traced wordmark, hero line and heading can all become live text too. */
@font-face{
  font-family:'TT Serif';
  src:url('../fonts/MenoBannerCondensed-Bold.otf') format('opentype');
  font-weight:400 900; font-style:normal; font-display:swap;
}

/* ── Tokens ─────────────────────────────────────────────── */
:root{
  --black:#000;
  --white:#fff;
  --accent:#d0df83;

  /* Header box — PSD: bar is 92px tall, logo ink starts at x=50,
     "GET IN TOUCH" ink ends 72px short of the right edge.
     The asymmetry is in the source file; it also happens to centre
     the ABOUT/PROJECTS pair to within 6px of where the PSD puts it. */
  --bar-h:92px;
  /* Was 50 / 72 — the nav board's own asymmetric margins. Both are now the
     grid's, so the bar starts on column 1 and ends on column 7 like
     everything else. The 22px the logo gains and the 28 the CTA loses is the
     cost of that, and it squares the bar with the page under it. */
  --pad-l:var(--grid-margin);
  --pad-r:var(--grid-margin);

  --logo-w:131px;            /* PSD ink 262 × 89 @2x → 131 × 44.5 */

  /* "About / Projects" — BDO Grotesk Regular, caps, 17pt, −50 tracking */
  --nav-size:17px;
  /* "Get in touch"    — BDO Grotesk Regular, caps, 20pt, −50 tracking */
  --cta-size:20px;
  --track:-0.05em;           /* −50/1000 em */
  --nav-gap:34px;            /* PSD ink gap 36.5px less both sidebearings */

  --flip-h:1.24em;           /* flip box height — also the depth it turns through */
  --flip-dur:.52s;
  --flip-ease:cubic-bezier(.2,.72,.24,1);

  --burger-w:36px;           /* icon box 26.4 wide → ink bars land at ~22px */

  /* ── The grid ─────────────────────────────────────────────
     Seven columns, 40px gutters, 100px margins, on the 1460
     board. The studio's system, set once here; every section
     places against it rather than against its own PSD offsets.

     The boards were not drawn on it — anchors sat 4–75px off
     the nearest column — so adopting it moved things. What
     each section gave up is recorded in its own note below.

     Margin and gutter are stated as vw so they hold their
     proportion down the range and reach exactly 100 and 40 at
     1460, where the shell caps. --col is derived, never typed:
     any element's left is  margin + n·(col + gutter)  and its
     width is  span·col + (span−1)·gutter. */
  --grid-cols:7;
  --grid-margin:clamp(20px, 6.8493vw, 100px);   /* 100 at 1460 */
  --grid-gutter:clamp(16px, 2.7397vw,  40px);   /*  40 at 1460 */
  --grid-max:1460px;
  /* Viewport edge → column 1, for full-bleed elements (the fixed bar, a
     black band) that cannot live inside a capped shell but still have to
     start where the content does. */
  --edge:max(var(--grid-margin),
             calc((100vw - var(--grid-max)) / 2 + var(--grid-margin)));
  /* One column, as an absolute length off the capped board — so a composition
     can place against the grid without having to be the shell's width. */
  --gcol:calc((min(100vw, var(--grid-max)) - 2 * var(--grid-margin)
               - 6 * var(--grid-gutter)) / 7);
}

/* The content column: everything on the site sits inside one of these. */
.shell{
  width:min(100%, var(--grid-max));
  margin-inline:auto;
  padding-inline:var(--grid-margin);
}

/* A seven-track grid for anything laid out in flow. */
.grid{
  display:grid;
  grid-template-columns:repeat(var(--grid-cols), minmax(0,1fr));
  column-gap:var(--grid-gutter);
}

/* Placement for compositions that are positioned rather than flowed. The
   element sets --c (how many columns in, 0-based) and --s (how many it
   spans); the arithmetic lives here once. Art that must keep its drawn size
   sets --c and overrides width instead of setting --s. */
.gridbox{
  position:relative;
  --col:calc((100% - 2 * var(--grid-margin) - 6 * var(--grid-gutter)) / 7);
}
.gridbox > *{
  position:absolute;
  left:calc(var(--grid-margin) + var(--c, 0) * (var(--col) + var(--grid-gutter)));
  width:calc(var(--s, 1) * var(--col) + (var(--s, 1) - 1) * var(--grid-gutter));
}

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

body{
  margin:0;
  min-height:100svh;
  background:var(--black);
  color:var(--white);
  font-family:'BDO Grotesk',-apple-system,BlinkMacSystemFont,'Helvetica Neue',Arial,sans-serif;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
}
html.menu-open, html.menu-open body{overflow:hidden;}

/* ── Header ─────────────────────────────────────────────── */
.header{
  /* fixed, not absolute: the mobile panel is fixed to the viewport, so the
     button that closes it has to stay on screen too. It has no background,
     so it retreats on the way down and comes back on the way up rather than
     letting the hero's headline slide under it. */
  position:fixed;
  top:0; left:0; right:0;
  z-index:100;
  transition:transform .38s var(--flip-ease);
  height:var(--bar-h);
  background:transparent;
  /* --edge, not --pad-*: the bar spans the viewport while the page under it
     caps at 1460, and the logo has to line up with column 1 either way */
  padding-inline:var(--edge);
  display:grid;
  grid-template-columns:1fr auto 1fr;
  align-items:center;
  column-gap:24px;
  /* the ink every mark in the bar takes; see "The bar's ground" below */
  --bar-ink:var(--white);
}

/* the panel takes focus on open so a screen reader lands inside it, but a
   container focus ring would read as "this link is selected" — suppress it */
.menu:focus{outline:none;}

/* ── The bar's ground, and its ink ───────────────────────── */
/* One token, two states, and they settle in cascade order rather than in a
   specificity fight — the last rule that applies wins.

   At the top of a page the bar has no ground of its own, exactly as the nav
   board draws it, so its ink has to match whatever is under it. Every section
   declares the ink it needs with `data-ink` and the script publishes the one
   crossing the bar's midline. Once the bar leaves the top it takes a black
   ground, and then the question is settled: white, whatever is underneath.

   Why both: the homepage's ground changes per section, not per page — black
   hero and About, white and lime through the Approach deck, lime at Let's
   Talk. Measured before this went in, 18 of 25 sampled scroll positions put
   the white nav below 4.5:1, and the whole deck put it at 1.00:1. The ground
   is what guarantees the bar is always readable; the adaptive ink is what
   keeps the top of every page looking like the board.

   Neither the ground nor the ink is transitioned. A crossfade between them
   puts grey ink on a grey ground for its whole duration — worse at the
   threshold than the switch it is smoothing. Nothing is lost: the bar
   retreats on the way down and returns on the way up, so it is almost always
   off screen while the threshold is crossed. */
.header[data-ink="dark"]{--bar-ink:var(--black);}
.header[data-scrolled="true"]{--bar-ink:var(--white);}

/* The ground is a FADE, not a slab. A flat black bar is a hard horizontal
   edge across whatever is behind it, and on the Approach deck that edge was
   landing straight through the top of the 10:30 and 12:00 numerals — they
   read as cut off rather than as covered. The gradient carries the same ink
   contrast where the ink actually is (the marks sit at y 30–62 of a 92px bar,
   where the ramp is still .93–.78) and then lets go, so nothing behind it ever
   gets a line drawn across it.

   It runs 1.1 × the bar's own height. Long enough that the tail is a fade and
   not a soft edge; short enough that it is fully spent by y≈101, which is
   where the deck's clearance below puts the highest numeral — a longer ramp
   (1.55 was the first cut) left a visible 36% grey wash across the top of
   12:00 instead of a line, which is not an improvement. It is a
   pseudo-element rather than a background on .header because .header is a
   grid: a background would have to stop at the bar's foot, and a real child
   would become a grid item.

   Measured with the ink centred at y≈46: white on the lime Let's Talk ground
   comes out 13.4:1, white on white 11.7:1, and the lime wordmark on lime
   9.3:1 — all well clear of their floors, and every one of them was the case
   the solid slab existed for. */
.header::before{
  content:"";
  position:absolute;
  inset:0 0 auto;
  height:calc(var(--bar-h) * 1.1);
  z-index:-1;
  opacity:0;
  pointer-events:none;
  background:linear-gradient(to bottom,
      rgb(0 0 0 / 1)   0%,
      rgb(0 0 0 / .93) 38%,
      rgb(0 0 0 / .62) 60%,
      rgb(0 0 0 / .26) 80%,
      rgb(0 0 0 / 0)   100%);
}
.header[data-scrolled="true"]::before{opacity:1;}

/* The marks take the token with no transition on it, for the reason above.
   Hover keeps its fade by scoping the transition to the hovered state: it
   eases in and snaps back, which is the half you feel, and a change of ground
   never animates. (The nav links and the CTA hover by flipping, not by
   recolouring — this is for the logo, the burger, and the reduced-motion
   path, where colour is the only affordance left.) */
.logo:hover, .logo:focus-visible,
.burger:hover, .burger:focus-visible{transition:color .3s var(--flip-ease);}

.header[data-hidden="true"]{transform:translateY(-100%);}
/* A transform makes the header the containing block for its own fixed
   children, which would shrink the panel to the height of the bar. The bar is
   always down when the panel can be opened — you cannot tap a burger that is
   off screen — but this makes that guarantee explicit rather than incidental. */
.menu-open .header{transform:none;}
/* The open panel is the ground, so the bar must not paint one of its own over
   it — least of all black, on a field that has just gone lime. Same specificity
   as the scrolled rule above, so this has to sit after it. */
.menu-open .header{background:transparent;}
.menu-open .header::before{opacity:0;}

/* On desktop the panel is not a box at all — display:contents dissolves it
   so .nav and .cta are direct grid items of .header, exactly as the PSD has
   them. Below the breakpoint it becomes a real fixed-position overlay.
   One set of links, two layouts, no duplicated markup to keep in sync. */
.menu{display:contents;}

/* ── Wordmark ───────────────────────────────────────────── */
/* Traced from the PSD's own pixels, so the swash y and the ™ are the
   studio's drawing rather than a font substitution. currentColor lets it
   take the accent on hover. */
.logo{
  justify-self:start;
  display:block;
  width:var(--logo-w);
  color:var(--bar-ink);
}
.logo svg{display:block; width:100%; height:auto;}

/* ── Link type ──────────────────────────────────────────── */
.nav{
  justify-self:center;
  display:flex;
  align-items:center;
  gap:var(--nav-gap);
}

.link{
  display:inline-block;
  color:var(--white);
  text-decoration:none;
  font-weight:400;
  white-space:nowrap;
  /* 11 × the flip-box height: enough foreshortening to read as depth,
     not enough to warp a long label. In em so the depth ratio holds at
     17px in the bar and at 40px in the mobile panel. */
  perspective:13.6em;
}
/* the bar sets its links in caps on −50; the footer does not, so this rides
   on top of .link rather than inside it */
.link--caps{
  text-transform:uppercase;
  letter-spacing:var(--track);
}
.nav .link{font-size:var(--nav-size); color:var(--bar-ink);}
.cta{justify-self:end; font-size:var(--cta-size); color:var(--bar-ink);}

/* ── The flip ───────────────────────────────────────────── */
/* Two faces of one shallow box: the label you see, and an identical
   copy stood up 90° above it in the accent colour. Hovering rotates the
   box −90° about its own horizontal centre line, so the white face falls
   away underneath and the lime face drops into its place. Both faces are
   the same string at the same weight, so the box never changes width and
   nothing beside it moves. */
.flip{
  display:block;
  position:relative;
  transform-style:preserve-3d;
  /* the assembly is pushed back by half the box depth so the front face
     sits at z=0 and takes no perspective scaling while at rest */
  transform:translateZ(calc(var(--flip-h) / -2));
  transition:transform var(--flip-dur) var(--flip-ease);
}
.flip__face{
  display:block;
  line-height:var(--flip-h);   /* = the box height, and so the flip depth */
  backface-visibility:hidden;
  -webkit-backface-visibility:hidden;
}
.flip__face--front{
  transform:translateZ(calc(var(--flip-h) / 2));
}
.flip__face--back{
  position:absolute;
  inset:0;
  color:var(--accent);
  transform:rotateX(90deg) translateZ(calc(var(--flip-h) / 2));
}

/* Only real pointers get :hover — on touch it sticks after a tap. */
@media (hover:hover){
  .link:hover .flip{transform:translateZ(calc(var(--flip-h) / -2)) rotateX(-90deg);}
  .logo:hover{color:var(--accent);}
  .burger:hover{color:var(--accent);}
}
.link:focus-visible .flip{transform:translateZ(calc(var(--flip-h) / -2)) rotateX(-90deg);}
.logo:focus-visible{color:var(--accent);}
/* Touch has no hover to give feedback with, so the accent lands on press. */
.link:active .flip__face--front{color:var(--accent);}

/* ── The button ─────────────────────────────────────────── */
/* source/button.psd — second cut (2026-08-29), 447 × 398 @2x, two artboards
   drawing the same pill in its two states. The first cut is parked as
   button-superseded-2026-08-29a.psd; this one is TIGHTER and it has no edge:

     pill    348 × 108 @2x  ->  260 × 99   i.e. 174 × 54 CSS -> 130 × 49.5
     padding 46.8 CSS a side ->  25.09
     stroke  1 hard px       ->  none — both fills run straight to the paper
     corner  48.4 @2x        ->  47.6, so 23.8 CSS either way (a full pill
                                 would need 49.5; it is not one)

   Rest: black fill, lime label. Hover: exactly inverted. Label BDO Grotesk
   Regular at 20px on −0.01em — the size the board renders (26.11 × the text
   matrix's 1.531980 = 40.00 px @2x = 20 CSS, exactly) and the tracking the
   studio specified; the layer itself records 0. See MEASUREMENTS §09.

   Only the width moves. Height, radius and padding are fixed, so a longer
   label stretches the pill horizontally and nothing else changes:
   View All 126.2, About Us 139.4, Let's Create 162.0.

   Everything but the edge is in `em` of the label, so the board's proportions
   hold at whatever size a section asks for. */
.btn{
  --btn-ground:var(--black);
  --btn-ink:var(--accent);
  --btn-edge:transparent;       /* the board draws none — see below */
  display:inline-grid;
  place-items:center;
  box-sizing:border-box;
  /* The component's own box is the default; a destination board that draws a
     different pill overrides these three. about-us.psd (2026-09-01) draws a
     bigger one — 174.5 × 54.5 on a 25.5 radius, fitted off the shape layer's
     alpha (least squares over the top corner: dy 40.5 sits at dx 20.0, where a
     true pill on a 109 @2x box would put it at 22.9) — and it draws the SAME
     box on both artboards regardless of label, so there the width is fixed
     rather than following the text. */
  height:var(--btn-h, 2.475em); /* 49.5 / 20 */
  min-height:44px;              /* keeps a tap target when a section sets a
                                   smaller size; the board's 49.5 already is one */
  min-width:var(--btn-w, auto);
  padding-inline:1.2545em;      /* 25.09 / 20 */
  border-radius:var(--btn-r, 1.19em);   /* 23.8 / 20 */
  border:0.5px solid var(--btn-edge);
  background:var(--btn-ground);
  color:var(--btn-ink);
  font-size:var(--btn-size, 20px);
  letter-spacing:-0.01em;       /* the specified −10; the layer says 0 */
  font-weight:400;
  line-height:1;
  white-space:nowrap;
  text-decoration:none;
  /* the custom properties are not animatable, but the three real properties
     that read them are, so the swap crossfades on the flip's own curve */
  transition:background-color var(--flip-dur) var(--flip-ease),
             border-color     var(--flip-dur) var(--flip-ease),
             color            var(--flip-dur) var(--flip-ease);
}
@media (hover:hover){
  .btn:hover{--btn-ground:var(--accent); --btn-ink:var(--black);}
}
.btn:focus-visible,
.btn:active{--btn-ground:var(--accent); --btn-ink:var(--black);}

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

/* The board is drawn on white, where a solid pill needs no edge. Two of the
   three grounds it lands on here ARE the pill's own two colours, and on those
   one of the two states has a fill that is the paper — with no edge the pill
   would not just flatten, it would disappear, leaving a floating label.

   So each of those two gets a hairline back, in ONE fixed colour rather than
   in the ink: the colour of the state that still reads. It is therefore
   invisible in that state (it matches the fill) and only appears in the one
   that needs it. `.about__cta` gets none — its ground is the photograph, and
   the board is exact there. Set these to `transparent` for the board
   everywhere and accept a disappearing pill on black and on lime. */
.projects__all{--btn-edge:var(--accent);}   /* black on black at rest */
.talk__cta{--btn-edge:var(--black);}        /* lime on lime on hover */
/* about-us.psd draws this one WITH a hairline — 1 raw px of #d0df83 at x 1261
   on a 2x board, sampled straight off the composite — so here the edge is the
   board's, not a repair. On hover the fill goes lime and the ring vanishes
   into it, which is fine: that state's ground is a photograph. */
.about__cta{--btn-edge:var(--accent);}

/* View All was briefly inverted to start lime-filled, read off pro.psd's
   homepage board. button.psd is the COMPONENT board and it draws the rest state
   black-with-a-lime-label like every other button here, so the component wins
   and the inversion is gone: View All is the same object as Let's Create and
   About Us again, and the lime hairline above is what draws it on black. */

:where(a,button):focus-visible{
  outline:2px solid var(--accent);
  outline-offset:8px;
  border-radius:2px;
}
/* the 44px tap target already stands well clear of the 22px ink, and an
   offset ring on it would run off the right edge of the viewport */
.burger:focus-visible{outline-offset:0; border-radius:4px;}

/* ── Burger ─────────────────────────────────────────────── */
/* The supplied icon, split into its three bars so they can converge.
   The bars sit at y 11.13 / 16.63 / 22.13 in the icon's own units, so
   ±5.5 brings the outer two onto the middle one before they rotate. */
.burger{
  display:none;                /* desktop has the full row; no burger */
  grid-column:3;
  justify-self:end;
  width:44px; height:44px;     /* tap target, larger than the ink */
  margin-right:-11px;          /* keeps the ink on --pad-r, not the box */
  padding:0;
  border:0;
  background:none;
  color:var(--bar-ink);
  cursor:pointer;
  place-items:center;
  -webkit-tap-highlight-color:transparent;
}
.burger__icon{
  display:block;
  width:var(--burger-w);
  height:auto;
}
.burger__bar{
  transform-box:fill-box;
  transform-origin:center;
  transition:transform .42s var(--flip-ease), opacity .2s linear;
}
.menu-open .burger__bar--top{transform:translateY(5.5px) rotate(45deg);}
.menu-open .burger__bar--bot{transform:translateY(-5.5px) rotate(-45deg);}
.menu-open .burger__bar--mid{opacity:0; transform:scaleX(.2);}


/* ═══════════════════════════════════════════════════════════
   02 · HERO — from "hero.psd" (2870 × 1638 @2x → 1435 × 819)
   The board is the full viewport with no bar drawn on it, so the
   header overlays the hero rather than sitting above it.
   ═══════════════════════════════════════════════════════════ */
:root{
  /* "Time changes everything!" — BDO Grotesk REGULAR, 20px, tracking −10.
     The new board reset all three: it was Bold 25 at tracking 0. The layer
     records FontSize 54.54178 against a text matrix of 0.733383, i.e. 40px
     @2x = 20 CSS px exactly, and /Tracking −10 = −0.01em.
     See MEASUREMENTS.md §02. */
  --eyebrow-track:-0.01em;
  /* The layer also carries a 105% horizontal transform. Reproducing it is
     one edit — set this to 1.05 — but it stretches the letterforms, so the
     build ships at 1. */
  --eyebrow-stretch:1;

  /* PSD ink 1223 × 172 @2x → 611.5 × 86. One continuous curve rather than a
     desktop law and a mobile law: the board's own 42.61vw ratio collapses to
     327px on a tablet, while a flat 86vw would be 1.2× the board at 1435. The
     middle term is the line through both anchors (611.5 at 1435, 86vw at 390);
     the outer two cap it at the board size and hold a margin on small screens.
     The eyebrow is a fixed fraction of it, so the PSD's proportion between the
     two survives at every width. */
  --headline-w:min(611.5px, calc(231.8px + 26.46vw), 86vw);
  --eyebrow-size:max(12px, calc(var(--headline-w) * 0.032706));

  --hero-top:48.47%;         /* the eyebrow's BASELINE: y=397 of 819 */

  /* The plate is the board's own photograph, drawn 3126px wide on a 2870px
     board — 1.08920× the frame, centred across it and hard against its top
     edge. Everything else about the framing follows from those two facts;
     object-fit does the covering and this scales past it. */
  --plate-zoom:1.08920;
  /* the board draws .4588; see .hero__scrim */
  --scrim:.53;

  --chev-w:21px;             /* PSD ink 42 × 22 @2x, 1.0px stroke */
  --chev-bottom:clamp(56px, 5.06vw, 72.6px);  /* 72.6 puts the ink's foot on y=732.5 of 819 */
}

.hero{
  position:relative;
  min-height:100svh;
  width:100%;
  overflow:hidden;
  background:var(--black);
}

/* ── Hero plate ─────────────────────────────────────────── */
.hero__media{
  position:absolute;
  inset:0;
  z-index:0;
  display:block;
  overflow:hidden;
}
/* `cover` fills the frame at any aspect; the scale then takes it out to the
   board's 1.08920, anchored at the top edge and centred, which is exactly
   where the board draws the photograph.

   And it drifts. --par is the parallax module's, 0% at the top of the page
   and −6% by the time the hero has left — so the picture is EXACTLY the
   board on the frame everyone sees first, and the travel is spent on frames
   the board never described anyway.

   Up, not down, and that is forced rather than chosen: anchoring at the top
   edge puts all 8.92% of the spare picture below the frame. There is nothing
   above it to travel into, and the only way to make some would be to zoom
   past the board's 1.0892, which would stop the rest state being the board.
   6 of the 8.92 leaves the rest as margin for a fractional viewport. */
.hero__img{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  object-fit:cover;
  object-position:50% 0;
  transform:translate3d(0, var(--par, 0%), 0) scale(var(--plate-zoom));
  transform-origin:50% 0;
  will-change:transform;
}

/* The board's scrim is one flat black slab, not a gradient: hero.psd's
   "Layer 2" is pure black at 117/255 = .4588 across the whole board. At the
   board's own 1435x819 that holds every line over its floor with almost
   nothing spare (lime wordmark 3.01:1 against 3), and it holds only there —
   at 1920x1080 the lime falls to 2.98, at 1440x1000 to 2.94 with the eyebrow
   at 4.30 against 4.5. `cover` puts a different part of the picture behind
   the type at every other aspect, and the walker is the bright part.
   .53 is the smallest flat value that clears every size measured; the board's
   own number is one edit away. MEASUREMENTS.md §02. */
.hero__scrim{
  position:absolute;
  inset:0;
  z-index:1;
  pointer-events:none;
  background:rgba(0,0,0,var(--scrim));
}

.hero__type{
  z-index:2;
  position:absolute;
  top:var(--hero-top);
  left:0; right:0;
  text-align:center;
  padding-inline:20px;
}

.hero__eyebrow{
  margin:0;
  font-weight:400;
  font-size:var(--eyebrow-size);
  letter-spacing:var(--eyebrow-track);
  line-height:1;
  color:var(--white);
  /* -0.82em lifts the line box so --hero-top lands on the baseline (BDO
     Grotesk puts it 0.8115em down a 1em line box, Regular and Bold alike);
     the rest is the board's 10.1px from that baseline to the headline, which
     at 20px is 0.505em less the 0.18em the line box already spends */
  margin:-0.82em 0 0.325em;
}
.hero__eyebrow span{
  display:inline-block;
  transform:scaleX(var(--eyebrow-stretch));
}

.hero__headline{
  margin:0;
  line-height:0;              /* the art is the line; no leading around it */
}
/* Traced from the PSD — "Yours starts at" is the same bespoke serif as the
   wordmark and there is no font file for it, so the headline is artwork with
   the real string carried underneath for readers and crawlers. */
.hero__headline svg{
  display:block;
  width:min(var(--headline-w), 100%);
  height:auto;
  margin-inline:auto;
}

/* ── Scroll cue ─────────────────────────────────────────── */
.hero__scroll{
  position:absolute;
  z-index:2;
  left:50%;
  bottom:var(--chev-bottom);
  transform:translateX(-50%);
  display:grid;
  place-items:center;
  width:44px; height:44px;     /* tap target around a 21 × 11 mark */
  color:var(--white);
  transition:color .3s var(--flip-ease);
}
.hero__scroll svg{display:block; width:var(--chev-w); height:auto;}
@media (hover:hover){
  .hero__scroll:hover{color:var(--accent);}
}
.hero__scroll:focus-visible{color:var(--accent);}

/* a slow fall-and-return, the length of the chevron's own height */
@media (prefers-reduced-motion:no-preference){
  .hero__scroll svg{animation:nudge 2.4s var(--flip-ease) infinite;}
}
@keyframes nudge{
  0%,62%,100%{transform:translateY(0);}
  31%{transform:translateY(5px);}
}

.sr-only{
  position:absolute;
  width:1px; height:1px;
  margin:-1px; padding:0;
  overflow:hidden;
  clip-path:inset(50%);
  white-space:nowrap;
  border:0;
}


/* ═══════════════════════════════════════════════════════════
   03 · FOOTER — from "footer.psd" (2870 × 717 @2x → 1435 × 358.5)

   The third cut of this board and the second composition. It
   replaces the 1435 × 423.5 one (parked as -423) and, before
   that, the 1435 × 406 + 948 pair. What moved from the 423.5:

     · the wordmark is smaller — 174.0 rather than 263.5 — and
       that is now exactly ONE COLUMN (173.57, 0.25% off), so
       under the section-00 rule it snaps rather than keeping a
       drawn width;
     · the brand rule starts on COLUMN 2 (243.50 vs 243.57)
       rather than at the mark's own right edge, and sits on the
       foot of the mark's ink rather than 5 below its baseline;
     · Journal is gone from the Sitemap — About and Work only;
     · the type drops: headings 25 → 20 named, items 18 → 14;
     · both rules are now 0.5 CSS px, and lighter — 40% and
       26.7% where they were 80% and 66.7%.

   The board's SECOND rule — the full-width hairline it draws
   across the foot at y 286.5 — is removed by request. The
   copyright still sits on the board's own baseline of 313.0,
   so the rhythm below the lists is the board's with the line
   taken out of it rather than closed up.

   ── The grid ─────────────────────────────────────────────
     margins 50 · gutters 20 · column 173.57 · track 193.57
     starts  50 · 243.57 · 437.14 · 630.71 · 824.29 · 1017.86
             · 1211.43        right edge 1385.00

   | wordmark ink |   49.50 | col 1 | −0.50 |
   | wordmark width | 174.00 | 1 col | +0.43 (0.25%) |
   | rule 1 left  |  243.50 | col 2 | −0.07 |
   | Sitemap      |  824.50 | col 5 | +0.21 |
   | Socials      | 1018.00 | col 6 | +0.14 |
   | Location     | 1210.50 | col 7 | −0.93 |
   | copyright    |   49.50 | col 1 | −0.50 |
   | both rules → | 1385.00 | edge  |  0.00 |

   Still the site's seven-column structure at HALF its margin
   and half its gutter — the page grid is 100 / 40 on 1460,
   which at 1435 computes to 98.29 / 39.31. So the footer still
   overhangs every other left and right edge by 48.3px at 1435.
   Built as drawn, on request; point --foot-margin and
   --foot-gutter at --grid-margin / --grid-gutter to snap it.

   ── Scale ────────────────────────────────────────────────
   A true scale of the board via cqw on .footer__board, with
   floors under the type. Hairline thickness stays in px.
   ═══════════════════════════════════════════════════════════ */

.footer{background:var(--black); color:var(--white);}

/* the container is the capped board, so 100cqw is the board's own width and
   one board pixel is 0.0696864cqw (100/1435) */
.footer__board{
  width:min(100%, 1435px);
  margin-inline:auto;
  container-type:inline-size;
}
.footer__inner{
  /* PSD: wordmark ink top 81.5 · margins 50 · copyright to the foot at 358.5 */
  padding:5.67943cqw 3.48432cqw 2.93449cqw;
}

/* ── The brand row: the mark on column 1, the rule on 2→7 ──
   Both are placed on the footer's own seven tracks rather than measured off
   each other, which is what the board now draws. `align-items:end` puts the
   rule on the foot of the mark's ink — the board has it at 140.0 against the
   swash's 139.5. */
.footer__brand{
  display:grid;
  grid-template-columns:repeat(7, minmax(0,1fr));
  column-gap:1.39373cqw;        /* 20 */
  align-items:end;
}

/* The wordmark, not the 1030 mark — the same trace the bar and the hero use.
   PSD ink 174.0 × 58.5 against the file's 507 × 172 viewBox. 174.0 is 0.25%
   off one column, well inside the 5% section 00 allows, so it snaps to the
   column instead of keeping its drawn width. */
.footer__mark{
  grid-column:1;
  display:block;
  color:var(--accent);
}
.footer__mark svg{display:block; width:100%; height:auto;}

.footer__rule{display:block; border:0;}

/* PSD: y 140.0, from column 2 to the right edge. One raw pixel on a @2x board,
   i.e. 0.5 CSS, at 102/255. It is the only rule the build draws — the board's
   second one, across the foot at 68/255, is removed by request. */
.footer__rule--brand{
  grid-column:2 / -1;
  height:0.5px;
  background:rgba(255,255,255,.4);
}

/* ── The three lists, on columns 5, 6 and 7 ───────────────── */
.footer__cols{
  display:grid;
  grid-template-columns:repeat(7, minmax(0,1fr));
  column-gap:1.39373cqw;        /* 20 */
  margin-top:2.37845cqw;        /* heading baseline to 191.5 */
}
.footer__col:nth-child(1){grid-column:5;}
.footer__col:nth-child(2){grid-column:6;}
.footer__col:nth-child(3){grid-column:7;}

/* PSD ink: Sitemap 86.00, Socials 75.50, Location 88.50 — BDO Bold at 21
   renders 86.50 / 75.12 / 89.12. The file names 20 (43.02 × 0.9297 × 2 = 40
   @2x); 21 is that × the 1.05 every board carries, and the ink says to
   reproduce it. Same one line down, and on the copyright. */
.footer__head{
  margin:0 0 0.20970cqw;        /* heading baseline 191.5 → first item 216.0 */
  font-size:1.46341cqw;         /* 21 = 20 × 1.05 */
  font-weight:700;
  line-height:1;
}

.footer__list{margin:0; padding:0; list-style:none;}

/* PSD ink: About 43.00, Work 36.00, Instagram 70.50, Linkedin 60.00, Beirut
   Lebanon 110.00 — BDO Regular at 14.7 renders 42.62 / 36.75 / 70.75 / 59.88
   / 110.38. Baselines 216.0 and 242.0: a 26.0 pitch, the layer's own leading
   over a blank line. */
.footer__list,
.footer__note{
  font-size:1.02439cqw;         /* 14.7 = 14 × 1.05 */
  line-height:1.768707;         /* 26.0 on 14.7 */
}
.footer__note{margin:0;}
.footer__list .link{display:block;}

/* the flip's box is taller than the line, so the leftover would otherwise
   push the baselines apart */
.footer__list li{line-height:0;}
.footer__list .flip__face{line-height:1.768707;}

/* PSD: baseline 313.0, ink 49.5 → 181.5, tracking −10. The string is drawn
   "© TenThiry 2026" — a missing t, in this board as in the three before it —
   and the build spells it TenThirty, so it renders ~6px wider. */
.footer__copy{
  /* The board draws a second hairline across the foot at y 286.5 and puts
     this line 26.5 under it. The rule is removed by request; the copyright
     stays on the board's own baseline of 313.0, so this margin is the rule's
     2.50871 plus its half pixel plus the 0.87873 that followed it. Close it
     up to 0.87873cqw if the footer should get shorter rather than keep the
     board's rhythm with an empty band. */
  margin:3.42227cqw 0 0;        /* last item baseline 242.0 → baseline 313.0 */
  font-size:1.17073cqw;         /* 16.8 = 16 × 1.05 */
  letter-spacing:-0.01em;
  line-height:1;
}

/* the mark is already the accent, so its hover goes the other way — the same
   move the open mobile panel makes */
@media (hover:hover){ .footer__mark:hover{color:var(--white);} }
.footer__mark:focus-visible{color:var(--white);}

/* ── Floors, and where the board gives up ──────────────────
   cqw keeps working all the way down, so the composition scales as one piece;
   what the small end needs is a floor under the type, not a second layout.
   Each is max(floor, the board's own cqw), which is continuous by
   construction — where the floor takes over, the two are equal.

   This board's type is small to begin with (14.7 items at 1435), so the
   floors bite earlier than the last one's. The binding item is still
   "Beirut, Lebanon" against its column: while the type scales, both are
   fractions of the width; once it floors at 12.5 the text is fixed at 93.5
   while the column keeps shrinking, and the two meet at about 773. The board
   is given up at 800. */
.footer__head{font-size:max(15px, 1.46341cqw);}
.footer__list,
.footer__note{font-size:max(12.5px, 1.02439cqw);}
.footer__copy{font-size:max(12px, 1.17073cqw); margin-top:max(28px, 3.42227cqw);}
.footer__inner{
  padding:max(36px, 5.67943cqw) max(20px, 3.48432cqw) max(26px, 2.93449cqw);
}
.footer__cols{
  column-gap:max(14px, 1.39373cqw);
  margin-top:max(18px, 2.37845cqw);
}
.footer__head{margin-bottom:max(2px, 0.20970cqw);}

/* Below 800 the three lists can no longer hold the right third of the board,
   so the composition is given up: the brand rule drops under the wordmark and
   the lists run across the full measure. No responsive board has been drawn
   for this footer — the old 948 one draws the first composition and is parked
   with it — so all of this is a decision, not a measurement. */
@media (max-width:799.98px){
  .footer__brand{
    grid-template-columns:1fr;
    row-gap:clamp(14px, 2.5vw, 22px);
  }
  .footer__mark{width:min(174px, 42vw);}
  .footer__rule--brand{grid-column:1;}
  .footer__cols{
    grid-template-columns:repeat(auto-fit, minmax(124px, 1fr));
    row-gap:clamp(22px, 4vw, 32px);
    margin-top:clamp(18px, 3vw, 26px);
  }
  .footer__col:nth-child(1),
  .footer__col:nth-child(2),
  .footer__col:nth-child(3){grid-column:auto;}
}


/* ═══════════════════════════════════════════════════════════
   04 · FEATURED PROJECTS — from "projects.psd"
   (3115 × 2447 @2x → 1557.5 × 1223.5). That board is wider than
   the 1435 the rest of the site uses, but its content spans
   110.5 → 1436.5 = 1326, and the site's own 50/72 gutters give
   1313 — within 1%. So the section is read as the same content
   column the bar sits in, not as a new board width.
   ═══════════════════════════════════════════════════════════ */
:root{
  /* pro.psd, @2x like every board here (its pill is 264 × 103 against
     button.psd's 260 × 99, and its heading ink is 625 × 173 — the same number
     --proj-head-w already carried). Two artboards: the homepage row and the
     projects page.

     THE PLATES ARE NOT EQUAL ANY MORE. Each row is a THREE-column plate beside
     a FOUR-column one, filling all seven with a single gutter between — no
     empty column, and no drop. Rows alternate which side is wide, so the
     4-cycle below reads 3+4, 4+3. The homepage takes the first two cards, so
     it gets 3+4, which is exactly its own board.

     Both plates in a row share a HEIGHT rather than a ratio, so their aspects
     differ by span: 0.711 for three columns, 0.955 for four, measured on all
     five plates across the two boards (spread 0.8% and 0.04%). Held as one
     height because the boards align the plates' tops AND feet. */
  --proj-cap-gap:33.5px;      /* plate's foot → "Category" cap */
  --proj-head-w:312.5px;      /* PSD ink 625 × 173 @2x */
  /* The row height, off the grid rather than off a board width. The two spans
     imply 727.3 and 736.0 at 1460; this is their mean, so each gives up 0.6%
     — inside the boards' own spread on the 4-column plate (1663…1673). */
  --plate-h:calc(4.2040 * var(--gcol) + 2.9772 * var(--grid-gutter));
}

.projects{
  background:var(--black);
  padding-block:79.5px 163.5px;      /* PSD: heading ink 79.5 down */
}
/* was max-width:1313 centred — its own measure. It is the grid's content
   column now, which is 1260 at the board: 53px narrower. */
.projects__inner{
  width:min(100%, var(--grid-max));
  margin-inline:auto;
  padding-inline:var(--grid-margin);
}

.projects__top{
  display:flex;
  align-items:flex-start;
  justify-content:space-between;
  gap:24px;
}
/* Traced, like the wordmark and the hero line — the serif is Fenway Banner
   Variable Compressed Light and there is no font file for it here. */
.projects__head{margin:0; line-height:0;}
.projects__head svg{display:block; width:var(--proj-head-w); height:auto;}

/* The board draws a line of type here; button.psd replaces it with the pill,
   which is a different object at a different size, so what is held is the
   number this board actually recorded — the label's BASELINE at 145.5. The
   pill puts its baseline 30.98 down its own 49.5 where the 25px flip box put
   it at 23.29, so the margin gives back the 7.69 difference. */
/* "ALL PROJECTS", from source/all-projects-button.psd (2026-09-01) — a second
   COMPONENT board, 447 × 398 @2x like button.psd, two artboards drawing the
   same two states. It is not a new object: the box measures 397 × 99 @2x, the
   SAME 99 button.psd's second cut gave, and a least-squares fit over 47 corner
   rows puts the radius at 48.5 @2x against that board's 47.6 — 0.45 CSS apart,
   which is inside the fit's own noise. The label is BDO Regular at
   26.11 × the layer matrix's 1.531980 = 40.00 @2x = 20 CSS on tracking −10,
   and this time the layer RECORDS the −10 rather than 0. Colours are the
   component's exactly: black fill, #d0df83 label, inverted.

   So the whole board is `.btn` with a longer label in caps, and the width
   follows the text the way the component already says it does — the board's
   198.5 is what 155 of label ink plus the component's 25.09 of padding a side
   comes to. Nothing here overrides the component but the case.

   (The board draws a black hairline, visible only on its lime state against
   the white artboard. On the page the ground is black, so the repair runs the
   other way and the lime edge above stays.) */
.projects__all{
  margin-top:28.31px;             /* was 36, less 7.69 */
  flex:none;
  text-transform:uppercase;
  /* The one place the two component boards disagree. Ink box to pill edge is
     52 @2x = 26.0 CSS on button.psd ("View All", 156 of ink in a 260 box) and
     43.5 @2x = 21.75 on this one ("ALL PROJECTS", 310 in 397) — 4.25 tighter a
     side, which is exactly the 4.27 the component's 1.2545em rendered over the
     board's 198.5. Scoped here rather than changed on .btn: button.psd is
     still the named component, and each destination board holding its own
     number is the rule the About pills already follow. README 18.

     1.14775em, not the board's own proportion, for the same reason the About
     pills take an absolute box: the label is built at the named 20 where the
     board renders 21, so pinning the BOX to the board and letting the 5% fall
     on the padding keeps the drawn pill the drawn size. */
  padding-inline:1.14775em;       /* 22.955 / 20 -> the board's 198.5 */
}

/* The board's grid, and the only one on the site. Both pages share it, so
   projects.html is the homepage row repeated rather than a second layout.

   A row is 3 columns + 4 columns with one gutter between, filling all seven,
   and the next row swaps them. Measured against a 3154-wide board with the
   grid's own 100 margins and 40 gutters, every plate lands on a column
   boundary within 5px — 0.16%:

     r1c1  cols 1–3    r1c2  cols 4–7
     r2c1  cols 1–4    r2c2  cols 5–7

   The plates used to be three columns each with column four empty between
   them and the right one dropped an eighth of its height. The board draws
   neither now: no empty column, and the tops are level. */
.plate-grid{
  display:grid;
  grid-template-columns:repeat(var(--grid-cols), minmax(0,1fr));
  column-gap:var(--grid-gutter);
  row-gap:82.5px;                 /* PSD: caption foot 2344 → next plate 2509 */
  align-items:start;
}
/* Scoped above the breakpoint on purpose. `.plate-grid > :nth-child(...)` is a
   class plus a pseudo-class and beats the `.plate-grid > *` reset the stacked
   layout would need, so the spans would survive into a one-track grid and
   squash every odd card into a sliver. Same trap §05 records for .phase__body,
   solved here by never emitting the rules below 768 rather than by !important. */
@media (min-width:768px){
  .plate-grid > :nth-child(4n+1){grid-column:1 / span 3;}
  .plate-grid > :nth-child(4n+2){grid-column:4 / span 4;}
  .plate-grid > :nth-child(4n+3){grid-column:1 / span 4;}
  .plate-grid > :nth-child(4n+4){grid-column:5 / span 3;}
}

.projects__grid{margin-top:26.5px;}   /* PSD: heading foot 166 → plate top 192.5 */

.proj{display:block; text-decoration:none; color:var(--white);}
.proj__plate,.proj__meta,.proj__name{display:block;}
/* A height, not a ratio: the two spans in a row are different widths and the
   board aligns their feet, so the shared number is the height. */
.proj__plate{
  display:block;
  width:100%;
  height:var(--plate-h);
  background:var(--accent);       /* the board's own placeholder */
  overflow:hidden;
}
.proj__plate img{
  display:block; width:100%; height:100%;
  object-fit:cover;
  transition:transform .5s var(--flip-ease);
}
@media (hover:hover){
  .proj:hover .proj__plate img{transform:scale(1.03);}
}

.proj__meta{
  margin-top:var(--proj-cap-gap);
  font-size:20px;                 /* BDO Regular, matches the bar's CTA size */
  line-height:1;
}
.proj__error{margin:0; font-size:15px; color:var(--accent);}
.proj__error code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;}
.proj__name{
  margin-top:17px;                /* PSD: 37 between the two baselines */
  font-family:'TT Serif',Didot,'Bodoni 72','Times New Roman',serif;
  font-weight:700;
  font-size:30px;                 /* PSD cap 29.5 — see the stand-in note */
  line-height:1;
  perspective:13.6em;             /* the flip wants depth on the parent */
}
@media (hover:hover){
  .proj:hover .proj__name .flip{transform:translateZ(calc(var(--flip-h) / -2)) rotateX(-90deg);}
}
.proj:focus-visible .proj__name .flip{transform:translateZ(calc(var(--flip-h) / -2)) rotateX(-90deg);}
.proj:active .proj__name .flip__face--front{color:var(--accent);}

@media (max-width:767.98px){
  .projects{padding:48px 20px 72px;}
  /* the pill needs 107 next to a 179 heading in a 297 row, so below the
     breakpoint the row wraps and the button takes its own line under the
     heading. As a line of type it used to fit; as a button it does not. */
  .projects__top{align-items:flex-end; flex-wrap:wrap; row-gap:26px;}
  .projects__head svg{width:min(var(--proj-head-w), 46vw);}
  .projects__all{--btn-size:17px; margin-top:0;}
  /* One column, so the spans above have to be unset or they overflow a
     single-track grid. The plate takes the 4-column aspect rather than the
     3-column one — full width at 0.711 is 550px of image on a 390 screen. */
  .plate-grid{grid-template-columns:minmax(0,1fr); row-gap:40px;}
  /* The plate takes the 4-column aspect rather than the 3-column one — full
     width at 0.711 is 550px of image on a 390 screen. */
  .proj__plate{height:auto; aspect-ratio:.955;}
  .projects__grid{margin-top:28px;}
  .proj__meta{font-size:14px; margin-top:18px;}
  .proj__name{font-size:22px; margin-top:10px;}
}

/* ── Responsive ─────────────────────────────────────────── */
/* Down to the breakpoint, type and gaps scale off the 1435 board. */
@media (min-width:768px) and (max-width:1459.98px){
  :root{
    --nav-size:clamp(13px,1.18vw,17px);
    --cta-size:clamp(13px,1.39vw,20px);
    --nav-gap:clamp(16px,2.37vw,34px);
    /* --pad-l / --pad-r follow --grid-margin, which clamps itself */
    --logo-w:clamp(92px,9.13vw,131px);
    --bar-h:clamp(64px,6.41vw,92px);
  }
}

/* ── Mobile: burger right, wordmark centred ─────────────── */
/* No mobile board exists, so this is a decision, not a measurement:
   the bar keeps only the brand and the control, and the three labels
   move into a full-screen panel at display sizes. */
@media (max-width:767.98px){
  /* the frame is so tall here that cover keeps only about a quarter of the
     plate's width, and centred that is nothing but the blurred white of the
     walker's leg. 30% puts the dark ground under the type and brings the
     figure in from the right edge, which is the picture rather than a wash.
     object-position and transform-origin have to move together — matched,
     the pair reads as "this fraction of the total overflow". */
  .hero__img{object-position:30% 0; transform-origin:30% 0;}

  :root{
    --bar-h:64px;
    /* the grid margin bottoms out at 20 on its own */
    --logo-w:104px;
    /* half the display sizes this started at, which lands the panel back
       on the bar's own scale — ~18px / ~22px on a 390 phone against the
       PSD's 17 / 20 — and keeps the 20:17 ratio between them */
    --m-size:clamp(15px,4.7vw,21px);      /* About / Projects */
    --m-cta:clamp(18px,5.55vw,25px);      /* Get in touch */
    --m-gap:clamp(14px,3.6vw,20px);

  }

  /* the panel is a child of .header, so it paints inside the header's own
     stacking context — the brand and the control have to be lifted over it
     or the open panel swallows them. */
  .logo{grid-column:2; justify-self:center; position:relative; z-index:110;}
  .burger{display:grid; position:relative; z-index:110;}

  /* the panel becomes a real box, and lifts out of the grid entirely */
  .menu{
    display:flex;
    position:fixed;
    inset:0;
    z-index:90;
    flex-direction:column;
    align-items:flex-start;
    gap:var(--m-gap);
    /* auto margins centre the group but collapse before they clip, which
       justify-content:center will not do — insurance for a short landscape
       viewport, or for the day the nav grows past three items */
    overflow-y:auto;
    overscroll-behavior:contain;
    padding:var(--bar-h) var(--pad-l) max(40px,env(safe-area-inset-bottom));
    background:var(--black);
    opacity:0;
    visibility:hidden;
    transition:opacity .38s var(--flip-ease), visibility 0s linear .38s;
  }
  .menu-open .menu{
    opacity:1;
    visibility:visible;
    transition:opacity .38s var(--flip-ease), visibility 0s;
  }

  .nav{
    flex-direction:column;
    align-items:flex-start;
    gap:var(--m-gap);
    justify-self:auto;
  }
  .nav .link{font-size:var(--m-size);}
  .menu .nav{margin-top:auto;}
  .cta{
    justify-self:auto;
    font-size:var(--m-cta);
    margin-top:.34em;
    margin-bottom:auto;
  }

  /* ── Open state: the panel inverts ──────────────────────
     Field goes lime, every mark on it goes black, and the flip's second
     face — accent everywhere else on the site — turns white, so the
     hover still reads as the one thing that changes colour. */
  .menu-open .menu{background:var(--accent);}
  .menu-open .logo,
  .menu-open .burger,
  .menu-open .menu .link{color:var(--black);}
  .menu-open .menu .flip__face--back{color:var(--white);}

  /* the ink crosses at the same rate the field does, so the two read as
     one move rather than two */
  .logo,.burger{transition:color .38s var(--flip-ease);}

  @media (hover:hover){
    .menu-open .logo:hover,
    .menu-open .burger:hover{color:var(--white);}
  }
  .menu-open .logo:focus-visible,
  .menu-open .burger:focus-visible{color:var(--white);}
  .menu-open .menu .link:active .flip__face--front{color:var(--white);}

  /* a lime focus ring is invisible on a lime field */
  .menu-open :where(a,button):focus-visible{outline-color:var(--black);}

  /* the labels arrive just behind the panel, one after the next */
  .menu .link{
    opacity:0;
    transform:translateY(16px);
    transition:opacity .34s var(--flip-ease), transform .44s var(--flip-ease);
  }
  .menu-open .menu .link{opacity:1; transform:none;}
  .menu-open .nav .link:nth-child(1){transition-delay:.10s;}
  .menu-open .nav .link:nth-child(2){transition-delay:.16s;}
  .menu-open .cta{transition-delay:.22s;}
}

/* No flip if the reader has asked for less motion — the accent colour
   carries the whole affordance instead. The rotation has to be unset
   explicitly, or it would snap to edge-on with nothing behind it. */
@media (prefers-reduced-motion:reduce){
  /* the bar picks up its ground instead of moving out of the way */
  .header{transition:none;}
  .header[data-hidden]{transform:none;}
  /* the ground is no longer a reduced-motion special case — every reader gets
     it now — so all this block still has to do is stop the bar moving */
  .flip,
  .link:hover .flip,
  .link:focus-visible .flip{transform:none; transition:none;}
  .flip__face--front{transform:none;}
  .flip__face--back{display:none;}
  .link{transition:color .2s ease;}
  /* ...but not across a change of ground — see "The bar's ground" */
  .header .link{transition:none;}
  .header .link:hover,
  .header .link:focus-visible,
  .header .link:active{transition:color .2s ease;}
  @media (hover:hover){
    .link:hover{color:var(--accent);}
    /* accent on the accent field would be no change at all */
    .menu-open .menu .link:hover{color:var(--white);}
  }
  .link:focus-visible{color:var(--accent);}
  .link:active{color:var(--accent);}
  .menu-open .menu .link:focus-visible,
  .menu-open .menu .link:active{color:var(--white);}

  .menu,.menu-open .menu{transition:opacity .01s;}
  .menu .link,.menu-open .menu .link{transition:none; transform:none; transition-delay:0s;}
  .burger__bar{transition:none;}
}

/* ═══════════════════════════════════════════════════════════
   05 · PROJECTS PAGE — no board for this one.
   Everything structural is borrowed from projects.psd rather
   than invented: the 21px gutter, the 0.938 plate ratio, and
   the idea that neighbouring plates hang at different heights.
   ═══════════════════════════════════════════════════════════ */
.page-projects{background:var(--black);}
/* the bar overlays the hero on the homepage; here there is no hero to
   overlay, so the page starts below it */
.page-projects main{padding-top:var(--bar-h);}

/* pro.psd's second artboard puts a photograph behind this page — a
   motion-blurred street, black at the top and building to warm light at the
   foot. The board draws it starting 328 CSS down and running past the board's
   own bottom edge, so it is cropped by the artboard rather than fitted to it.

   THE BOARD IS A FIXED HEIGHT AND THIS PAGE IS NOT: it is as long as there are
   projects. So the picture is placed PROPORTIONALLY — it starts 13.9% down
   (the board's 328 of 2367) and covers the rest — rather than at a fixed
   offset or a fixed size. That keeps the glow's position relative to the grid
   whatever the project count, which is what the board is actually saying.

   Anchoring it to the foot at its natural height was tried first and is wrong:
   on a four-project page it drove the bright band straight through row one's
   captions, 2.28:1 against white where 4.5 is needed. Proportional placement
   puts it back under the last row. The photograph's own top is black, so where
   it begins never reads as an edge.

   .work is full-bleed now to carry it, and the 1460 cap moved to __inner —
   the same split .footer__board / .footer__inner already uses. */
.work{
  position:relative;
  isolation:isolate;
  overflow:hidden;
  background:var(--black);
}
.work__inner{
  position:relative;
  z-index:1;
  padding:64px var(--pad-r) 120px var(--pad-l);
  max-width:var(--grid-max);
  margin-inline:auto;
}
.work__ground{
  position:absolute;
  inset:0;
  z-index:0;
  overflow:hidden;
  pointer-events:none;
}
/* The board shows the photograph's top 84% and lets the section's foot crop
   the rest, so the box runs PAST the foot and the ground's overflow clips it:
   328 down and 18% over, of the board's 2367. */
.work__ground img{
  position:absolute;
  inset:13.9% 0 -18% 0;
  display:block;
  width:100%;
  object-fit:cover;
  object-position:center top;
}
/* THE BOARD SCRIMS ITS OWN PHOTOGRAPH, and this is not invented — "Layer 4"
   sits over the picture at norm blend with a vertical alpha ramp, read off the
   layer's own alpha channel down the page board:

     0%  255      39%   89      80%    8
    18%  240      49%    8      90%   13
    28%  167      59–69% 0     100%   34

   Opaque at the head, gone by the middle, and a whisper of it back at the
   foot. It is what keeps the heading and the first row on flat black while the
   trails still read lower down, and without it the last row's captions land on
   the glow at 1.63:1 against white. Stops below are that ramp, /255. */
.work__ground::after{
  content:"";
  position:absolute;
  inset:0;
  background:linear-gradient(to bottom,
    rgba(0,0,0,1)    0%,
    rgba(0,0,0,.94) 18%,
    rgba(0,0,0,.65) 28%,
    rgba(0,0,0,.35) 39%,
    rgba(0,0,0,.03) 49%,
    rgba(0,0,0,0)   59%,
    rgba(0,0,0,0)   69%,
    rgba(0,0,0,.03) 80%,
    rgba(0,0,0,.05) 90%,
    rgba(0,0,0,.13) 100%);
}

.work__head{
  display:flex;
  align-items:flex-end;      /* a traced mark has no useful baseline */
  justify-content:space-between;
  gap:24px;
  margin-bottom:56px;
}
/* It is the serif after all. This was BDO caps because the studio's display
   face is Fenway Banner, there is no font file for it, and a stand-in read 80%
   too wide — the same reason the homepage heading is traced rather than set.
   pro.psd supplies this word as artwork now, so it gets the same treatment:
   PSD ink 285 × 170 @2x = 142.5 × 85 CSS. */
.work__title{margin:0; line-height:0;}
.work__title svg{display:block; width:142.5px; height:auto; color:var(--white);}
.work__count{
  margin:0;
  font-size:clamp(13px,1.4vw,20px);
  color:var(--accent);
  white-space:nowrap;
}

@media (max-width:767.98px){
  .work__inner{padding:40px 20px 72px;}
  .work__head{margin-bottom:32px;}
  .work__title svg{width:min(142.5px, 34vw);}
}

/* ── Plate media & the gallery ───────────────────────────── */
.proj__plate{position:relative;}
.proj__plate video{
  display:block; width:100%; height:100%;
  object-fit:cover;
  transition:transform .5s var(--flip-ease);
}
@media (hover:hover){
  .proj:hover .proj__plate video{transform:scale(1.03);}
}
/* how many pieces are behind this cover */
.proj__count{
  position:absolute; right:12px; bottom:12px;
  min-width:26px; height:26px;
  display:grid; place-items:center;
  padding:0 8px;
  background:rgba(0,0,0,.72);
  color:var(--white);
  font-size:12px; line-height:1;
  letter-spacing:var(--track);
  backdrop-filter:blur(6px);
}

/* Full-screen reel. It is a dialog rather than a page so a project's media
   can be reached from either page without a second template to keep in sync. */
.gallery{
  position:fixed; inset:0; z-index:200;
  border:0; padding:0; margin:0;
  width:100%; max-width:100%; height:100%; max-height:100%;
  background:var(--black); color:var(--white);
  opacity:0; visibility:hidden;
  transition:opacity .3s var(--flip-ease), visibility 0s linear .3s;
}
.gallery[open]{opacity:1; visibility:visible; transition:opacity .3s var(--flip-ease), visibility 0s;}
.gallery::backdrop{background:var(--black);}

.gallery__bar{
  position:sticky; top:0; z-index:2;
  display:flex; align-items:baseline; justify-content:space-between; gap:20px;
  /* the right side has to clear the fixed close button, or the meta runs
     straight under the × */
  padding:22px calc(var(--pad-l) + 52px) 22px var(--pad-l);
  background:linear-gradient(180deg,rgba(0,0,0,.92) 40%,rgba(0,0,0,0));
}
.gallery__title{margin:0; font-size:20px; font-weight:700; letter-spacing:var(--track);}
.gallery__meta{margin:0; font-size:15px; color:var(--accent); white-space:nowrap;}
.gallery__close{
  position:fixed; top:14px; right:14px; z-index:3;
  width:44px; height:44px; display:grid; place-items:center;
  border:0; background:none; color:var(--white); cursor:pointer;
  -webkit-tap-highlight-color:transparent;
}
.gallery__close svg{display:block; width:22px; height:22px;}
@media (hover:hover){.gallery__close:hover{color:var(--accent);}}

.gallery__reel{
  padding:0 var(--grid-margin) 96px;
  display:grid; gap:24px;
  max-width:var(--grid-max); margin-inline:auto;
}
.gallery__reel img,
.gallery__reel video{
  display:block; width:100%; height:auto;
  max-height:88svh; object-fit:contain;
  background:#0a0a0a;
}
.gallery__empty{color:var(--accent); font-size:15px;}

html.gallery-open, html.gallery-open body{overflow:hidden;}

@media (max-width:767.98px){
  .gallery__bar{padding:18px 62px 18px 20px;}
  .gallery__reel{padding:0 20px 72px; gap:16px;}
  .gallery__title{font-size:15px;}
  .gallery__meta{font-size:12px;}
}

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

/* ── Case study ──────────────────────────────────────────── */
/* Sits between the bar and the reel: read it, then look at the work. */
.gallery__intro{
  max-width:var(--grid-max);
  margin:0 auto;
  padding:0 var(--grid-margin) 44px;
}
.gallery__intro[hidden]{display:none;}
.gallery__case{
  max-width:62ch;                 /* a measure, not the full 1200 */
  font-size:clamp(15px,1.25vw,18px);
  line-height:1.6;
  color:rgba(255,255,255,.82);
}
.gallery__case p{margin:0 0 1.15em;}
.gallery__case p:last-child{margin-bottom:0;}

@media (max-width:767.98px){
  .gallery__intro{padding:0 20px 32px;}
}

/* ═══════════════════════════════════════════════════════════
   06 · APPROACH — from "approach.psd" (2920 × 8406 @2x →
   1460 × 4203). Replaces an earlier 1460 × 3270 cut of the
   same board: the four phases were re-laid-out and a fifth
   band was added on top — a motion-blurred plate carrying
   "Our Approach", which retires the small "Approach" mark
   that used to sit in phase 01.

   The palette inverts here: black type on lime and white.
   The serif is all traced, so no Fenway licence is needed for
   this section. The four numerals and the four phase labels
   are the SAME artwork as the previous cut — measured
   identical to the pixel — so those traces are reused; only
   "Our Approach" is newly cut, and it is white ink on a
   photograph, which is what --invert in tools/trace.py is for.

   Played as a deck of five: one panel holds the viewport, then
   the whole thing — paper, type and numeral together — slides
   up and the next is already behind it. The plate is panel one
   and travels with the rest. Pace lives in site.js.

   ── On the grid ──
   Everything here is placed on the 7-column grid, not on the
   board's own offsets. What that moved, in board px:

     phase 01 label   80  → 100   (col 1)        +20
     phase 01 copy   301.5→ 285.7 (col 2, span 2) −15.8
     phase 01 col b  733  → 657.1 (col 4, span 2) −75.9
     phase 02 block  976.5→1028.6 (col 6, span 2) +52.1
     phase 03 block  268.5→ 285.7 (col 2, span 2) +17.2
     phase 04 label  732  → 657.1 (col 4)         −74.9
     phase 04 block  972.5→1028.6 (col 6, span 2) +56.1

   The numerals all span five columns — c3–c7 on the odd
   phases, c1–c5 on the even — which is within 36px of every
   drawn width and makes the alternation regular where the
   board had it by eye. Widening them to the column changes
   their height a little (07:30 559.5 → 564, 12:30 496 → 516),
   and each panel's inner is sized to hold its own numeral in
   full, since a deck has no next band for it to bleed onto.
   ═══════════════════════════════════════════════════════════ */
.approach{
  position:relative;
  background:var(--black);
  /* The deck keeps its composition clear of the bar. Every panel centres its
     board inside 100svh, and the two numerals that sit nearest the top of
     their band — 10:30 at 66 of an 828 board, 12:00 at 19.5 of an 821 — were
     landing at y 84 and y 42 on an 800px viewport, i.e. inside a 92px bar. The
     fade above stops that reading as a cut, but the ink is still under it, so
     the panel reserves the clearance and the composition sits below it.

     88, and it is solved rather than picked. Spending reserve R leaves a board
     of 0.94(svh − R) under a top offset of 0.97R + 0.03·svh, so 12:00's ink —
     2.5 CSS px down an 804 band, i.e. flush with the head of its own picture —
     lands at `0.9670771·R + 0.0329229·svh`. Against the fade's foot at 101.2
     that needs R ≥ 77.5 at svh 800, ≥ 80.8 at 700 and ≥ 84.2 at 600, which is
     the shortest window the deck runs in at all (below 1200 wide it is a
     stacked list). 84.2 leaves 0.8px in hand at 600, which is not a margin —
     88 leaves 3.6 and costs 4px of board at 800. 10:30 clears at 118.4 in the
     same worst case, and 07:00 and 09:00 are never near it.

     It was 64 until the bands were re-cut on 2026-09-01: 12:00 used to sit
     19.5 px down an 821 band, and reading its band against the photo mask's
     own head moved it to 2.5. Costs nothing where there is room — from about
     950svh the board is capped by --grid-max and the reserve is pure offset;
     at 800 the composition is ~11% smaller than an unreserved one. */
  --deck-clear:88px;
}

.phase-stack{
  position:sticky;
  top:0;
  height:100svh;
  overflow:hidden;
}
.phase{
  position:absolute;
  inset:0;
  display:grid;
  place-items:center;
  /* the bar's clearance, reserved off the top — see .approach above. The
     stacked layout below 1200 sets padding-block and overrides it. */
  padding-top:var(--deck-clear);
  overflow:hidden;
  will-change:transform;
  /* This is the deck. site.js publishes --out per panel (0% while the panel
     holds, down to -100% as it clears) and the panel above slides off to
     reveal the one beneath; --under, on .phase__inner, eases the one beneath
     up behind it. Without this line the script still computes --out and
     nothing reads it, so all five panels sit at inset:0 and the section is
     7200px of the plate — which is exactly what it was doing. */
  transform:translate3d(0, var(--out, 0%), 0);
}
/* The whole section is a photograph now, so the palette goes the other way:
   white ink, and the sub-heading and body copy in the accent. Every panel is
   the same dark ground — the alternating lime and white paper is gone. */
.phase{color:var(--white); background:var(--black);}
.phase--0{z-index:5;}
.phase--1{z-index:4;}
.phase--2{z-index:3;}
.phase--3{z-index:2;}
.phase--4{z-index:1;}

/* ── Each panel's own slice of the photograph ─────────────
   The board is one continuous image 1460 × 4203 — checked across all four
   band boundaries, mean |Δ| 1.3–2.7 — so each panel carries its own slice.
   EVERY mark is drawn live now, the four numerals and "Our Approach" included,
   so the slice is pure photograph. That is what lets the picture go full-bleed:
   while the numerals were burnt into it, the image and the type had to scale as
   one piece, so the panel was capped at the board's 1460 and letterboxed — 230px
   of black each side on a 1920 screen, 550 on a 2560. The plate now covers the
   whole panel and the composition sits in a 1460 column inside it, which is what
   the stacked layout below 1200 already did.

   The numerals are cut at the pre-0.860 size (viewBox 884 × 560.5 for 07:00) and
   scaled down by the cqw widths below — they are vectors, so that is lossless.
   Graded against their own layer alpha on this board: shape error 0.00% on all
   five. They are <img> rather than inlined because the four of them are 287KB of
   path data, which would more than double index.html; the fill is baked into the
   file for the same reason, since an <img> cannot inherit currentColor. Everything that sits on FLAT BLACK — the four headings and all ten
   copy blocks, ground spread ≤ 14 of 255 — was filled out of the slices and is
   drawn live over them.

   The slices are cut by tools/cut_bands.py, which also rebuilds the patch of
   phase 01 under the board's leftover "10:30 Make It Tick" layer. See
   MEASUREMENTS 05. */
.phase__plate{position:absolute; inset:0; z-index:0;}
.phase__plate img{width:100%; height:100%; object-fit:cover; display:block;}

/* The numerals and "Our Approach", drawn live over the photograph. Positions
   are the layer rects like every other mark; the artwork is white, and the
   plate's mark is the accent, both baked into the SVGs. */
.phase__mark img,
.phase__time img{display:block; width:100%; height:auto;}

.phase__inner{
  position:relative;
  z-index:1;
  /* 94% of what is LEFT after the bar's clearance, not of the whole viewport —
     the panel's content box is 100svh − --deck-clear tall now, and the 6% it
     keeps back is the headroom --under's 3% depth easing spends. */
  width:min(100%, var(--grid-max),
            calc((100svh - var(--deck-clear)) * 0.94 * var(--fit, 1.6553)));
  container-type:inline-size;
  transform:translate3d(0, var(--under, 0%), 0);
}
.phase__inner > *{position:absolute; margin:0;}
.phase__label svg{display:block; width:100%; height:auto; color:var(--white);}

/* PSD ink: Read the Room 198.0, Set the Time 161.5, Make It Tick 156.5,
   Keep It Moving 191.0 — BDO Bold at 26.25 renders 197.75 / 161.75 / 158.25 /
   193.00. The file names 25; 26.25 is that × the 1.05 every board carries. */
.phase__title{
  font-size:1.7979cqw;       /* 26.25 = 25 × 1.05 */
  line-height:1;
  font-weight:700;
  white-space:nowrap;
  color:var(--accent);
}
/* the body is the accent too on this board, not white */
.phase__body{
  font-size:1.4384cqw;       /* 21 = 20 × 1.05 */
  line-height:1.142857;      /* PSD baseline pitch 24.0 on 21 */
  letter-spacing:-0.01em;    /* the layers' −10 */
  font-weight:400;
  color:var(--accent);
}

/* ── Panel 0: the plate ─────────────────────────────────── */
.phase--0 .phase__inner{aspect-ratio:1460/924; --fit:1.5801;}
.phase--0 .phase__mark{left:39.89726cqw; width:20.20548cqw; top:26.54110cqw;} /* ink 582.5, 387.5 */

/* ── The four phases ────────────────────────────────────────
   A SEVENTH board (2026-08-31), on the same 1460 × 4203 canvas. It touches ONE
   band: phase 04's heading, sub-heading and copy all rise 496 @2x — exactly 248
   board px — with the same x, width and height, and the photograph behind them
   moved. Comparing the three layers' channels between the boards after the
   shift gives max |Δ| 0, so they are the same bytes moved: phase-04-head.svg is
   reused untouched and this cut is the three `top` values below, nothing else.
   The 12:00 numeral did not move, so the block now sits level with its top.
   Bands 01, 02 and 03 are byte-identical to the sixth board's.
   The plate band is no longer cut from this PSD at all. The sixth board's
   re-shot plate put "Our Approach" on a pale highlight and the seventh did not
   fix it, so the studio draws the plate as its own document now —
   source/approach-hero.psd, with the mark at exactly the rect the board uses,
   so nothing below moves. The override lives in tools/cut_bands.py so a re-cut
   cannot revert it, and it is the only place the build diverges from the
   board. See MEASUREMENTS 05.

   The numerals are still the same artwork at 0.860 (760.5/884.0 = 0.8603,
   779.0/905.5 = 0.8603, 734.0/853.0 = 0.8605, 737.5/857.0 = 0.8606) and every
   one is whole and inside its band.

   Positions are the layer rects, exact. One board pixel is 0.0684932cqw. */

/* ── Phase 01 · Discovery ─ band 924–1752 (828) ─────────── */
.phase--1 .phase__inner{aspect-ratio:1460/828; --fit:1.7633;}
.phase--1 .phase__label{left:3.39041cqw;  width:28.01370cqw; top:4.45205cqw;}   /* ink 49.5, 65.0 */
.phase--1 .phase__title{left:16.84932cqw; width:30cqw;       top:21.10329cqw;}  /* ink 246.5, 308.5 */
.phase--1 .phase__body {left:16.88356cqw; width:24.65753cqw; top:23.77666cqw;}  /* ink 247.5, 349.0 */
.phase--1 .phase__body--b{left:16.78082cqw; width:24.65753cqw; top:28.09605cqw;}/* ink 245.0, 412.0 */
.phase--1 .phase__time{left:44.17808cqw; width:52.08904cqw; top:15.13699cqw;}  /* ink 645.0, 221.0 */

/* ── Phase 02 · Strategy ─ band 1752–2554 (802) ─────────── */
.phase--2 .phase__inner{aspect-ratio:1460/802; --fit:1.8204;}
.phase--2 .phase__label{left:57.50000cqw; width:26.36986cqw; top:7.46575cqw;}   /* ink 839.5, 109.0 */
.phase--2 .phase__title{left:57.50000cqw; width:30cqw;       top:16.58433cqw;}  /* ink 839.5, 242.5 */
.phase--2 .phase__body {left:57.46575cqw; width:25.00000cqw; top:18.87888cqw;}  /* ink 839.0, 277.5 */
.phase--2 .phase__time{left: 3.42466cqw; width:53.35616cqw; top:22.05479cqw;}  /* ink 50.0, 322.0 */

/* ── Phase 03 · Identity ─ band 2554–3382 (828) ─────────── */
/* Band top moved 2554 -> 2590 on 2026-09-01 (the photo mask's own head; see
   tools/cut_bands.py), so every y here is 36 CSS px smaller than it was and the
   band is 828 -> 809 tall. x is untouched. */
.phase--3 .phase__inner{aspect-ratio:1460/809; --fit:1.80470;}
.phase--3 .phase__label{left:3.90411cqw;  width:25.68493cqw; top: 7.29452cqw;}  /* ink 57.0, 106.5 */
.phase--3 .phase__title{left:17.08904cqw; width:30cqw;       top:27.60274cqw;}  /* ink 249.5, 403.0 */
.phase--3 .phase__body {left:17.15753cqw; width:25.34247cqw; top:30.61644cqw;}  /* ink 250.5, 447.0 */
.phase--3 .phase__body--b{left:17.22603cqw; width:25.34247cqw; top:35.03425cqw;}/* ink 251.5, 511.5 */
.phase--3 .phase__time{left:45.13699cqw; width:50.27397cqw; top: 2.05479cqw;}  /* ink 659.0, 30.0 */

/* ── Phase 04 · Activation ─ band 3382–4203 (821) ───────── */
/* Band top moved 3382 -> 3399 the same day and for the same reason, so every y
   here is 17 smaller and the band is 821 -> 804. That puts 12:00's ink 2.5 CSS
   px below the panel's own top, which is what the board draws — the numeral is
   flush with the head of its picture — and it is why --deck-clear is 88. */
.phase--4 .phase__inner{aspect-ratio:1460/804; --fit:1.81592;}
.phase--4 .phase__label{left:57.50000cqw; width:29.21918cqw; top: 7.22603cqw;}  /* ink 839.5, 105.5 */
.phase--4 .phase__title{left:71.06164cqw; width:30cqw;       top:18.69863cqw;}  /* ink 1037.5, 273.0 */
.phase--4 .phase__body {left:71.19863cqw; width:22.94521cqw; top:21.19863cqw;}  /* ink 1039.5, 309.5 */
.phase--4 .phase__time{left: 2.56849cqw; width:50.51370cqw; top: 0.17123cqw;}  /* ink 37.5, 2.5 */

/* ── Small screens ──────────────────────────────────────── */
/* Below 1200 the phases stop being a deck and stack down the page: a
   scroll-driven deck fights momentum scrolling on touch, and held to the
   1460 composition a 1024 laptop renders the type far too small. */
@media (max-width:1199.98px){
  .approach{height:auto !important;}
  .phase-stack{position:static; height:auto; overflow:visible;}
  .phase{
    position:relative;                 /* the plate is absolute against this */
    transform:none !important;
    display:block;
    padding-block:clamp(56px,9vw,110px);
  }
  .phase--0{padding:0;}
  .phase__inner{
    position:relative;                 /* the plate is absolute inside it */
    width:min(100%, var(--grid-max));
    margin-inline:auto;
    padding-inline:var(--grid-margin);
    aspect-ratio:auto !important;
    transform:none !important;
    container-type:normal;
  }
  .phase--0 .phase__inner{padding:0; aspect-ratio:16/11 !important;
                          justify-content:center;}   /* the mark centres, as on the board */
  /* Flex only so the numeral can lead the panel; children still stretch to the
     full width, as they did when this was block flow — align-items:flex-start
     would shrink every paragraph to its longest unbreakable word. */
  .phase__inner{display:flex; flex-direction:column;}
  .phase__inner > *{position:static; left:auto; width:auto;}
  /* The numeral is the panel's biggest mark. Held to the board it would be
     half the panel wide and land on the copy, so stacked it leads the panel at
     a size that keeps its own proportion — the same move the label makes. */
  .phase__mark, .phase__time{order:-1; margin-bottom:20px; align-self:flex-start;}
  .phase--1 .phase__time{width:52.09vw !important;}   /* 760.5 / 1460 */
  .phase--2 .phase__time{width:53.36vw !important;}   /* 779.0 */
  .phase--3 .phase__time{width:50.27vw !important;}   /* 734.0 */
  .phase--4 .phase__time{width:50.51vw !important;}   /* 737.5 */
  .phase--0 .phase__mark{width:20.21vw !important; align-self:center;}
  /* the plate covers the whole panel, and the panel's own padding is what
     holds the type off its edges. It is a child of .phase now, so inset:0
     already reaches the padding — the negative inset this used to need, back
     when the plate lived inside .phase__inner, is gone. */
  .phase__plate{position:absolute; inset:0;}
  /* `cover` crops the band differently at these shapes, so the flat black the
     board puts under its type is no longer guaranteed to be behind it — on
     phases 02 and 04 the photograph's highlights land straight under the copy.
     A scrim keeps the white heading and the lime copy off them. It is measured,
     not guessed: at .62 the worst ground under the 21px lime body composited to
     97 and gave 4.30:1, under the 4.5 that size needs. .72 takes it to 71 and 6.40:1.
     Measured at 768 over all four phases: white heading 9.23, lime 26px 6.40,
     lime 21px body 6.40, against thresholds of 3, 3 and 4.5. */
  .phase--1 .phase__plate::after,
  .phase--2 .phase__plate::after,
  .phase--3 .phase__plate::after,
  .phase--4 .phase__plate::after{
    content:""; position:absolute; inset:0; background:rgba(0,0,0,.72);
  }
  /* The label is a wide serif heading, and each one is a different width, so
     each keeps its own board proportion in vw rather than sharing a clamp.
     Below 1200 a vw value can never exceed what the board gives at 1200, so
     the two layouts meet with nothing to cap. */
  .phase__label{margin-bottom:18px;}
  .phase--1 .phase__label{width:28.01vw !important;}   /* 409.0 / 1460 */
  .phase--2 .phase__label{width:26.37vw !important;}   /* 385.0 */
  .phase--3 .phase__label{width:25.68vw !important;}   /* 375.0 */
  .phase--4 .phase__label{width:29.22vw !important;}   /* 426.5 */
  /* `.phase--N .phase__title` is two classes and beats `.phase__inner > *`, so
     the deck's cqw widths survived into this layout and squeezed the copy to
     24.66cqw -- 189px at 768, 96px at 390, four words a line. The label widths
     below already carry !important for exactly this reason; the title and body
     need it too. And with no container-query ancestor here, those cqw values
     were resolving against the viewport, which is what made them scale down
     with it. */
  .phase__title, .phase__body{width:auto !important;}
  .phase__title {font-size:max(16px, 1.7979vw); margin-bottom:10px;}
  .phase__body  {font-size:max(14px, 1.4384vw); line-height:1.4; max-width:44ch;}
  .phase__body--b{margin-top:.9em;}
}
@media (prefers-reduced-motion:reduce){
  .phase__inner{transform:none !important;}
}

/* ═══════════════════════════════════════════════════════════
   07 · ABOUT (the homepage teaser) — from "about us.psd"
   (2870 × 1638 @2x → 1435 × 819, the hero's board again).

   A NEW BOARD (2026-08-28) puts the whole thing on a PHOTOGRAPH — a long-
   exposure light painting, the same frame the What we offer page now opens
   with. Nothing else moved: diffed against the board it replaces, the only
   change is the ground, and every type layer sits at exactly the same rect.
   So the trace and both positions below are the ones already measured, and
   the section simply gained a plate.

   Centred, calm. The statement runs 281.5 → 437 and
   "About Us" sits with its baseline at 537; that group's
   centre is 409.25 against a board centre of 409.5, so it is
   simply centred rather than positioned.

   Centred is a grid position — the block spans the content
   column and is centred in it. The statement is traced art at
   558 wide; the nearest span is 3 columns (517.1), 7.9% off,
   which is past the 5% the section-00 rule allows, so it keeps
   its drawn width. Its link now opens about.html.
   ═══════════════════════════════════════════════════════════ */
:root{
  /* one continuous curve, same shape as the hero headline: 610px on the
     board, ~86vw on a phone, the line through both in between. (558 until
     2026-09-01 — the new board resets the statement bigger and higher.) */
  --about-w:min(610px, calc(232.92px + 26.2775vw), 86vw);
  /* Both pills on about-us.psd are the same 174.5 × 54.5 box on a 25.5 radius,
     whatever the label. --pill scales the three together where a section sets
     a smaller label, so the board's proportions survive the reflow. */
  --pill:1;
}

.about{
  position:relative;
  background:var(--black);
  min-height:100svh;
  display:grid;
  place-items:center;
  padding:80px var(--grid-margin);
  text-align:center;
  overflow:hidden;
}
/* Full-bleed, like the Approach deck: the type is drawn live and the picture
   is only ground, so it covers the whole section instead of being held to the
   board's 1435 and letterboxed. The type was lifted out of the plate by
   compositing the layer stack without it (tools/psd_compose.py), exact to
   max |d| 0.9 of 255. */
.about__plate{position:absolute; inset:0; z-index:0;}
/* This one drifts both ways — ±4% of its own height as the section crosses
   the viewport, DOWN as the page goes down, which is the reading the hero
   cannot have. It can because nothing here is a board framing: `cover` has
   always cropped this picture differently at every window, so the 1.10 that
   buys the headroom costs nothing that was ever guaranteed. 4 of the
   available 5 each way; the scrim below is re-measured across the travel. */
.about__plate img{
  width:100%; height:100%; object-fit:cover; display:block;
  transform:translate3d(0, var(--par, 0%), 0) scale(1.10);
  transform-origin:50% 50%;
  will-change:transform;
}
/* A measured scrim, and a light one. On the board the type sits over the dark
   top of the frame and needs nothing, but `cover` crops to the viewport, and a
   short wide window (1024 × 640 is the worst of eight sampled) lifts the fire
   trails up under the "About Us" link: white on them is 4.17:1, under the 4.5
   that size needs. .20 takes it to 5.99 and is barely visible on the picture.
   The statement never drops below 20:1 — it is the link, 231px lower, that
   this is for. */
.about__plate::after{
  content:""; position:absolute; inset:0; background:rgba(0,0,0,.20);
}
.about__inner{position:relative; z-index:1; display:grid; justify-items:center;}

/* Traced — the statement is a fixed string in the studio's Fenway, which has
   no font file here. PSD ink 1220 × 327 @2x → 610 × 163.5, traced off the
   layer's own alpha and graded against it: shape error 0.00%. */
.about__line{margin:0; line-height:0;}
.about__line svg{
  display:block;
  width:var(--about-w);
  height:auto;
  color:var(--white);
}

/* The board draws the pill itself now, so both numbers are box-to-box rather
   than ink-to-ink and no baseline correction is needed: the statement's SVG
   box IS its ink (line-height 0, svg display:block) and a pill's border box
   IS its ink. Statement foot 410.0 → pill top 443.25 = 33.25, spent as a
   fraction of the statement (33.25 / 610 = 5.45082%) so it holds the board's
   proportion all the way down instead of stepping at a breakpoint.

   Vertically the group is NOT the board's: its ink runs 246.5 → 497.75, whose
   centre is 372.1 against a board centre of 409.5, i.e. 37.4 high. The section
   is min-height:100svh, so the 819 board's own vertical placement is already
   an approximation here — the group stays centred, as it was when the board
   did centre it. Worth re-reading if the section ever gets a fixed height. */
.about__cta{
  --btn-h:calc(54.5px * var(--pill));
  --btn-r:calc(25.5px * var(--pill));
  --btn-w:calc(174.5px * var(--pill));
  /* the board sets it in caps; the markup keeps a normal word so the
     accessible name is not five spelled-out letters */
  text-transform:uppercase;
  margin-top:calc(var(--about-w) * 0.0545082);   /* 33.25 / 610 */
}

@media (max-width:767.98px){
  .about{min-height:auto; padding:96px 20px;}
  .about__cta{--btn-size:17px; --pill:.85;}
}

/* ═══════════════════════════════════════════════════════════
   09 · LET'S TALK (the homepage contact block)
   — from "about us.psd" (2026-09-01), the lime artboard of the
   board the About teaser above now shares: y 1538 → 2919 @2x,
   i.e. 1435 × 690.5 CSS, the same band contact.psd drew.

   The About teaser inverted: lime ground, black ink. Nothing
   on it is positioned. Every run's ink centre lands within
   4.5px of the board's own 717.5, so the block is simply
   centred, which is what About does too.

   THE BOARD'S STATEMENT CARRIES TWO TYPOS — it reads "10:30 is
   tcloser than you think! Got a business that's ready to
   move?." Saad supplied the corrected line as artwork
   (source/talk-line-corrected.png, 1315 × 326 @2x, the same
   setting 23 raw px narrower) and that is what is traced here;
   the PSD layer is not. Graded against the supplied alpha:
   shape error 0.00%. If the PSD is ever re-sent fixed, retrace
   from the layer and delete the PNG.

   The copy also changed with this board — "brand" → "business",
   and the full stop after "think" is now an exclamation.
   ═══════════════════════════════════════════════════════════ */
:root{
  /* the hero headline's curve again: the drawn 657.5 at 1435, ~86vw on a
     phone, the line through both in between. (803.5 until 2026-09-01.) */
  --talk-w:min(657.5px, calc(215.19px + 30.823vw), 86vw);
  /* PSD ink 176.5 × 18 and 260.5 × 23 — see the note on the size below */
  --talk-detail:clamp(14px, 1.5331vw, 22px);
}

.talk{
  background:var(--accent);
  color:var(--black);
  /* The board's band is 690.5 of 1435 — 48.1185% — and it is a floor, not a
     height: it holds the board's proportion at 1435 and above, and below that
     it falls away under the block's own height rather than stepping at a
     breakpoint the way a flat 690.5 would (441 → 690.5 across 767/768). */
  min-height:min(48.1185vw, 690.5px);
  display:grid;
  place-items:center;
  padding:96px var(--grid-margin);
  text-align:center;
}
/* The board no longer centres this group and this section, unlike the About
   teaser above, is short enough to say so: min-height holds the board's own
   690.5 : 1435, so 179 → 557.5 is a real position. The group's ink centre is
   368.25 against a band centre of 345.25 — 23 low. A one-sided margin biases
   the centring by half of what it adds, so 46 is the 23; it is a fraction of
   the statement like every other gap here, and it simply fades out if the
   block ever grows past the band. */
.talk__inner{
  display:grid;
  justify-items:center;
  margin-top:calc(var(--talk-w) * 0.069962);   /* 2 × 23 / 657.5 */
}

/* Traced, like the About statement — Fenway Banner CompressedLight, which has
   no font file here. Cut from the corrected artwork rather than the board:
   1315 × 326 @2x → 657.5 × 163. */
.talk__line{margin:0; line-height:0;}
.talk__line svg{
  display:block;
  width:var(--talk-w);
  height:auto;
}

/* The pill on the lime ground, where the hover fill IS the paper — the black
   hairline this section restores is what carries the shape there, at 14.56:1.
   The black sections do the same thing in reverse. See MEASUREMENTS §09,
   "One of the two states is always an outline". */
.talk__cta{
  /* The same box the About pill takes, and the same box-to-box measurement:
     statement foot 342.0 → pill top 372.25 = 30.25, spent as a fraction of the
     statement (30.25 / 657.5 = 4.60076%) so it holds the board's proportion
     rather than stepping at a breakpoint.

     THE LABEL IS SAAD'S, NOT THE BOARD'S. about-us.psd draws "Let's Talk" in
     BDO Bold at 48 @2x (= 24 CSS at the named size), which would have made it
     the one pill on the site in a different weight AND a different size from
     every other. He asked for "Let's Create" instead, set exactly as the
     ABOUT pill above it: BDO Regular 20 on −10 tracking. So this placement
     now takes the component's own type and overrides nothing but the box —
     no --btn-size, no font-weight. The board's 174.5 still wins the width:
     "Let's Create" at 20 comes to 162.0 including the component's padding.

     The BOX is still the board's, and it is the same on both artboards
     whatever the label. */
  --btn-h:calc(54.5px * var(--pill));
  --btn-r:calc(25.5px * var(--pill));
  --btn-w:calc(174.5px * var(--pill));
  text-transform:uppercase;                     /* as ABOUT, his call */
  margin-top:calc(var(--talk-w) * 0.0460076);   /* 30.25 / 657.5 */
}

/* Phone and address: BDO Regular, the file's 44 @2x → 22, which is what the
   About page sets the same two lines at. Backed out of the new board the same
   way: the layer's ink is 353 × 36 and 521 × 46 @2x, against 336.25 × 33 and
   502.25 × 43.25 rendered at 44 — both widths land on 1.05, which is the
   scale every board here carries. Baselines 34.13 and 102.75 @2x, i.e. 34.31
   CSS apart, so the 1.5455 the old board gave is still the pitch. */
@media (max-width:767.98px){
  .talk__cta{--btn-size:17px; --pill:.85;}
}

.talk__detail{
  /* Pill foot 426.75 → the phone's ink top 501.5 = 74.75, as a fraction of the
     statement (74.75 / 657.5 = 11.3688%). What comes off it is the 7.60 the
     first line's ink sits below the paragraph's own box top — half-leading
     6.253 plus the 16.25 ascent, less the digits' 32.50/2 cap offset — and
     that part is flat px, because the type does not scale with the board.
     The negative foot is the matching correction at the other end: the last
     line box hangs 6.02 below the email's descender, and centring a box that
     is not symmetric about its ink pulls the group up by half of it. */
  margin:calc(var(--talk-w) * 0.113688 - 7.60px) 0 -0.27em;   /* 74.75 / 657.5 */
  font-size:var(--talk-detail);
  line-height:1.5455;
}

/* The board sets these two as plain strings, not the two-faced boxes the bar
   uses, so they fade rather than flip. Same treatment .offer-contact__plain
   gives the same two lines on about.html; worth folding into one modifier if
   a third ever appears. They deliberately do NOT carry .link — nothing here
   needs the flip machinery, and .link's white would have to be undone. */
.talk__plain{
  display:inline-block;
  color:inherit;
  text-decoration:none;
  transition:opacity .3s var(--flip-ease);
}
@media (hover:hover){ .talk__plain:hover{opacity:.55;} }

/* The palette inverts here, so every mark that assumes a black ground has to
   be restated. The focus ring goes black — a lime ring on a lime field is
   invisible — and the flip's second face gives its colour up entirely.

   That last one is a departure from the open mobile panel, which is the only
   other lime field on the site and turns its second face white. White on
   #d0df83 measures 1.44:1. The panel gets away with it because it is a
   touch-only surface where :hover barely fires; this is a desktop hover that
   would fire constantly, so the accent swap is dropped and the tumble carries
   the affordance on its own. Say the word if you want the white anyway — it
   is one line — and the panel's own 1.44:1 is worth a look either way. */
.talk .link{color:var(--black);}
.talk .flip__face--back{color:inherit;}
/* Touch has no hover, so press fades rather than recolours — the same move the
   phone and address make, and the only legible one on this ground. The colour
   has to be restated as well as the opacity: the site-wide
   `.link:active .flip__face--front` puts the accent on the label, which on this
   ground would take the pressed state to lime on lime. */
.talk .link:active .flip__face--front{color:var(--black); opacity:.55;}
.talk :where(a,button):focus-visible{outline-color:var(--black);}

/* Only the side padding changes below the phone breakpoint — the band and both
   gaps are proportions of the statement, so they carry themselves down. */
@media (max-width:767.98px){
  .talk{padding:96px 20px;}
}

@media (prefers-reduced-motion:reduce){
  /* the flip is gone, and neither colour the site reaches for works here —
     accent on accent is no change, white on accent is 1.44:1 — so the fade
     the plain links use carries it instead */
  .talk .link{transition:opacity .3s var(--flip-ease);}
  @media (hover:hover){ .talk .link:hover{color:var(--black); opacity:.55;} }
  .talk .link:focus-visible,
  .talk .link:active{color:var(--black); opacity:.55;}
}

/* ═══════════════════════════════════════════════════════════
   08 · WHAT WE OFFER — about.html, from "what we offer.psd"
   A NEW BOARD (2026-08-28), 2920 × 7997 @2x → 1460 × 3998.5.

   The old board was 3474 wide with a 277px transparent margin
   and a stray "Lorem Ipsum" parked in it; this one is the
   board and nothing else, so a layer rect IS a board
   coordinate. (The Lorem Ipsum is still there, at x −277 —
   entirely off-canvas now, so it composites to nothing.)

   What changed, against the board it replaces:
     · the white paper is gone. The page is #0d0d0b — a flat
       near-black the board fills, NOT var(--black); sampled at
       three places, all exactly rgb(13,13,11).
     · the hero is a full-width PHOTOGRAPH, the same long-
       exposure frame the homepage About teaser now uses. It
       runs to y 1756.5 and the two service rows sit ON it.
     · there are TWO service rows, not three. "Brand
       Experience" is dropped from the board entirely; its
       capability column stays.
     · a new section — Our Clients & Partners, a serif heading
       and four logo cells.
     · the statement is LIME on the dark ground, where it was
       black on white; the capabilities are white on it.
     · the contact block is unchanged in everything but its y:
       the band moved up 167.5 and got 30.5 taller, and inside
       it only "Let's create" moved (down 52). Every other mark
       is within 1.5px of where it was, so the numbers below
       are the old ones nudged, not re-derived.

   Same idiom as before and as the Approach deck: a container-
   query board, everything placed in cqw, exact at 1460 and
   scaling as one piece under it. Vertical positions are BOX
   tops — the PSD ink less that line's ink offset — and the
   number in the comment is always the ink. Under 1200 the
   board is given up and the page reflows.
   ═══════════════════════════════════════════════════════════ */

/* The board's ground. Not var(--black): the file fills a flat rgb(13,13,11),
   which is what every dark band on this page is, photograph included where it
   fades out. */
.page-about{background:#0d0d0b; color:var(--white);}

/* The bar reads each section's data-ink, so the page no longer names colours
   for it. The one exception the old board needed — "Get in touch" alone
   staying white over the hero's dark plate while the rest of the bar sat on
   lime — is gone with the lime hero: the whole top of this page is dark now,
   and data-ink="light" covers it. */
html.menu-open .page-about .menu .link{color:var(--black);}

.offer-top,
.offer-statement,
.offer-caps{background:#0d0d0b;}

.offer-top__inner,
.offer-statement__inner,
.offer-caps__inner,
.offer-contact__inner{
  width:min(100%,1460px);
  margin-inline:auto;
  position:relative;
  container-type:inline-size;
}

/* the board is a fixed composition, so its parts are placed, not flowed */
@media (min-width:1200px){
  .offer-top__inner > *,
  .offer-statement__inner > *,
  .offer-caps__inner > *,
  .offer-contact__inner > *{position:absolute; margin:0;}
}

/* ── The photographic top ───────────────────────────────── */
/* Hero and both service rows share one ground.

   This plate is part of the COMPOSITION, not a band behind it — the opposite
   of the Approach deck, and for a measured reason. There the type sits on each
   band's flat black, so the picture can be cropped to any viewport and the
   marks stay legible. Here the type is placed against specific dark areas of a
   busy photograph, and decoupling the two breaks it: with the plate full-bleed,
   `cover` scales the frame up past 1460 and lifts the fire trails into the
   copy — row 1 measured 1.15:1 at 1920 and 1.01:1 at 2200, against the 4.5 that
   size needs. Anchoring the crop does not help (top anchoring is worse, 1.01),
   and no scrim short of .50 recovers it, which would flatten the picture.

   The content remains held to the 1460 board, while the photograph itself
   extends to the viewport edges so the hero reads as a true full-bleed image
   on wide screens. The centered crop keeps the composition aligned with the
   board at its reference width.

   The marks were lifted out of the plate by compositing the layer stack without
   them (tools/psd_compose.py), exact to max |d| 0.0 of 255. */
.offer-top{position:relative; overflow:hidden;}   /* the client row runs off the board on purpose */
.offer-top__inner{height:143.1507cqw;}          /* tightened after the client row */
.offer-top__plate{
  left:50%; top:0;
  width:100vw;
  height:123.2877cqw;             /* the plate image is 1800 board px */
  overflow:hidden;
  transform:translateX(-50%);
  z-index:0;
}
/* NO PARALLAX HERE ABOVE THE BREAKPOINT, and it was measured before it was
   dropped. This is the one plate on the site whose type is placed against
   SPECIFIC dark patches of a busy photograph. A vertical translate can pull
   the bright trails directly underneath the copy:
   glyph-masked at 1460 × 900, `.offer-top__body` reads 15.87:1 at rest and
   **1.70:1** at −2.5% of the plate's height, because 45px is all it takes to
   pull a fire trail under the copy. The drift lives in the reflowed layout
   below, where the plate is a banner with nothing on it. */
.offer-top__plate img{width:100%; height:100%; object-fit:cover; display:block;}
.offer-top__inner > *:not(.offer-top__plate){z-index:1;}

.offer-top__line{
  left:var(--grid-margin);        /* board 47.5 — column 1 */
  top:9.1096cqw;                  /* ink 133 — an SVG's box IS its ink */
  width:51.2671cqw;               /* drawn 748.5; 6.5% off 4 columns, so kept */
}
.offer-top__line svg{display:block; width:100%; height:auto;}
.offer-top__body{
  left:var(--grid-margin);        /* board 56.5 — column 1 */
  top:22.0233cqw;                 /* ink 328 */
  width:45.5137cqw;               /* drawn 664.5; 5.8% off 4 columns, so kept */
  font-size:1.7123cqw;            /* 25 */
  line-height:1.2;                /* pitch 30 */
  font-weight:500;
}

/* ── The two service rows ───────────────────────────────── */
.offer-row__head{
  position:absolute; margin:0;    /* an h2 arrives with UA margin */
  left:var(--grid-margin);        /* board 51 / 48 — column 1 */
  font-size:1.7123cqw;            /* 25 */
  line-height:1;
  font-weight:700;
}
.offer-row__rule{
  position:absolute; margin:0;    /* an hr arrives with 8px of it */
  left:var(--grid-margin);        /* board 48.5 / 47.5 — column 1 */
  width:calc(7 * var(--gcol) + 6 * var(--grid-gutter));   /* drawn 1362 — all seven columns */
  height:1px; border:0;
  background:currentColor;
}
.offer-row__a,
.offer-row__b{
  position:absolute;
  width:calc(2 * var(--gcol) + 1 * var(--grid-gutter));   /* drawn 342 / 367.5 — 2 columns */
  font-size:1.7123cqw;            /* 25 */
  line-height:1.2;                /* pitch 30 */
  font-weight:500;
}
.offer-row__a{left:calc(var(--grid-margin) + 3 * (var(--gcol) + var(--grid-gutter)));}   /* board 641.5 — column 4 */
.offer-row__b{left:calc(var(--grid-margin) + 5 * (var(--gcol) + var(--grid-gutter)));}   /* board 1036.5 — column 6 */
/* the board breaks these lines itself; that is a break, not a wrap */
.offer-row__line{display:block;}

/* Ink: heads 1032 / 1344, rules 1070.5 / 1380.5, copy 1094 / 1427.5. Row 1's
   two copy columns are drawn 5.5 apart (1094 and 1099.5) where row 2's are
   1.5; the odd one is normalised to its own row's first column, as the third
   board's row spacing was. */
.offer-row__rule--1{top:73.3219cqw;}            /* ink 1070.5 */
.offer-row__head--1{top:70.4356cqw;}            /* ink 1032 */
.offer-row__a--1,
.offer-row__b--1{top:74.4890cqw;}               /* ink 1094 */

.offer-row__rule--2{top:94.5548cqw;}            /* ink 1380.5 */
.offer-row__head--2{top:91.8055cqw;}            /* ink 1344 */
.offer-row__a--2,
.offer-row__b--2{top:97.3315cqw;}               /* ink 1427.5 */

/* ── Our Clients & Partners ─────────────────────────────── */
/* The block is its own element so the scroll driver in site.js has something
   to measure its travel against, and so the row can overflow it. It runs from
   the grid margin to the section's right edge, which is the window the track
   shows through — the board bleeds this row, so it is `right:0`, not the grid. */
.offer-clients{
  left:var(--grid-margin);        /* board 49 — column 1 */
  right:0;
  top:118.1849cqw;                /* heading ink 1725.5 */
  height:22.8082cqw;              /* to the cells' foot at 2058.5 */
}
.offer-clients__line{
  position:absolute; margin:0;
  left:0; top:0;
  width:27.8425cqw;               /* drawn 406.5; 27% off 3 columns, so kept */
}
.offer-clients__line svg{display:block; width:100%; height:auto;}

/* The board draws four equal cells running OFF the right edge — four at the
   drawn size need 1556 against a 1460 board, so the last is cut about 60%
   through. That is the board saying the row continues, and the list is
   content/clients.json now, so it can be any length. site.js drives the track
   sideways as the block crosses the viewport: every logo comes past, and the
   distance is always exactly the row's own overflow. Nothing moves when they
   already fit. */
.offer-clients__row{
  position:absolute; margin:0;
  left:0; top:8.3219cqw;          /* cells 1847 */
  list-style:none; padding:0;
  display:flex;
  gap:1.3699cqw;                  /* 20 */
  width:max-content;              /* the track is as wide as its logos */
  will-change:transform;
  transition:none;
}
.offer-clients__cell{
  flex:0 0 auto;
  width:25.6336cqw;               /* 374.25 */
  height:14.4863cqw;              /* 211.5 */
  display:grid;
  place-items:center;
  /* The board draws this at half a CSS pixel — one device pixel at @2x, lime
     at 46.7% over the ground. No border-width reproduces that exactly; 1px at
     the drawn colour is the closest that renders the same on both densities. */
  border:1px solid rgba(208,223,131,.47);
}
.offer-clients__cell img{
  display:block;
  width:var(--logo-w, 75%);       /* each logo's own drawn width, from the JSON */
  height:auto;
}
.offer-clients__link{display:block; width:100%; text-align:center;}
.offer-clients__link img{margin-inline:auto;}

/* Hand-scrollable instead of driven, when motion is not wanted. */
@media (prefers-reduced-motion:reduce){
  .offer-clients{overflow-x:auto; overscroll-behavior-x:contain;}
  .offer-clients__row{position:relative; top:auto; margin-top:8.3219cqw;}
}

/* ── The statement ──────────────────────────────────────── */
/* Lime now, not black — the ground went the other way. */
.offer-statement__inner{height:15.8219cqw;}     /* compact bridge into capabilities */
.offer-statement__line{
  left:var(--grid-margin);        /* board 46 — column 1 */
  top:3.4247cqw;
  width:calc(5 * var(--gcol) + 4 * var(--grid-gutter));   /* drawn 909.5, within 2% of 5 columns */
  color:var(--accent);
}
.offer-statement__line svg{display:block; width:100%; height:auto;}

/* ── Capabilities ───────────────────────────────────────── */
.offer-caps__inner{height:27.7397cqw;}
/* a 394 track at 50.5 / 445 / 838 — two columns each, at 1, 3 and 5 */
.offer-caps__col{position:absolute; top:0; width:calc(2 * var(--gcol) + 1 * var(--grid-gutter));}
.offer-caps__col--1{left:var(--grid-margin);}
.offer-caps__col--2{left:calc(var(--grid-margin) + 2 * (var(--gcol) + var(--grid-gutter)));}
.offer-caps__col--3{left:calc(var(--grid-margin) + 4 * (var(--gcol) + var(--grid-gutter)));}
.offer-caps__head{
  position:absolute; margin:0;
  top:2.7397cqw;
  font-size:1.7123cqw;            /* 25 */
  line-height:1;
  font-weight:700;
}
.offer-caps__list{
  position:absolute; margin:0; padding:0;
  top:5.5431cqw;
  list-style:none;
  font-size:1.2329cqw;            /* 18 */
  font-weight:500;
}
.offer-caps__list li{line-height:1.4444;}       /* pitch 26 on 18 */

/* ── Contact ────────────────────────────────────────────── */
.offer-contact{background:var(--accent); color:var(--black);}
.offer-contact__inner{height:70.5479cqw;}
.offer-contact__say{position:static !important;}

.offer-contact__line{
  /* not a direct child of the inner, so the block reset above misses it and
     the h2 keeps its UA margin */
  position:absolute; margin:0;
  left:var(--grid-margin);        /* board 47.5 — column 1 */
  top:5.7192cqw;                  /* ink 2990 */
  width:55.1370cqw;               /* drawn 805.5; 9% off a span, so kept */
}
.offer-contact__line svg{display:block; width:100%; height:auto;}

.offer-contact__cue{
  position:absolute; margin:0;
  left:var(--grid-margin);        /* board 54 — column 1 */
  top:24.6575cqw;
  font-size:2.5137cqw;            /* 36.7 */
  line-height:1.1308;             /* pitch 41.5 */
  font-weight:700;
}
.offer-contact__detail{
  position:absolute; margin:0;
  left:var(--grid-margin);        /* board 51.5 — column 1 */
  top:32.8561cqw;
  font-size:1.5068cqw;            /* 22 */
  line-height:1.4773;             /* pitch 32.5 */
}
.offer-contact__label{
  position:absolute; margin:0;
  left:var(--grid-margin);
  top:43.5109cqw;
  font-size:1.5068cqw;
  line-height:1;
  font-weight:700;
}
.offer-contact__socials{
  position:absolute; margin:0;
  left:var(--grid-margin);
  top:46.1266cqw;
  font-size:1.5068cqw;
  line-height:1;
  display:flex;
  gap:0;
}
/* The board draws two — Instagram at 53 and "Linked in" at 227.5, i.e. columns
   1 and 2 — and LinkedIn was dropped on 2026-09-01 (README 6). With one link
   the column machinery has nothing to space, so the row is just its own width
   at the left margin, which is where column 1 already puts it. Restore the
   three rules below if a second social ever comes back:
     width:calc(2 * var(--gcol) + 1 * var(--grid-gutter));
     .link{display:inline-block; width:calc(var(--gcol) + var(--grid-gutter));}
     .link:last-child{width:auto;}                                            */
.offer-contact__socials .link{display:inline-block;}
.offer-contact__where{
  position:absolute; margin:0;
  left:var(--grid-margin);
  top:50.5314cqw;
  font-size:1.5068cqw;
  line-height:1;
  font-weight:700;
}
/* live links, so they take a hover — but the board sets them as plain strings,
   not the two-faced boxes the nav uses, so the flip would be wrong here */
.offer-contact__plain{
  color:inherit; text-decoration:none;
  transition:opacity .3s var(--flip-ease);
}
@media (hover:hover){ .offer-contact__plain:hover{opacity:.55;} }
.offer-contact__plain:focus-visible{outline:2px solid var(--black); outline-offset:3px;}

/* ── The form ───────────────────────────────────────────── */
.offer-form{
  position:absolute;
  left:calc(var(--grid-margin) + 3 * (var(--gcol) + var(--grid-gutter)));                        /* was 648 — column 4 */
  top:27.5685cqw;                 /* 3476.5 */
  width:calc(4 * var(--gcol) + 3 * var(--grid-gutter));                       /* was 753.5 — columns 4 to 7 */
  /* its own container: a field's 65.5 height has to resolve against the
     753.5 form, not the 1460 board, or it comes out at 126.9 */
  container-type:inline-size;
}
.offer-form__pair{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:var(--grid-gutter);         /* the board's 34.5 becomes a real gutter */
  margin-bottom:3.3433cqw;        /* field foot 3542 → 3565.5 */
}
.offer-form__field{display:block;}
/* the direct-child combinator matters: without it this also fires on Email,
   which is the second field inside the pair, and drops it half a field */
.offer-form > .offer-form__field + .offer-form__field{
  margin-top:3.7702cqw;           /* subject foot 3631 → message 3657.5 */
}

.offer-form input,
.offer-form textarea{
  display:block; width:100%;
  height:9.3186cqw;               /* 65.5 */
  border:0;
  border-radius:3.8412cqw;        /* 27, now of a 4-column form */
  background:var(--white);
  color:var(--black);
  font:inherit;
  font-size:3.1298cqw;            /* 22 */
  padding:0 3.0cqw;
  outline-offset:2px;
}
.offer-form textarea{
  height:42.2535cqw;              /* 297, now of a 4-column form */
  padding:2.6cqw 3.0cqw;
  resize:vertical;
  line-height:1.45;
}
.offer-form ::placeholder{color:var(--black); opacity:1;}
.offer-form input:focus-visible,
.offer-form textarea:focus-visible{outline:2px solid var(--black);}

/* out of the flow: the board's spacing is measured, and a status line that
   grows when it has something to say would push the button off its mark */
.offer-form__note{
  position:absolute;
  left:0; right:0; top:100%;
  margin:1.4cqw 0 0;
  font-size:2.4cqw;
  line-height:1.3;
}
.offer-form__note.is-bad{font-weight:700;}

.offer-form__send{
  display:block;
  margin:2.3474cqw 0 0 auto;      /* message foot 3954.5 → 3971 */
  width:25.1102cqw;               /* 176.5, now of a 4-column form */
  height:7.4690cqw;               /* 52.5, now of a 4-column form */
  border:0;
  border-radius:3.5566cqw;        /* 25, now of a 4-column form */
  background:var(--black);
  color:var(--accent);
  font:inherit;
  font-size:3.5566cqw;            /* 25, now of a 4-column form */
  cursor:pointer;
  transition:background .3s var(--flip-ease), color .3s var(--flip-ease);
}
@media (hover:hover){
  .offer-form__send:hover{background:var(--white); color:var(--black);}
}
.offer-form__send:focus-visible{outline:2px solid var(--black); outline-offset:3px;}

/* ── Under 1200: the board stops being a board ───────────
   Held to the 1460 composition, a phone renders the body at
   6px — and even a 1024 laptop only gets a 12.6px capability
   list. So the board is given up below 1200, where its own
   body is still 20.5px: the placement is dropped and the page
   becomes an ordinary column. The photograph stops being a
   band behind the hero and becomes the hero's own ground, the
   rows put their heading, rule and both copy columns one under
   the other, the client cells fall to two across then one, the
   capability columns fall to one, and the form goes full
   width. Type stops scaling with the viewport and clamps
   instead — every ceiling below is that element's board size
   × 1200/1460, so the two layouts meet at the breakpoint
   rather than stepping.
   ─────────────────────────────────────────────────────── */
@media (max-width:1199.98px){
  .offer-top__inner > *,
  .offer-statement__inner > *,
  .offer-caps__inner > *,
  .offer-contact__inner > *,
  .offer-row__head, .offer-row__rule, .offer-row__a, .offer-row__b,
  .offer-caps__head, .offer-caps__list,
  .offer-contact__line, .offer-contact__cue, .offer-contact__detail,
  .offer-contact__label, .offer-contact__socials, .offer-contact__where{
    position:static !important;
    left:auto !important; top:auto !important; width:auto !important;
  }
  .offer-top__inner,
  .offer-statement__inner,
  .offer-caps__inner,
  .offer-contact__inner{
    height:auto !important;
    container-type:normal;
    padding-inline:var(--grid-margin);
  }

  /* the photograph becomes the hero's own ground rather than a band behind a
     fixed composition; the rows come off it onto the flat dark */
  .offer-top__plate{
    position:relative; height:auto;
    margin-inline:calc(-1 * var(--grid-margin));
    margin-bottom:clamp(40px,6vw,80px);
    transform:none;
  }
  .offer-top__plate img{aspect-ratio:1460/1100; height:auto;}
  /* Below the breakpoint the plate stops being a ground and becomes a banner
     — the type moves out from over it and sits underneath — so the drift the
     desktop layout cannot have is free here. ±3% on a 1.06 zoom. */
  .offer-top__plate img{
    transform:translate3d(0, var(--par, 0%), 0) scale(1.06);
    transform-origin:50% 50%;
    will-change:transform;
  }
  .offer-top{padding-block:0 clamp(56px,9vw,110px);}
  .offer-top__inner{padding-top:0;}
  .offer-top__line{width:min(100%,616px) !important; margin-bottom:clamp(18px,2.6vw,30px);}
  .offer-top__body{
    font-size:clamp(16px,1.7123vw,20.5px);
    max-width:52ch; margin-bottom:clamp(48px,7vw,90px);
  }

  .offer-row{margin-bottom:clamp(40px,6vw,72px);}
  .offer-row__head{font-size:clamp(17px,1.7123vw,20.5px); margin-bottom:clamp(10px,1.4vw,18px);}
  .offer-row__rule{margin:0 0 clamp(18px,2.6vw,34px); width:100% !important;}
  .offer-row__a{margin-bottom:clamp(14px,2vw,26px);}
  .offer-row__a, .offer-row__b{font-size:clamp(15px,1.7123vw,20.5px); max-width:56ch;}

  /* the block comes out of the composition but the row stays a track: it is
     still driven by scroll here, which is the whole point of it, and a fixed
     grid would either shrink the logos to nothing or run off the screen */
  .offer-clients{
    position:static !important; left:auto !important; top:auto !important;
    right:auto !important; height:auto !important;
    margin-inline:calc(-1 * var(--grid-margin));
    padding-left:var(--grid-margin);
    overflow:hidden;
  }
  .offer-clients__line{
    position:static !important; width:min(100%,334px) !important;
    margin:clamp(48px,7vw,90px) 0 clamp(20px,3vw,36px);
  }
  .offer-clients__row{
    position:static !important; top:auto !important;
    gap:clamp(12px,2vw,20px);
    padding-bottom:clamp(8px,1.5vw,16px);
  }
  .offer-clients__cell{
    width:clamp(196px, 42vw, 300px);
    height:auto; aspect-ratio:374/211.5;
    padding:clamp(10px,2vw,24px);
  }
  .offer-clients__cell img{width:var(--logo-w, 75%);}

  .offer-statement{padding-block:clamp(28px,4vw,56px);}
  .offer-statement__line{width:min(100%,748px) !important;}

  .offer-caps{padding-bottom:clamp(40px,6vw,76px);}
  .offer-caps__col{width:auto !important; margin-bottom:clamp(28px,4vw,48px);}
  .offer-caps__head{font-size:clamp(17px,1.7123vw,20.5px); margin-bottom:clamp(8px,1.2vw,14px);}
  .offer-caps__list{font-size:clamp(14px,1.2329vw,14.8px);}

  .offer-contact{padding-block:clamp(44px,7vw,84px);}
  .offer-contact__line{width:min(100%,662px) !important; margin-bottom:clamp(20px,3vw,38px);}
  .offer-contact__cue{font-size:clamp(22px,2.5137vw,30px); margin-bottom:clamp(14px,2vw,24px);}
  .offer-contact__detail{font-size:clamp(15px,1.5068vw,18px); margin-bottom:clamp(20px,3vw,34px);}
  .offer-contact__label{font-size:clamp(15px,1.5068vw,18px); margin-bottom:clamp(6px,1vw,10px);}
  .offer-contact__socials{width:auto !important; gap:clamp(18px,3vw,40px); margin-bottom:clamp(18px,3vw,30px);}
  .offer-contact__socials .link{width:auto !important;}
  .offer-contact__where{font-size:clamp(15px,1.5068vw,18px);}
  .offer-form{
    position:static !important;
    left:auto !important; top:auto !important; width:auto !important;
    margin-top:clamp(40px,6vw,72px);
    container-type:normal;
  }
  .offer-form input, .offer-form textarea{
    height:auto; min-height:clamp(48px,6vw,66px);
    border-radius:clamp(18px,3vw,28px);
    font-size:clamp(15px,1.5068vw,18px);
    padding:clamp(12px,1.6vw,18px) clamp(16px,2.2vw,26px);
  }
  .offer-form textarea{height:auto; min-height:clamp(180px,26vw,300px);}
  .offer-form__pair{margin-bottom:clamp(12px,2vw,24px);}
  .offer-form > .offer-form__field + .offer-form__field{margin-top:clamp(12px,2vw,24px);}
  .offer-form__note{position:static; margin:clamp(10px,1.6vw,18px) 0 0; font-size:clamp(13px,1.4vw,16px);}
  .offer-form__send{
    margin-top:clamp(16px,2.4vw,28px);
    width:clamp(140px,20vw,180px); height:clamp(44px,5.4vw,54px);
    border-radius:clamp(20px,3vw,26px);
    font-size:clamp(16px,2vw,25px);
  }
}

/* one column of copy, and the pair unstacks, on a narrow phone */
@media (max-width:479.98px){
  .offer-form__pair{grid-template-columns:minmax(0,1fr);}
  .offer-form__pair > .offer-form__field + .offer-form__field{margin-top:clamp(12px,2vw,24px);}
  .offer-clients__row{grid-template-columns:minmax(0,1fr) !important;}
}
