/* base.css and house.css are linked ahead of this file in index.html now, so
   the browser fetches all three at once instead of discovering them one by
   one. Cascade order is unchanged: house, then base, then this. */
/* The page ground IS the printed stock. base.css ships a slightly different
   cream for the older explorations, so the homepage pins its own. */
:root{--paper:#F5F0E4}
html{background:var(--paper)}     /* no flash before the sheet paints */
html,body{margin:0}
body{background:var(--paper)}

/* ---------- the page grows on a big monitor ---------- */
/* Jeff's standard is a body zoom of min(100vw/1400, 1.45) above 1400px. Chrome's
   `zoom` cannot be used here: it does NOT divide viewport units by the zoom, so
   100vw becomes 1.45 screens wide and every svh section height, the sticky hero
   and both fixed layers overflow (measured: at 2560 the document went 3712px
   wide and .lab-sticky 2030px tall inside a 1400px viewport).
   --u is the same scale expressed as a design pixel. Below 1400 --u is exactly
   1px and nothing moves; at 2560 it is 1.45px, so the wrap, the pads, the marks
   and every clamp ceiling are 1.45x, which is the zoom Jeff asks for, without
   the viewport units double-scaling.
   The invariant that makes it ONE factor rather than a different one per
   element: in every clamp(min, Xvw, C * --u) the ceiling C is exactly 14X, the
   value that vw term renders at 1400px. Set C any higher and the vw term stays
   unclamped past 2030px and that element keeps growing at the raw viewport rate
   (measured before this was tightened: at 2560 the h1 was 1.56x while the
   eyebrow was 1.42x, so the page did not scale, it drifted). With C = 14X the
   size is 14X * min(100vw/1400, 1.45) exactly, and every element lands on the
   same factor. Nothing below 2030px changes, because below it the vw term is
   the smaller of the two either way. */
:root{--u:clamp(1px, calc(100vw / 1400), 1.45px);
      --wrap:calc(1240 * var(--u));
      --nav-wrap:calc(1500 * var(--u));
      --pad:clamp(20px,3.6vw,calc(50.4 * var(--u)));
      --nav-h:calc(96 * var(--u))}

/* ---------- the bar's height, stated rather than measured ----------
   Two things below the fold have to start under the fixed bar: the stacked
   reel's pinned picture and the closing headline. works.js measures the bar and
   publishes it, which is exact but arrives after first paint, and anything that
   moves the layout when it lands is a layout shift (measured: 0.046 against a
   0.027 baseline, all of it the footer's padding jumping when the number
   arrived). So the height is also derivable here, from the three things that
   actually set it, and the JS value only ever confirms it:
     under 600  two rows, both 44, 8px between them, 13+11 of padding, and the
                zero-height break is its own flex line, so a second 8: 128 flat
     600 - 760  one row of 44 (base.css stands the lockup down at 760) in 2 pads
     761 - 900  the same row with the full 78px lockup
   Checked against the rendered bar at 320/390/480/599/600/700/768/820/900:
   128, 128, 128, 128, 87.2, 94.4, 133.3, 137, 142.8, exact at every step. */
@media (max-width:900px){ :root{--bar-h:calc(2 * var(--pad) + 84px)} }
@media (min-width:761px) and (max-width:900px){ :root{--bar-h:calc(2 * var(--pad) + 78px)} }
@media (max-width:599px){ :root{--bar-h:calc(2 * var(--pad) + 80px)} }

/* text carried for screen readers and crawlers, out of the picture */
/* `clip`, not `hidden`, for the same reason as the hero's stage: these carry a
   full sentence of description inside a 1px box, and `hidden` would make each
   one a touch-pannable scroll container holding up to 1200px of text. */
.vh{position:absolute;width:1px;height:1px;margin:0;padding:0;overflow:clip;
  /* margin:-1px is the classic recipe, but it pushes the box a pixel past
     the left edge; the clip-path below already hides it, so the offset is
     only a way to widen the page. */
  left:0;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

/* ---------- the sticky stage ---------- */
/* longer, so the client work has room to be read one mark at a time */
/* z-index 0, not auto: the hero's own layers carry z 5 and 6, and with no
   stacking context here they outranked the works section's z 4 entirely, so
   the closing title painted OVER the first project on the way back up. */
.lab{position:relative;z-index:0;height:850svh;background:var(--paper)}
/* The phone's stage, third pass: 980svh measured out to ~7.6 screens of
   scrolling before OUR WORK, which is a commitment no thumb makes. 590svh
   is ~4.2 screens — and the beat FRACTIONS are retimed in
   home.js so the cut comes out of the transitions while the readable
   moments (the deck's dwell, the standing crowd) keep their share. */
@media (max-width:900px){
  .lab{height:590svh}
}
/* a scroll target, nothing more: parked at the beat where the deck names
   what the studio does */
.lab .jump{position:absolute;top:52%;left:0;width:1px;height:1px}
/* `clip`, not `hidden`. Both hide the overflow, but `hidden` makes this a
   scroll container, and on iOS a scroll container can be PANNED by touch even
   when it shows no bars: the deck is translated up to a screen and a half
   outside this box on its way in and out, so a finger could drag the whole
   hero sideways. `clip` never creates a scroll container, so there is nothing
   to pan. This is the horizontal wiggle. */
.lab-sticky{position:sticky;top:0;height:100svh;overflow:clip}
/* svh is the SMALLEST viewport: on a phone, once the URL bar retracts the
   real viewport is ~100px taller than the stage and a strip of raw paper
   stood under the hero — with the scroll cue sitting in it, it read as the
   cue having a bar of its own. lvh, not dvh: dvh RESIZES the box as the bar
   slides and the GL buffer cannot reliably follow it on iOS, which printed
   FORWARD stretched. lvh is a constant, so the canvas never resizes; the
   only cost is ~100px of stage below the fold at the resting top of the
   page, before the bar has retracted — and every vh figure the shrink and
   grow beats are solved in equals lvh, so their geometry stays exact. */
@media (max-width:900px){
  /* --lvh is the largest viewport lab.js has actually measured, published
     because some iOS versions resolve the lvh UNIT short of the true
     retracted-bar viewport; the unit is only the first paint's fallback */
  .lab-sticky{height:100lvh;height:var(--lvh, 100lvh)}
  .works-rail{height:100dvh}   /* the drift grains reach the true bottom edge */
  /* the cream copy of the growing title is rebuilt at the stage's own height
     inside the clip; left at svh its centre sat half a bar above the slate
     copy's and the title doubled during the grow */
  .grow-clip .grow-front{height:100lvh;height:var(--lvh, 100lvh)}
}
#scene{position:absolute;inset:0;z-index:1}
#scene canvas{display:block;width:100%;height:100%}

/* The cream page eats the blue plate from its edges at the end of the stage,
   so the section narrows and hands off to Our Work rather than just stopping.
   No background of its own: the ring of cream is the box-shadow outside it. */
/* The cream does not just frame the card, it closes right in on it: by the end
   the stage is a panel rather than a near-full-bleed slab, so the page beneath
   is visible well before the card has finished leaving. Measured against the
   closing line, which is 546px wide at this height, so 710px of card still
   holds it with 80px either side. */
/* The cream closes all the way in: by the end the card is a point and the whole
   sheet is paper. --gi is the inset, shared with the clip below so the two can
   never drift apart. */
/* on body, not :root: --shp is set on the body element, and a custom property
   declared on the root cannot see a value that lives below it */
body{--gi-y:calc(var(--gsh,0) * 50vh); --gi-x:calc(var(--gsh,0) * 50vw)}
.shrink{position:absolute;pointer-events:none;z-index:5;
  inset:var(--gi-y) var(--gi-x);
  border-radius:calc(var(--gsh,0) * 40px);
  box-shadow:0 0 0 100vmax var(--paper)}

/* ---------- the title growing out of the card ---------- */
/* Above the cream frame, not below it. Below, the frame's box-shadow painted
   over BOTH copies everywhere outside the card and only the cream one inside it
   ever showed. Above, the slate copy covers the whole stage and the clipped
   cream copy sits on top of it exactly where the card is. */
.grow{position:absolute;inset:0;z-index:6;pointer-events:none;
  /* It belongs to the closing beat, not to the hero. Without this the cream
     copy is on screen from the first frame, since at rest the card is the whole
     viewport and the clip therefore covers everything. Arrives with the first
     of the shrink and is fully there by a seventh of it. */
  opacity:var(--gop,0)}
.grow-back,.grow-front{position:absolute;inset:0;display:grid;place-content:center;
  justify-items:center;text-align:center;padding-inline:var(--pad);
  transform:scale(var(--gscale,0.16));
  transform-origin:50% 50%}
/* clipped to the card exactly, so the cream copy exists only on the blue */
.grow-clip{position:absolute;inset:var(--gi-y) var(--gi-x);overflow:clip;
  border-radius:calc(var(--gsh,0) * 40px)}
/* and inside the clip the stage is rebuilt at its true size, pushed back by the
   inset, so the two copies land on exactly the same pixels */
.grow-clip .grow-front{inset:calc(-1 * var(--gi-y)) calc(-1 * var(--gi-x));
  width:100%;height:100svh}
.grow-back .wo-brow,.grow-back h2{color:var(--slate)}
.grow-back .wo-say{color:#5B6470}
.grow-front .wo-brow,.grow-front h2,.grow-front .wo-say{color:#F5F0E4}
.grow h2{margin:0;font-family:var(--dcond);text-transform:uppercase;
  letter-spacing:var(--track);line-height:.86;
  font-size:clamp(64px,12.4vw,calc(173.6 * var(--u)))}
/* arrives after the title has landed, not with it */
.grow .wo-say{margin:clamp(16px,2.4vh,30px) 0 0;max-width:46ch;
  font-size:clamp(15px,1.35vw,calc(18.9 * var(--u)));line-height:1.5;
  opacity:var(--gsay,0);transition:opacity .1s linear}

/* The closing line, on the card and cropped by it as the cream closes in.
   It scrolls through at page speed rather than parking, and the rule under it
   grows downward on the same clock, pointing into Our Work. */
.l-next{position:absolute;inset:0;z-index:4;pointer-events:none;
  display:flex;align-items:center;justify-content:center;
  opacity:var(--nxo,0);transform:translateY(var(--nty,120vh))}
.l-next p{position:relative;margin:0;font-family:var(--dcond);text-transform:uppercase;
  letter-spacing:var(--track);line-height:.86;color:#F5F0E4;
  font-size:clamp(48px,8.6vw,calc(120.4 * var(--u)));text-align:center;white-space:nowrap}
/* absolutely placed, so the rule growing never nudges the type */
.l-next p::after{content:'';position:absolute;left:50%;top:calc(100% + 30px);
  width:2px;height:calc(var(--ntl,0) * 1px);background:#F5F0E4}

/* ---------- copy layers, each owned by a beat ---------- */
.layer{position:absolute;inset:0;z-index:3;pointer-events:none;
  display:flex;padding:calc(var(--nav-h,110px) + 2vh) var(--pad) 8vh;
  opacity:0;transition:opacity .5s ease, transform .6s cubic-bezier(.2,.7,.2,1)}
.layer > *{pointer-events:auto}

/* "Work that moves campaigns": sits over the crowd while it assembles */
/* symmetric padding, so centred really is centred */
.l-headline{align-items:center;justify-content:center;padding-top:0;padding-bottom:0}
.l-headline h1{margin:0 auto;font-family:var(--dcond);text-transform:uppercase;
  line-height:.84;letter-spacing:var(--track);color:#141A35;
  font-size:clamp(55px,11.6vw,calc(162.4 * var(--u)));max-width:100%;text-align:center;
  white-space:nowrap}
/* Caps here, against the usual rule, because this site's logotype and nav are
   set in bold caps: the eyebrow mirrors the wordmark rather than fighting it.
   The deep orange, not the bright: #F2762B reads 2.9:1 on the cream and the
   deep cut of the same colour holds the line legible at this size. */
.l-headline .hstack{display:flex;flex-direction:column;align-items:center;
  gap:clamp(10px,1.1vw,calc(15.4 * var(--u)));
  position:relative;top:calc(-20 * var(--u))}
/* the auto margins would recentre it inside the flex column and lose the
   right edge the eyebrow is hung off */
.l-headline .hstack h1{margin:0}
/* the eyebrow: orange type straight on the page, lifted off the grain by a
   1px white shadow rather than by a chip. */
.brow{margin:0;font-family:var(--dtext);font-weight:700;text-transform:uppercase;
  font-size:clamp(14px,1.15vw,calc(16.2 * var(--u)));letter-spacing:.1em;
  line-height:1.15;color:var(--signal);white-space:nowrap;
  text-shadow:1px 0 0 #F5F0E4,-1px 0 0 #F5F0E4,0 1px 0 #F5F0E4,
    0 -1px 0 #F5F0E4,
    0 0 4px rgba(245,240,228,.6),0 0 10px rgba(245,240,228,.5),
    0 0 20px rgba(245,240,228,.4),0 0 34px rgba(245,240,228,.3);
  /* a soft cream pool, wide and quiet: translucent layers by request,
     the one sanctioned exception to the flat-colour rule */
  -webkit-text-stroke:.55px var(--signal);   /* true thickening, not faux bold */
  display:inline-block;align-self:center}
.brow span{padding:0 .5em;color:#141A35;text-shadow:none;-webkit-text-stroke:0}

/* the services line floats halfway between the headline's foot and the
   SCROLL cue, centred on the page rather than hanging off the headline */
.hero-deck{position:absolute;left:0;right:0;top:76vh;transform:translateY(-50%);
  margin:0 auto;max-width:44ch;text-align:center;
  font-family:var(--body);font-weight:500;
  font-size:clamp(15px,1.3vw,calc(18.2 * var(--u)));line-height:1.5;
  color:var(--slate)}
.hero-deck b{font-weight:700;color:#1E88A6}
/* the hold: printed at full strength until the reader leaves the hero once */
body.hold-hero .l-headline{opacity:1 !important;transform:none !important}
/* after the first pass the hero simplifies: eyebrow centred, services line
   gone from the hero and waiting in the deck instead */
body.hero-visited .brow{align-self:center}
body.hero-visited .hero-deck{display:none}
.dk-intro{display:none}
body.deck-intro-on .dk-intro{display:block}
html:is(.no-js,.no-gl) .dk-intro{display:none}

body[data-beat="assemble"] .l-headline{opacity:1}
body[data-beat="hero"]     .l-headline{opacity:0;transform:translateY(-4vh)}

/* the deck, entering from the right once the sheet floods */
.l-deck{align-items:center;justify-content:flex-end;text-align:left}
.l-deck > div,.l-deck{gap:22px}
/* ch resolves against this element's own font, so the column carries the
   paragraph's face and size or the measure comes out narrower than intended */
.l-deck .deck-col{max-width:32ch;font-family:var(--body);
  font-size:clamp(17px,1.55vw,calc(21.7 * var(--u)))}
.l-deck p{margin:0 0 26px;font-family:var(--body);font-weight:500;
  font-size:clamp(17px,1.55vw,calc(21.7 * var(--u)));line-height:1.45;color:var(--slate)}
/* the hand-off into the work that follows */
.l-deck .such{margin:0;font-weight:700}
.l-deck{flex-direction:column;align-items:flex-end;justify-content:center;
  padding-right:clamp(40px,11vw,calc(154 * var(--u)))}
/* On the flooded sheet the deck was still printing in slate. It takes cream
   there, the same way the nav does, and changes on the same class. */
.l-deck p,.l-deck .such{transition:color .45s ease}
body.is-flooded .l-deck p,body.is-flooded .l-deck .such{color:#F5F0E4}

/* position and opacity both come straight off scroll, no easing of its own:
   it climbs the frame like ordinary content and only fades at the nav */
.l-deck{opacity:var(--dko,1);
  transform:translate(var(--dkx,0px), var(--dky,120vh));transition:none}
/* the client marks are canvas plates on this page; the markup grid is the
   still page's (see the html:is(.no-js,.no-gl) rules at the foot) */
.deck-logos{display:none}

/* It rides the whole page rather than belonging to the hero, so it is fixed and
   sits above the works section, which carries its own background. It ducks out
   at each hand-off between projects and comes back once the next one has
   landed, so it reads as prompting the next move rather than sitting there. */
.cue{position:fixed;left:50%;bottom:2.2vh;transform:translateX(-50%);z-index:60;margin:0;
  font-family:var(--dtext);font-weight:700;font-size:calc(11 * var(--u));letter-spacing:.18em;
  text-transform:uppercase;color:var(--slate);pointer-events:none;
  opacity:var(--cue,1);transition:opacity .38s ease}
/* and it knocks out over the blue the same way the nav does */
.cue-knock{position:absolute;left:0;top:0;width:100%;color:#F5F0E4;
  pointer-events:none;clip-path:inset(0 0 100% 0)}

/* ---------- Our work ---------- */
/* ---------- nav: dark on the sheet, cream once it floods ---------- */
/* base.css states the bar in flat px, which is what stranded it on a big
   monitor: at 2560 a 1500px bar of 13px type sat in the corner of the screen.
   Restated in design pixels, so the bar grows with the rest of the page.
   Gated above 1400 rather than written unconditionally: --u is 1px below that
   and these would compute identically, but they sit later in the cascade than
   base.css's own phone overrides and would silently outrank them. */
@media (min-width:1401px){
  .nav .lsmark{width:calc(78 * var(--u));height:calc(78 * var(--u))}
  .nav .in{gap:calc(30 * var(--u))}
  .nav .mk{gap:calc(13 * var(--u))}
  .nav .wm{font-size:calc(14 * var(--u))}
  .nav a.l{font-size:calc(13 * var(--u))}
  .nav .btn{font-size:calc(13 * var(--u));padding:calc(12 * var(--u)) calc(17 * var(--u))}
  .nav .mk-knock{gap:calc(13 * var(--u))}
}
/* ---------- the bar on a phone ---------- */
/* The desktop bar is one line: lockup left, three section links and the CTA
   right. Squeezed onto a phone that line gave every link a 28x48 target and
   made the wordmark stand down, which is the format winning over the design.
   So the bar is TRANSLATED rather than shrunk: it takes two rows. The lockup
   and the CTA hold the top line, exactly the pairing the desktop bar ends and
   begins with, and the three section links take the whole width beneath, one
   third each. Measured on the rebuilt bar at 390: each link is 112x44, the CTA
   is 88x44 and the lockup 44 tall, against 28x48 / 88x36 / 36x36 before.
   Nothing is hidden behind a hamburger, the wordmark comes back, and the bar
   costs 110px of an 844px phone, which is the 12.8% the 96px bar costs of a
   768px desktop. The break is a zero-height flex item, so the two rows are one
   flex container and the knockout clone inherits the whole thing unchanged. */
@media (max-width:599px){
  .nav .in{flex-wrap:wrap;align-items:center;
    column-gap:10px;row-gap:8px;padding:13px 16px 11px}
  .nav .mk{order:0;margin-right:auto;min-height:44px}
  .nav .wm{font-size:11.5px;letter-spacing:.13em}
  .nav .btn{order:0;font-size:11.5px;padding:0 16px;min-height:44px;
    display:inline-flex;align-items:center;box-shadow:4px 4px 0 var(--slate)}
  .nav-knock .btn{box-shadow:4px 4px 0 var(--signal)}
  .nav .in::after{content:'';order:1;flex-basis:100%;height:0;margin:0}
  .nav a.l{order:2;flex:1 1 0;min-width:0;min-height:44px;
    display:inline-flex;align-items:center;justify-content:center;
    font-size:11.5px;white-space:nowrap;padding:0}
  .nav .lsmark,.nav .mk-gl{width:40px;height:40px}
}
@media (max-width:400px){
  .nav .in{column-gap:8px;padding-inline:13px}
  .nav .wm{font-size:11px;letter-spacing:.1em}
  .nav a.l{font-size:11px}
  .nav .btn{font-size:11px;padding:0 13px}
  .nav .lsmark,.nav .mk-gl{width:38px;height:38px}
}
.nav a.l{color:var(--slate)}
.nav a.l:hover{color:var(--signal)}
/* The logo knocks out against dark objects passing under it. A second copy of
   the lockup sits exactly over the first in the reversed colour, clipped to
   whatever dark thing is behind it, so the intersection reads as the logo
   inverted. mix-blend-mode cannot do this: the nav is fixed, which makes it its
   own stacking context, and a blend inside one can never see the page behind. */
.nav .wm{color:var(--slate)}
/* The lockup's mark is drawn in grains on the canvas beside it, in the studio
   section's own blues (cream in the knockout clones). The SVG keeps the box
   and stays in the DOM as the path every rasteriser reads; only its paint is
   off. This is nav-only: the footer bar's cloned mark still prints solid. */
.nav .lsmark{fill:var(--slate);visibility:hidden}
.nav .mk-gl{position:absolute;left:0;top:50%;transform:translateY(-50%);
  width:calc(78 * var(--u));height:calc(78 * var(--u));pointer-events:none}
/* The bar carries one link on a phone now, so the mark gets the room the
   three that were there used to take. */
@media(max-width:760px){.nav .mk-gl,.nav .lsmark{width:84px;height:84px}}
/* restated after the rule above, which is the one it has to outrank: the
   two-row phone bar sets the lockup at 40 and 38, and both live earlier in
   this sheet than the 760px rule */
@media(max-width:599px){.nav .mk-gl,.nav .lsmark{width:80px;height:80px}}
@media(max-width:400px){.nav .mk-gl,.nav .lsmark{width:72px;height:72px}}
.nav .mk{position:relative}
.nav .mk-knock{position:absolute;inset:0;display:flex;align-items:center;
  /* gap:INHERIT, never a number: the clone must sit on exactly the pixels
     of the lockup it copies, and the phone bar tightens .mk's gap to 9px
     while this held 13, printing the cream wordmark 4px right of the slate
     one under it */
  gap:inherit;
  pointer-events:none;clip-path:inset(0 0 100% 0);
  mask-mode:alpha;mask-size:100% 100%;mask-repeat:no-repeat;
  -webkit-mask-size:100% 100%;-webkit-mask-repeat:no-repeat}
.nav .mk-knock .lsmark{fill:#F5F0E4}
/* the duplicate wordmark is generated content, not a text node: it prints the
   second time without adding a second time to the link's visible label */
.nav .mk-knock .wm{color:#F5F0E4}
.nav .mk-knock .wm::after{content:attr(data-wm)}
.nav .btn,.encore-btn{background:var(--slate);color:var(--paper);
  box-shadow:calc(5 * var(--u)) calc(5 * var(--u)) 0 var(--signal)}
/* base.css owns the hover colour here; only the offset is restated in --u */
.nav .btn:hover{box-shadow:calc(3 * var(--u)) calc(3 * var(--u)) 0 var(--slate)}
.nav a.l,.nav .wm,.nav .btn,.encore-btn{transition:color .35s ease,background .35s ease,box-shadow .35s ease}
.nav .lsmark{transition:fill .35s ease}
/* The whole bar knocks out against the blue, not just the mark. A full copy of
   it in the on-dark colours sits over the real one and is clipped to the card's
   own rectangle, so as the card's edges sweep in they change the letters where
   they cross and nowhere else. A class toggled at a threshold cannot do this:
   it flips the entire bar while an edge is still halfway through the wordmark,
   which is how the cream logo used to disappear into the cream paper for a
   stretch of the close. */
.nav-knock{position:absolute;inset:0;pointer-events:none;clip-path:inset(0 0 100% 0)}
.nav-knock .wm{color:#F5F0E4}
.nav-knock .lsmark{fill:#F5F0E4}
.nav-knock a.l{color:#F5F0E4}
/* Over the flooded card the rest of the bar knocks out to cream LETTERS, but
   the button was knocking out to a cream SLAB: a hard paper rectangle sitting
   on the blue, which read as a background pasted behind the nav rather than as
   a button. It takes the same treatment as its neighbours instead, cream on
   the blue, and keeps the orange offset that identifies it as the action. */
/* The button is DARK on both grounds, so it needs no inversion at all. It was
   being flipped to a cream outline whenever the card was flooded -- a class on
   the whole bar -- and the card shrinks away from that corner well before the
   flag clears, so for a stretch of the close it was cream on cream and simply
   vanished. Navy reads on the paper and on the blue alike; the only thing the
   knockout has to change is the letters around it. */
body.is-flooded .encore-btn{background:#F5F1E6;color:#0A0F2C;
  box-shadow:calc(7 * var(--u)) calc(7 * var(--u)) 0 var(--signal)}

/* ---------- Our work ----------
   An ordinary scrolling page: five projects, alternating sides. The only
   unusual thing is that no photograph is an image element. A single cloud of
   particles holds the viewport and takes the shape of whichever project is at
   the middle of the screen, coming apart and re-forming between them. Each
   .shot below is an empty box: it exists so the layout has somewhere to put
   the picture, and so the renderer knows where to draw it. */
/* long, because the hand-off between one project and the next is the thing
   worth watching and it needs room to be watched */
/* No background of its own. The works canvas prints the sheet itself, with a
   top fade so it can take over from the hero without a seam; a CSS paper
   band underneath it cut a hard edge across that fade and read as a solid
   panel dropped over Our Work. Same fault, same fix, as .studio and .foot. */
.works{position:relative;z-index:4;padding-bottom:0}

/* zero height, so the canvas can hold the viewport without taking any room in
   the flow, and everything under it scrolls normally over the top */
/* Fixed, not sticky. A sticky element is moved by the compositor and lands on
   fractional device offsets while you scroll, and the canvas hanging off it
   gets resampled every time it does: the grains are whole device pixels on the
   canvas's own grid, so resampling the canvas softens the whole picture, and
   walking in and out of that state frame by frame is what read as jitter.
   Fixed, it never moves at all, so the picture is rasterised once. Drawing is
   already gated on the section being on screen. */
/* The canvas inside is deliberately wider and taller than the screen so grains
   can exist off it before they travel in. Height 0 meant nothing contained
   that overspill, and because this rail is FIXED the root's overflow clip
   cannot reach it either: on a phone the extra 124px each side is what let the
   visual viewport pan sideways. The rail takes the viewport's own size and
   clips to it, which changes nothing about what is drawn on screen and stops
   the page being draggable. It is fixed, so it still occupies no flow. */
.works-rail{position:fixed;top:0;left:0;right:0;height:100svh;
  overflow:clip;pointer-events:none;z-index:1}
/* top and height are set by the renderer, from the same numbers as the
   drawing buffer; these are only a starting point */
.works-gl{position:absolute;top:-100svh;left:0;width:100%;height:200svh;display:block;
  pointer-events:none;visibility:hidden}

/* The heading is ordinary type, set once. The cloud does not make it; the
   cloud arrives out of the card above and turns over around it. It sits right
   where that first cloud forms rather than a screen and a half further down,
   and it scrolls past like anything else. */
/* Just enough to exist. It is the slot the first picture gathers out of, not
   a spacer: at 44svh it was a third of a screen of nothing between the title
   and the first project. */
.works-open{position:relative;z-index:2;height:4svh}
.wo-hold{height:100%}
/* Caps, against the usual rule and for the same reason the hero's eyebrow is:
   this site's logotype and nav are set in bold caps, so the label mirrors the
   wordmark rather than fighting it. Slate, not the orange, which at this size
   on cream comes out at 2.7:1. */
.wo-brow{margin:0 0 clamp(10px,1.3vh,18px);font-family:var(--dtext);font-weight:700;
  font-size:clamp(11px,1.02vw,calc(14 * var(--u)));letter-spacing:.2em;text-transform:uppercase;
  color:var(--slate)}
.wo-hold h2{margin:0;font-family:var(--dcond);text-transform:uppercase;
  letter-spacing:var(--track);line-height:.86;color:var(--slate);
  font-size:clamp(64px,12.4vw,calc(173.6 * var(--u)))}
/* arrives once the cloud has actually gathered around the words */
.wo-say{margin:clamp(16px,2.4vh,30px) 0 0;max-width:46ch;color:#5B6470;
  font-size:clamp(15px,1.35vw,calc(18.9 * var(--u)));line-height:1.5;
  opacity:0;transform:translateY(12px);
  transition:opacity .7s ease, transform .8s cubic-bezier(.2,.7,.2,1)}
.works-open.lit .wo-say{opacity:1;transform:none}

/* 20% narrower and 20% taller than the wide crop they were: a portrait-ish
   landscape, so the copy gets a real column beside it rather than a gutter. */
/* One height for every row, so the rhythm down the page is even. Tall, because
   the row's height IS the time the picture holds: the picture is sticky, so it
   pins in the middle of the screen and stays there while the copy beside it
   scrolls past, and it only lets go when the row runs out. */
.work-row{min-height:158svh;display:grid;
  grid-template-columns:minmax(0,0.946fr) minmax(0,1fr);
  gap:clamp(28px,3.6vw,calc(50.4 * var(--u)));align-items:start;position:relative;z-index:2;
  margin-inline:auto;padding-inline:var(--pad);width:100%}
.work-row:nth-child(even){grid-template-columns:minmax(0,1fr) minmax(0,0.946fr)}
/* the first row's offset is set by the renderer, which measures where the
   title lands and places the project a stated distance after it */
.work-row:nth-child(even) .shot{order:2}
.work-row:nth-child(even) .say {order:1}
/* On the rows where the copy sits to the left, the edge facing the picture is
   the ragged one, so the same gutter measured three times wider by eye than it
   does on the rows the other way round. The copy flushes to whichever edge
   faces the picture, so the two sides read the same. */
.work-row:nth-child(even) .say{text-align:right}
.work-row:nth-child(even) .say .svc{justify-content:flex-end}
/* the deck and the credit carry a measure, so their boxes are narrower than the
   column and have to be pushed across too or the copy flushes inside a block
   that is still sitting at the far side */
.work-row:nth-child(even) .say .desc,
.work-row:nth-child(even) .say .credit{margin-left:auto}

/* Empty: the grains draw here. Sticky, centred on the screen, so the picture
   is fixed while everything beside it moves. --shot-h is published by the
   renderer, which measures it. */
.shot{display:block;width:100%;aspect-ratio:1.164;
  position:sticky;top:calc(50svh - var(--shot-h, 320px) / 2);
  /* sticky is bounded by the containing block less this margin, so it lets go
     early and hands the rest of the row to the transition. Shorter pin, longer
     hand-off, without making the page any taller. */
  margin-bottom:30svh}
/* and the copy rides the scroll, arriving and leaving past the fixed picture */
/* The copy travels up, and once it is level with the picture it holds there
   too: same sticky top, same height, same bottom margin, so the two pin and
   let go on exactly the same scroll and leave together. It used to run on past
   a picture that was still parked, which read as the caption abandoning it. */
.say{align-self:start;min-height:var(--shot-h,320px);
  display:flex;flex-direction:column;justify-content:center;
  position:sticky;top:calc(50svh - var(--shot-h, 320px) / 2);
  margin-bottom:30svh}
/* The last row's hand-off goes to the studio, not to blank paper: a shorter
   row and no release margin, so the picture lets go straight into the mark's
   gathering grains and the studio is on you a full screen sooner. */
.work-row:last-child{min-height:126svh}
/* A sticky element is bounded by its containing block LESS this margin, so a
   bigger margin makes the picture let go sooner. At 4svh the last project sat
   pinned dead centre for about 300px of scroll after its copy had finished,
   fully assembled and doing nothing, before the hand-over to the mark would
   start: it read as the reel stalling on its closing shot. */
.work-row:last-child .shot,
.work-row:last-child .say{margin-bottom:30svh}

/* the quiet greys are all set at or above 4.5:1 on the cream: #9A9DAB measured
   2.37:1, #6E7482 4.12:1. Same family, real hexes, nothing faded. */
.say .n{margin:0 0 14px;font-family:var(--dtext);font-weight:700;font-size:calc(12 * var(--u));
  letter-spacing:.16em;color:#646978}
.say .kind{margin:0 0 8px;font-family:var(--dtext);font-weight:700;font-size:calc(12 * var(--u));
  letter-spacing:.16em;text-transform:uppercase;color:#5B6470}
.say h3{margin:0 0 clamp(12px,1.6vh,20px);font-family:var(--dcond);text-transform:uppercase;
  font-size:clamp(37.5px,4.88vw,calc(68.32 * var(--u)));line-height:.9;letter-spacing:var(--track);color:var(--slate)}
/* every project is a door to its live site: the title and the picture both */
.say h3 a{color:inherit;text-decoration:none;transition:color .25s ease}
.say h3 a:hover{color:var(--signal)}
.say .desc{margin:0 0 clamp(16px,2.2vh,26px);color:#5B6470;
  font-size:clamp(14px,1.12vw,calc(15.68 * var(--u)));line-height:1.55;max-width:42ch}
.say .svc{margin:0 0 18px;padding:0;list-style:none;display:flex;flex-wrap:wrap;gap:8px}
.say .svc li{font-family:var(--dtext);font-weight:700;font-size:calc(11 * var(--u));letter-spacing:.11em;
  text-transform:uppercase;color:var(--slate);border:calc(1.5 * var(--u)) solid var(--slate);
  padding:calc(6 * var(--u)) calc(10 * var(--u));white-space:nowrap}
.say .credit{margin:0;font-family:var(--dtext);font-weight:700;font-size:calc(10 * var(--u));
  letter-spacing:.09em;text-transform:uppercase;color:#646978;max-width:34ch;line-height:1.5}

/* ---------- the reel, stacked ----------
   The whole point of this section is that a picture PARKS on the screen and
   turns from the photograph into the site we built while it is parked. Stacked,
   both boxes were static, so the picture rode the scroll and the turn happened
   somewhere between the nav bar and the top of the screen: measured, a phone
   never once held a settled picture, and every frame caught it mid-dissolve.
   So the pin is kept, rotated for the format. The picture pins UNDER THE BAR
   instead of on the middle of the screen (--pin-top is that line: stated as
   --bar-h up top and confirmed by works.js), and the copy parks directly under
   the picture instead of beside it, so the pair holds the screen together while
   the turn runs.

   The row is one BLOCK, not a grid, and that is the whole of what holds the
   pair together. As a two-track grid the slack sat between the picture and the
   copy, and a sticky grid item is held inside its own track: the picture could
   only travel the 34svh of that gap, and the copy could not reach its line
   until it had climbed the same 34svh. So the picture pinned, 287px of blank
   paper stood under it, and by the time the name arrived the picture was
   already letting go. Measured across fourteen frames of the reel, the two
   were on the screen together in about half of them.
   In flow both are held by the ROW, so both answer to the same box and can be
   made to engage on the same scroll pixel and let go on the same one.

   The runway still has to exist - the picture needs somewhere to travel, and
   in flow that is the margin under it - so the copy is pulled back up over it
   by exactly the same figure less the gap. That one negative margin is what
   makes the pair a pair, and the arithmetic is exact rather than tuned:
     engage  the copy's natural top is the picture's foot + 22, and its sticky
             line is the picture's pinned foot + 22, so both catch at once
     release both boxes have the same bottom edge to clear, the picture's at
             140 + 301 + R and the copy's at 463 + 265, which are the same
             number when the copy's line is set from the same three terms
   Measured at 390x844, both engage at row top +89 and both let go at row top
   -386: the pair is on the screen together for every one of the 476 pixels the
   pin runs, against 22 of them before. And 476 is drivePins' own run, which
   subtracts exactly these terms - the formula was written for flow and the
   grid had been quietly lying to it. */
@media (max-width:900px){
  /* NO PINS on a phone. The stacked reel used to pin the picture under the
     bar and park the copy beneath it, which on glass read as the page
     SNAPPING and holding against the thumb. It is ordinary flow now: the
     picture, then its copy, scrolling at page speed like everything else.
     The turn (photograph -> site) is untouched: it rides the .turn-at
     marker, which never depended on the pin. */
  .work-row,.work-row:nth-child(even){display:block;
    gap:0;min-height:0;padding-block:7vh}
  .shot{position:relative;top:auto;width:100%;margin:0 0 24px}
  .say{position:static;top:auto;width:100%;min-height:0;display:block;
    margin:0}
  .work-row:nth-child(even) .shot{order:0}
  /* stacked, there is no picture beside it to flush against */
  .work-row:nth-child(even) .say{order:0;text-align:left}
  .work-row:nth-child(even) .say .svc{justify-content:flex-start}
  .work-row:nth-child(even) .say .desc,
  .work-row:nth-child(even) .say .credit{margin-left:0}
  /* the desktop's last-row release margins outrank the plain .shot/.say
     overrides above (they carry :last-child), so DLCC's copy sat 30svh off
     its photo and the studio stood a dead half-screen further down: restated
     at the same specificity, every row measures the same 24px */
  .work-row:last-child .shot{margin-bottom:24px}
  .work-row:last-child .say{margin-bottom:0}
  .work-row:last-child{min-height:0;padding-bottom:2vh}
  .works-open{min-height:52svh}
  .say .credit{padding-bottom:2px}
}

/* ---------- come back to the top and the page notices ---------- */
/* The headline itself does the talking, so there is no second voice competing
   with it. The button answers from the foot of the hero, over the scroll cue,
   on the same centre line as the headline. */
.encore-btn{position:absolute;left:50%;bottom:7.4vh;z-index:8;
  font-size:calc(16 * var(--u));padding:calc(19 * var(--u)) calc(32 * var(--u));
  box-shadow:calc(7 * var(--u)) calc(7 * var(--u)) 0 var(--signal);
  opacity:0;pointer-events:none;transform:translate(-50%,12px);
  transition:opacity .4s ease .12s, transform .5s cubic-bezier(.2,.7,.2,1) .12s,
             background .35s ease, box-shadow .35s ease, color .35s ease}
.encore-btn.encore-on{opacity:1;pointer-events:auto;transform:translate(-50%,0)}
.encore-btn:hover{transform:translate(-50%,3px);box-shadow:4px 4px 0 var(--signal)}
/* the headline hands off: out, text changes, back in */
.l-headline h1{transition:opacity .26s ease, transform .26s ease}
.l-headline h1.handing{opacity:0;transform:translateY(-14px)}
@media (max-width:760px){
  .encore-btn{bottom:8.5vh;font-size:14px;padding:16px 24px}
}

/* ---------- The studio ---------- */
/* The mark is the composition: the brush LS, blown up past the frame, made of
   the same grains as everything else on the page. The canvas scrolls with the
   section; its grains are round and soft on purpose, since a scrolling canvas
   lands on fractional device pixels and hard one-pixel squares shimmer there
   (the works pictures had exactly that fault, and they sit on a fixed rail for
   it; soft ink resamples invisibly). */
/* No background of its own: the body's paper is the same colour, and painting
   it here would cut a flat band across the works canvas's textured sheet at
   the seam (the section sits above that canvas so its grains can ride over
   the last picture's exit). */
/* 165svh left a half-screen of empty paper under the copy before the footer
   began, and the footer then opened with another 24vh of padding on top of
   that. Both halved; the mark still bleeds past the section either way. */
.studio{position:relative;min-height:96svh;z-index:5}
/* The canvas reaches up over the works section's tail, so the mark's grains are
   already adrift on screen while the last picture is still leaving. The section
   needs no overflow clip for it: the canvas only ever overflows at this top
   edge, on purpose, and the grains themselves never draw off its left edge. */
.studio-gl{position:absolute;left:0;right:0;top:-30svh;height:calc(100% + 30svh + 75svh);
  width:100%;display:block;pointer-events:none}
.studio-in{position:relative;z-index:2;max-width:var(--wrap);margin-inline:auto;
  min-height:inherit;padding:0 var(--pad);display:flex;align-items:center;min-height:96svh}
.studio-copy{margin-left:auto;width:min(calc(560 * var(--u)),100%)}
/* the copy arrives the way everything on this page does: with the grains.
   Staggered up from below as the mark finishes gathering, and it leaves the
   same way if the mark is scrolled back apart. (The transparent start state
   is the reveal's own; nothing holds an in-between value at rest.) */
.studio-copy .st-brow,.studio-copy h2,.studio-copy .st-p{
  opacity:0;transform:translateY(28px);
  transition:opacity .6s ease,transform .75s cubic-bezier(.2,.7,.2,1)}
.studio-copy h2{transition-delay:.09s}
.studio-copy .st-p{transition-delay:.18s}
.studio-copy .st-p + .st-p{transition-delay:.27s}
.studio.lit .studio-copy .st-brow,.studio.lit .studio-copy h2,
.studio.lit .studio-copy .st-p{opacity:1;transform:none}
/* the hero's own eyebrow, exactly: same weight, same tracking, same ink */
.st-brow{margin:0 0 14px;font-family:var(--dtext);font-weight:700;text-transform:uppercase;
  font-size:clamp(11px,1.02vw,calc(14 * var(--u)));letter-spacing:.2em;color:var(--slate)}
.studio h2{margin:0 0 clamp(18px,2.6vh,30px);font-family:var(--dcond);text-transform:uppercase;
  font-size:clamp(58px,7.2vw,calc(100.8 * var(--u)));line-height:.86;letter-spacing:var(--track);
  color:var(--slate);text-wrap:balance}
.st-p{margin:0 0 1.1em;color:#3D4351;font-size:clamp(16px,1.32vw,calc(18.48 * var(--u)));line-height:1.62;max-width:54ch}
.st-p:last-child{margin-bottom:0}

/* ---------- footer ---------- */
/* the hero again, said the other way round: the crowd on the other side, the
   ask in place of the headline, and the utilities hung underneath */
/* no background of its own, same reason as the studio: the body's paper is the
   same colour, and a solid sheet here would cut across the mark's grains as
   they cross the seam on the studio's canvas below */
.foot{position:relative;z-index:6}
.foot-stage{position:relative;min-height:96svh;display:flex;align-items:center}
/* the canvas reaches up over the studio's tail, so the crowd's grains are on
   screen falling in while the mark above is still letting go */
.foot-gl{position:absolute;left:0;right:0;top:-70svh;height:calc(100% + 70svh);
  width:100%;display:block;pointer-events:none}
/* The hero again, at the hero's own scale: the headline monumental, anchored
   to the left pad with its far end running over the crowd. The whole crowd
   stands on screen, centre-right, feet toward the stage's floor; the copy is
   all left-aligned beneath the headline, on paper. */
/* top-heavy padding: the block sits below the fixed nav's lockup, which the
   left-anchored headline would otherwise run straight into */
.foot-say{position:relative;z-index:2;width:100%;padding:8vh var(--pad) 4vh}
.ft-stack{display:flex;flex-direction:column;align-items:flex-start;
  width:fit-content}
.foot h2{margin:0;font-family:var(--dcond);text-transform:uppercase;
  font-size:clamp(96px,23.6vw,calc(330.4 * var(--u)));line-height:.8;letter-spacing:var(--track);
  color:var(--slate);white-space:nowrap}
/* This box is measured in ch off the inherited body size, and everything inside
   it is sized in --u, so on a big monitor the button outgrew the box it stands
   in and the mail address dropped onto its own line. The ch has to scale with
   the rest: the size set here is never painted, every child states its own. */
.ft-under{margin-top:clamp(20px,3.2vh,34px);display:flex;flex-direction:column;
  align-items:flex-start;font-size:calc(18 * var(--u));width:min(38ch,45vw)}
.ft-line{margin:0 0 clamp(20px,3vh,30px);color:var(--slate);
  font-size:clamp(16px,1.32vw,calc(18.48 * var(--u)));line-height:1.6;max-width:38ch}
.ft-cta{display:flex;align-items:center;gap:calc(26 * var(--u));flex-wrap:wrap}
.ft-cta .btn{font-family:var(--dtext);font-weight:700;font-size:calc(14 * var(--u));letter-spacing:.06em;
  text-transform:uppercase;background:var(--slate);color:var(--paper);
  padding:calc(17 * var(--u)) calc(28 * var(--u));text-decoration:none;
  box-shadow:calc(6 * var(--u)) calc(6 * var(--u)) 0 var(--signal);
  transition:transform .2s ease, box-shadow .2s ease}
.ft-cta .btn:hover{transform:translate(2px,2px);box-shadow:3px 3px 0 var(--signal)}
.ft-mail{font-family:var(--dtext);font-weight:700;font-size:calc(13 * var(--u));letter-spacing:.08em;
  color:var(--slate);text-decoration:none;border-bottom:2px solid var(--signal);padding-bottom:3px}
.ft-mail:hover{color:var(--signal)}

.foot-bar{position:relative;z-index:2;border-top:1px solid #DDD7C8;
  max-width:var(--nav-wrap);margin-inline:auto;
  padding:calc(26 * var(--u)) var(--pad) calc(30 * var(--u));
  display:flex;align-items:center;gap:calc(28 * var(--u));flex-wrap:wrap}
.fb-mk{display:flex;align-items:center;gap:calc(11 * var(--u));margin-right:auto;text-decoration:none}
.fb-mk .lsmark{width:calc(46 * var(--u));height:calc(46 * var(--u));fill:var(--slate)}
.fb-mk .wm{font-family:var(--dtext);font-weight:700;font-size:calc(12 * var(--u));letter-spacing:.15em;
  text-transform:uppercase;color:var(--slate)}
.fb-links{display:flex;gap:calc(24 * var(--u))}
.fb-links a{font-family:var(--dtext);font-weight:700;font-size:calc(12 * var(--u));letter-spacing:.08em;
  text-transform:uppercase;color:var(--slate);text-decoration:none}
.fb-links a:hover{color:var(--signal)}
.fb-legal{margin:0;font-family:var(--dtext);font-weight:700;font-size:calc(11 * var(--u));
  letter-spacing:.08em;text-transform:uppercase;color:#646978}

/* ---------- Talk with us ----------
   The popup's styles live entirely in assets/talk.css, which contact.js injects.
   The navy-ground version that used to sit here has been removed: it loaded
   first and lost on everything talk.css restated, but the handful of properties
   talk.css did NOT restate (.talk-in's width, .talk's display) still leaked
   through and fought it, pinning the sheet to 660 design px and killing the
   vertical centring. One sheet owns the popup now. */

@media (max-width:900px){
  .studio{min-height:auto}
  /* The mark owns the top of the section, and it is the same mark the desktop
     shows: taller than the screen and running off both edges, not a logo
     centred on the paper. studio.js sizes it min(162vw, 95vh) with its top at
     10vw, so the band reserved here is exactly that plus a breath. Change one
     and the other has to move with it. */
  .studio-in{min-height:auto;align-items:flex-start;
    /* 12vh of tail under the copy, plus the footer's own band above the crowd,
       left a dead screen between the studio's last line and LET'S TALK */
    padding:calc(10vw + min(162vw, 95vh) + 5vh) var(--pad) 4vh}
  .studio-copy{margin-left:0;width:100%;text-align:center}
  .studio-copy .st-p{margin-inline:auto}
  /* Stacked, the crowd is the footer's BACKDROP, not a band the ask has to
     share the screen with: the canvas hangs from the stage's floor at 80% of
     its height, so the crowd's feet stand on the page's foot and the ask
     prints over its upper reach, which is the composition the desktop has.
     studio.js frames the crowd to FILL this box's height (its shoulders may
     run off the sides, the way the studio's mark does) and centres the
     figure with the megaphone, so what a phone lands on is her, whole, with
     LET'S TALK over the crowd. The stage plus the utility bar is one screen
     exactly, so the resting frame is the closing frame. */
  .foot-stage{min-height:calc(100svh - var(--fbar,88px));
    padding-top:0;padding-bottom:0;align-items:flex-start}
  /* the whole stage, not a band: feet stay on the page's foot and the crowd
     grows upward behind the ask (studio.js dims it there so the ask reads) */
  .foot-gl{top:auto;bottom:0;height:100%}
  .foot-say{padding:calc(var(--bar-h,104px) + 2vh) var(--pad) 2vh}
  /* one cream wash rising from the FOOT of the stage: paper at the bottom
     edge, gone by the time it reaches under the ask, so the buttons and the
     "A campaign..." line stand on cream while the crowd's upper body still
     reads. z1: above the canvas, below the ask's own layer (z2). */
  .foot-stage::after{content:'';position:absolute;left:0;right:0;bottom:0;
    height:124%;z-index:1;pointer-events:none;
    background:linear-gradient(0deg, var(--paper) 0%, rgba(245,240,228,0) 100%)}
  /* and the utilities take their own measure back, so the ask has the room */
  .foot-bar{gap:12px;padding-block:18px 22px}
  .fb-legal{width:100%}
}

/* ---------- and on a short sheet the utilities give the crowd its size back
   Reserving the ask and the bar honestly is what stops the crowd being cut in
   half, but on a 640px Android it left the band at 158px, which is a strip
   rather than a crowd. The bar was the largest single consumer of what was
   left, 171.5px of it, and almost all of that is air: three rows of padding
   and gaps around type that has not moved. So on a short sheet the air comes
   out and the band takes it. Nothing a finger reaches gets smaller - every
   link and button in here keeps its 44 - and the type sizes are untouched
   except the closing headline, which drops from a fifth of the screen's height
   to a sixth, the same proportion the studio's own headline holds.
   Measured at 360x640: the ask and bar together fall from 474.9 to 391.4 and
   the band rises from 158 to 241.6. */
@media (max-width:900px) and (max-height:700px){
  .foot-say{padding:1.4vh var(--pad) 0.8vh}
  .foot h2{font-size:min(clamp(56px,39vw,234px), 17svh)}
  .ft-under{margin-top:10px}
  .ft-line{margin-bottom:14px}
  .ft-cta{gap:14px}
  .foot-bar{gap:6px;padding-block:8px 10px}
  .fb-links{gap:0}
}
/* and a phone on its side is shorter again: 390px of height with the bar and
   the ask both on it left 289 for the two of them and the band on its floor,
   which is 19px more than the screen has. The same air comes out a second
   time. Measured at 844x390: the ask and bar fall from 289 to 268 and the
   stage's top goes from -18.8 to +14, so the crowd is whole on its side too.
   Nothing a finger reaches is under 44 in here either. */
@media (max-width:900px) and (max-height:480px){
  .foot-say{padding:0.6vh var(--pad) 0.4vh}
  .ft-under{margin-top:6px}
  .ft-line{margin-bottom:10px}
  .foot-bar{padding-block:5px 7px}
}
/* ---------- and a phone on its side is the desktop's proportion ----------
   Stacking the crowd above the ask is right for a sheet that is taller than it
   is wide and wrong for one that is wider: at 844x390 the honest band came out
   112px and the crowd stood 90px wide in the middle of an 844px strip with the
   whole right half of the screen empty, which is a whole crowd and still not
   the closing frame. Turned on its side the screen has the DESKTOP's shape, so
   it gets the desktop's composition: the crowd takes the full height of the
   stage and stands to the right, the ask holds the left column under the
   headline, and studio.js shifts the framing across exactly as it does at
   1366. Measured at 844x390: the crowd goes from 112 to 306 of height and the
   stage's top stays positive. */
@media (max-width:900px) and (max-height:480px) and (min-aspect-ratio:12/10){
  /* height, not top+bottom: a canvas is a replaced element, so an auto height
     on it resolves to its intrinsic pixel size and not to the stretch between
     the two offsets. It overflowed the stage by 116px and took the document's
     own foot down with it. */
  .foot-gl{top:0;bottom:auto;height:100%}
  .foot-stage{padding-top:0;align-items:center;
    min-height:calc(100svh - var(--fbar,88px) - 2px)}
  .foot-say{width:58%}
}

/* ---------- the display type, translated for the phone ----------
   Every one of these lines is set in vw on the desktop, which means on a phone
   it was not translated at all, only shrunk: measured at 390, the headline came
   out 55px in an 844px viewport where the desktop sets 158px in a 768px one, so
   the phone got type a third of the relative size and two thirds of the screen
   stood empty around it. The rule here is the one the desktop already follows,
   applied to the format the phone actually is: the line fills the measure.

   Every ceiling is the value its vw term reaches at 599px, the width this block
   stops at, so nothing steps when the breakpoint is crossed on a tablet.

   Measured in DrukCond, em per line, tracking included:
     WORK THAT MOVES / CAMPAIGNS  4.04em   OUR WORK    2.33em
     THE STUDIO                   2.46em   LET'S TALK  2.22em
   which is what each of these sizes is solved from against the measure. */
@media (max-width:599px){
  /* the hero: two lines filling the width, not one line hiding in the middle.
     The pad comes in to 14px because at this size the headline IS the margin. */
  .l-headline{padding-inline:14px}
  /* The break is set in the markup, by the no-break space between "moves" and
     "campaigns": left to itself the line broke after "moves" and left
     "campaigns" standing alone. Two words on each line, and the desktop, which
     is nowrap, never sees the difference. */
  .l-headline h1{white-space:normal;font-size:clamp(56px,21.6vw,129px)}
  /* it hangs off the headline's right edge exactly as it does on the desktop;
     at 390 it needs 337px of a 362px measure, so it holds to the last phone */
  .brow{font-size:clamp(10.5px,2.75vw,16.5px)}
  .brow span{padding:0 .4em}
  /* Under 360 the eyebrow will not sit on one line at its floor size, and being
     nowrap it was the widest unbreakable thing in the stack: it set the whole
     column's min-content to 324px inside a 292px measure, so it pushed the
     HEADLINE off both edges with it (measured at 320: h1 box 324 wide starting
     at x = -2). It takes two lines there and hangs off the right the same way. */
  @media (max-width:359px){
    .brow{white-space:normal;text-align:right;line-height:1.55}
  }

  /* "Our work", both copies of it, and the deck under them */
  .grow h2{font-size:clamp(64px,38vw,228px)}
  .grow .wo-say{font-size:clamp(15px,4.2vw,25px);max-width:34ch}
  .wo-brow,.st-brow{font-size:clamp(11px,2.9vw,17.4px)}

  /* each project's name: it is the only type on a screen the picture is not on */
  .say h3{font-size:clamp(37.5px,12.6vw,75px)}

  /* the studio and the close, both filling the measure the way the hero does */
  .studio h2{font-size:clamp(58px,28.5vw,171px)}
  .st-p{font-size:clamp(16px,4.3vw,26px)}
  .foot h2{white-space:normal;font-size:clamp(64px,39vw,234px)}
  .ft-under{width:auto;max-width:100%}
  .ft-line{font-size:clamp(16px,4.3vw,26px);max-width:30ch}
  .ft-cta{gap:18px}
  .fb-links{width:100%}
}
/* 600 to 900 is the tablet, where the desktop's vw terms are already large
   enough; only the two lines that were pinned to hard pixel ceilings are
   reopened, so a 768 tablet is not still reading a 390 phone's ceiling. */
@media (min-width:600px) and (max-width:900px){
  .foot h2{white-space:normal;font-size:clamp(120px,24vw,216px)}
  .studio h2{font-size:clamp(96px,17vw,153px)}
  .grow h2{font-size:clamp(96px,22vw,198px)}
  .say h3{font-size:clamp(48px,8vw,72px)}
  .brow{font-size:clamp(12px,1.5vw,13.5px)}
}

/* ---------- a target is a thumb, everywhere the layout is stacked ----------
   The stacked layout is the touch layout, and a 768 tablet was still being
   handed the pointer sizes: measured before this block, 27 of the 48 things a
   finger can reach at 768x1024 were under 44x44, the worst of them the bar's
   own links at 31x18. Every one of them is a box that can simply be taller
   without a pixel of type moving, so it is. Measured after: 0.
   The one exception left anywhere is the address inside the popup's sentence,
   which is an inline link in a run of text and takes its height from the line,
   the case the guideline itself excepts; it is given 44 by hand in talk.css. */
@media (max-width:900px){
  .nav a.l,.nav .btn{min-height:44px;display:inline-flex;align-items:center}
  /* "Work" and "Studio" are short words: 33 and 41 px of type, so the box has
     to carry the last few pixels of width as well as the height */
  .nav a.l{min-width:44px;justify-content:center}
  .nav .mk{min-height:44px}
  /* the project's name is a door to its live site, and at the narrowest phone
     the type is 40px on a 0.9 line, which is 36 of box: the padding carries it
     to 44 and the negative margin hands the space straight back */
  .say h3 a{display:inline-block;padding-block:5px;margin-block:-5px}
  .ft-cta .btn{min-height:44px;display:inline-flex;align-items:center}
  .ft-mail{min-height:44px;display:inline-flex;align-items:center}
  .fb-mk{min-height:44px}
  .fb-links{gap:0;flex-wrap:wrap}
  .fb-links a{min-height:44px;display:inline-flex;align-items:center;
    padding-right:26px}
}

/* ---------- the bar hands over at the foot of the page ----------
   The closing block is the last thing in the document, so at the page's
   resting bottom it cannot be scrolled clear of a fixed bar: measured, ten hit
   tests out of ten across LET'S TALK returned a nav element, the bar's slate
   letters printing through the headline's, and the footer's own Talk with us
   sat under the bar's. The bar's job is done there anyway - the footer carries
   the same lockup, the same three links and the same Talk with us, at full
   size on clear paper - so it stands down as the ask reaches it and comes
   straight back the moment you leave. Phone and tablet only: a desktop's bar
   is 96px against a 768px screen and the ask clears it with 546px to spare. */
@media (max-width:900px){
  .nav{transition:transform .4s cubic-bezier(.2,.7,.2,1)}
  body.bar-down .nav{transform:translateY(-102%)}
  /* And when it comes back mid-page it stands on paper: works.js flips
     .bar-paper everywhere EXCEPT over the hero (whose full-bleed stage the
     bar must stay glass on — the knockout clones own the letters there), so
     the returning bar never prints through the copy beneath it. The plate is
     a child layer, so it slides with the bar and the knockouts still win. */
  .nav::before{content:'';position:absolute;inset:0 0 -1px;z-index:-1;
    background:var(--paper);border-bottom:1px solid #DDD7C8;
    opacity:0;transition:opacity .3s ease;pointer-events:none}
  body.bar-paper .nav::before{opacity:1}
}

/* ---------- and while it is standing there it stands on ground ----------
   A transparent bar is right over the hero, where the paper behind it IS the
   page's own ground, and wrong over everything after it. On a phone the copy
   takes the whole measure, so it runs straight under the bar's own box: at 390
   a scripted sweep of the reel found the project copy colliding with .nav .in
   at 43 sampled scroll positions, five runs of roughly 360px each, printing
   "TAMMY BALDWIN" through "WORK" and "WHAT WE DO" and the credit line through
   the logotype. Both strings were illegible, which on the section the studio is
   built around is not a nitpick. Desktop never sees this: the copy sits in a
   column left of the links and the sweep's 13 flagged rows are horizontal-band
   overlaps with clear air between them.

   So on a phone the bar carries paper of its own, everywhere except the two
   places it is meant to be glass: the top of the page, and the stretch where
   the hero's card is passing under it and the knockout clone owns the letters.
   works.js drives the class off floodClip, the same figure the knockout is
   clipped to, so the plate and the knockout can never both be on.
   The plate is a child rather than a background on .nav itself, so it can slide
   with the bar and so the knockout clone still prints over it. */
/* No plate. The bar is glass the whole way down the page: a paper band sliding
   in behind it on scroll reads as a header appearing out of nowhere, which is
   not what this page is. Collisions are handled by moving the bar, never by
   growing a background behind it. */

/* ---------- a short sheet is sized against its height ----------
   Every display line on this page is set in vw, which is the right measure on
   a phone held upright and the wrong one turned on its side: at 844x390 the
   closing headline came out 202px in a 390px window and the ask could not be
   on the screen at the same time as the bar it has to clear. The ceiling here
   is the same fraction of the viewport the desktop's own headline holds
   (158px of 768, 20.6%), so a landscape phone gets the desktop's proportion
   rather than a portrait phone's. It bites only where the screen is short:
   at 390x844 the vw term is 152 against a 169 ceiling and nothing moves. */
@media (max-width:900px){
  .foot h2{font-size:min(clamp(64px,39vw,234px), 20svh)}
  .studio h2{font-size:min(clamp(58px,28.5vw,171px), 17svh)}
}
@media (min-width:600px) and (max-width:900px){
  .foot h2{font-size:min(clamp(120px,24vw,216px), 20svh)}
  .studio h2{font-size:min(clamp(96px,17vw,153px), 17svh)}
}


/* ---------- the phone's bar, and where the cue sits ---------- */
/* Three links and a button is a desktop bar squeezed onto a phone, and every
   one of those links goes somewhere the page reaches by scrolling anyway. The
   ask is the only thing a thumb needs, so it is the only thing kept and the
   mark takes the space back. The wordmark stays: the bar has to say who this
   is, so the mark grows and the WORD gets smaller rather than disappearing. */
@media (max-width:760px){
  .nav a.l{display:none}
  .nav .in{gap:10px}
  .nav .mk{gap:9px}
  .nav .wm{font-size:12.5px;letter-spacing:.07em;line-height:1.15;
    max-width:8ch;white-space:normal}
  .nav .btn{padding:12px 16px;font-size:14px;letter-spacing:.04em}
  /* clear of the home indicator and Safari's toolbar */
  .cue{bottom:calc(9vh + env(safe-area-inset-bottom, 0px));
    font-size:16px;letter-spacing:.14em}
}

/* ---------- the blue section, centred on a phone ---------- */
/* Everything in the hero is hung off a right edge on a desktop, which is a
   composition for a wide sheet. On a phone there is no wide sheet: the same
   hang leaves the eyebrow running off the side and the deck pinned to one
   rail. Upright, it all centres. */
@media (max-width:900px){
  .l-headline .hstack{align-items:center}
  .hero-deck{max-width:32ch;font-size:15px}

  .brow{white-space:normal;text-align:center;text-wrap:balance}
  /* padding-right:var(--pad), NOT 0: zeroing it (undoing the desktop's
     right-hang) left the layer 20px of pad on the left and none on the
     right, so "centred" sat 10px right of the true axis */
  .l-deck{align-items:center;justify-content:center;padding-right:var(--pad);text-align:center}
  .l-deck .deck-col{max-width:34ch;text-align:center;margin-inline:auto}
  /* balance, not pretty: a centred block with an uneven rag reads as
     off-centre even when its box is dead on the axis */
  .l-deck p,.l-deck .such{text-align:center;text-wrap:balance}
}

/* ---------- the footer, stacked and centred ---------- */
/* The ask, the address and the mark all sat on one line built for a desktop.
   Upright they stack, and the lockup centres with the mark under the name. */
@media (max-width:900px){
  .ft-stack{align-items:center;width:100%}
  .foot h2{text-align:center;width:100%}
  .ft-under{align-items:center;width:100%}
  .ft-line{text-align:center;max-width:32ch;margin-inline:auto}
  /* the ask and the address are two separate offers, not one row */
  .ft-cta{flex-direction:column;align-items:center;gap:18px;width:100%}
  .ft-cta .btn{width:min(100%, 320px);text-align:center}

  .foot-bar{flex-direction:column;text-align:center;gap:18px;
    justify-content:center}
  .fb-mk{flex-direction:column;margin-right:0;align-items:center;gap:10px}
  .fb-mk .wm{text-align:center}
  .fb-legal{width:100%;text-align:center;margin-inline:auto}
}

/* the mark under the name, printed in the page's own blue rather than slate */
.fb-mk{position:relative}
.fb-mk .fb-gl{display:block;width:calc(46 * var(--u));height:calc(46 * var(--u))}
.fb-mk .lsmark{display:none}

/* ---------- the deck on a phone ---------- */
/* It FADES in at the top centre and fades out again — no horizontal travel:
   a paragraph that slides while it is being read cannot be read, which is
   what the dealt-card entrance measured out to. It arrives later than it did
   (home.js holds it until the flood is under way) and it takes the desktop's
   own ink logic: slate on the paper, cream once the sheet has flooded, so it
   is never cream type on a cream ground. */
@media (max-width:900px){
  /* dead centre of the sheet, not parked under the bar */
  .l-deck{justify-content:center;padding-top:0}
  .l-deck .deck-col,.l-deck p,.l-deck .such{font-size:18.5px}
  .l-deck p,.l-deck .such{color:var(--slate)}
  body.is-flooded .l-deck p,body.is-flooded .l-deck .such{color:#F5F0E4}
}

/* ---------- the reel, flat, on a phone ---------- */
/* Real pictures instead of grains. Both sit in the shot's box; the site fades
   over the photograph once the project properly owns the screen, so the beat
   the desktop draws in particles still happens here, on the compositor. */
@media (max-width:900px){
  /* a scroll target for the turn, nothing more: the picture and the copy are
     both pinned, so this is the only thing in the row that moves */
  .work-row{position:relative}
  .turn-at{position:absolute;top:62%;left:0;width:1px;height:1px;pointer-events:none}
  .shot{overflow:clip;border-radius:calc(14 * var(--u));
    background:var(--paper2)}
  .shot img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
    display:block;transition:opacity .55s ease}
  .shot .flat-st{opacity:0}
  .shot.turned .flat-ph{opacity:0}
  .shot.turned .flat-st{opacity:1}
}
@media (max-width:900px) and (prefers-reduced-motion: reduce){
  .shot img{transition:none}
}

/* ---------- the picture takes the whole measure on a phone ---------- */
/* The full width between the margins, the same on both sides: the 46svh cap
   above exists for tablets, where a full-width crop would swallow the screen;
   on a phone it only ever narrowed the picture inside its own column. */
@media (max-width:599px){
  .shot{width:100%}
  .say{width:100%}
}

/* ---------- the skip link, first thing a keyboard reaches ---------- */
.skip{position:fixed;left:14px;top:10px;z-index:200;background:var(--slate);
  color:var(--paper);font-family:var(--dtext);font-weight:700;font-size:13px;
  letter-spacing:.06em;text-transform:uppercase;padding:12px 16px;
  transform:translateY(-250%);transition:transform .2s ease}
.skip:focus{transform:none}

/* ================= THE STILL PAGE (no-js / no-gl) =================
   The scroll-driven page told in stills. With scripting off (html.no-js) or
   WebGL refused / svh missing (html.no-gl), the same document lays out as
   full-bleed plates in the site's own inks: the cream open, the teal flood
   held as a solid sheet, OUR WORK on slate, the reel as big photographs
   (which still turn into the sites where JS runs), the brush mark as a
   printed watermark, and the crowd as the duotone photograph the particles
   are sampled from. Nothing here is a new element: it is the same markup,
   restyled. */

/* skeleton: the stages collapse, the empty canvases stand down */
html:is(.no-js,.no-gl) .lab{height:auto}
html:is(.no-js,.no-gl) .lab-sticky{position:static;height:auto;overflow:visible}
html:is(.no-js,.no-gl) #scene,
html:is(.no-js,.no-gl) .shrink,
html:is(.no-js,.no-gl) .grow-clip,
html:is(.no-js,.no-gl) .cue,
html:is(.no-js,.no-gl) .encore-btn,
html:is(.no-js,.no-gl) .works-rail,
html:is(.no-js,.no-gl) .works-open,
html:is(.no-js,.no-gl) .studio-gl,
html:is(.no-js,.no-gl) .foot-gl{display:none}

/* the bar stands on paper the whole way down: there is no knockout to own
   the letters over the plates below. And it is a BAR, not a masthead: the
   main page's 78px lockup floats on glass, but printed on a plate the same
   figure read as a 180px header, so the still bar takes a compact 44px
   lockup and tight pads. */
html:is(.no-js,.no-gl) .nav{background:var(--paper);border-bottom:1px solid #DDD7C8}
html:is(.no-js,.no-gl) .nav .in{padding-block:12px}
html:is(.no-js,.no-gl) .nav .lsmark,
html:is(.no-js,.no-gl) .nav .mk-gl{width:44px;height:44px}
html.no-js .nav .lsmark{visibility:visible}

/* 1 · the open: a full screen of display type on the stock */
html:is(.no-js,.no-gl) .layer{position:static;opacity:1;transform:none;
  display:block;padding:0}
html:is(.no-js,.no-gl) .l-headline{display:flex;min-height:88vh;
  flex-direction:column;justify-content:center;align-items:center;
  padding:calc(var(--bar-h,128px) + 4vh) var(--pad) 10vh}
html:is(.no-js,.no-gl) .l-headline .hstack{align-items:center}
html:is(.no-js,.no-gl) .l-headline h1{white-space:normal;text-align:center}
html:is(.no-js,.no-gl) .brow{white-space:normal;text-align:center}

/* 2 · the flood, held still: FORWARD standing light in the ink, the deck
   over it, and the clients the flyby streams — as a grid under a rule */
html:is(.no-js,.no-gl) .l-deck{opacity:1;transform:none;background:#34B2CE;
  position:relative;overflow:clip;padding:14vh var(--pad) 12vh;
  display:flex;flex-direction:column;align-items:center;justify-content:flex-start}
html:is(.no-js,.no-gl) .l-deck::before{content:'FORWARD';position:absolute;
  left:50%;top:9vh;transform:translateX(-50%);white-space:nowrap;
  font-family:var(--dcond);font-size:min(23vw,330px);line-height:.8;
  letter-spacing:var(--track);color:#F5F0E4;opacity:.16;pointer-events:none}
html:is(.no-js,.no-gl) .l-deck .deck-col{position:relative;max-width:30ch;
  margin:clamp(30px,7vh,90px) auto 0;
  text-align:center;font-size:clamp(19px,1.9vw,27px)}
html:is(.no-js,.no-gl) .l-deck p,
html:is(.no-js,.no-gl) .l-deck .such{color:#F5F0E4;text-align:center;
  font-size:clamp(19px,1.9vw,27px);line-height:1.5}
html:is(.no-js,.no-gl) .deck-logos{display:grid;position:relative;
  grid-template-columns:repeat(5,1fr);gap:7vh 4vw;align-items:center;
  justify-items:center;width:min(1100px,100%);margin:9vh auto 0;
  padding-top:9vh;border-top:1px solid rgba(245,240,228,.45)}
html:is(.no-js,.no-gl) .deck-logos img{max-height:36px;width:auto;
  max-width:100%;filter:brightness(0) invert(1);opacity:.92}
@media (max-width:760px){
  html:is(.no-js,.no-gl) .deck-logos{grid-template-columns:repeat(3,1fr);
    gap:5vh 6vw}
  html:is(.no-js,.no-gl) .deck-logos img{max-height:26px}
}

/* 3 · OUR WORK on the slate plate */
html:is(.no-js,.no-gl) .grow{position:static;opacity:1;height:auto;
  pointer-events:auto;background:var(--slate);padding:15vh var(--pad)}
html:is(.no-js,.no-gl) .grow-back{position:static;transform:none;display:block;
  text-align:center}
html:is(.no-js,.no-gl) .grow-back .wo-brow,
html:is(.no-js,.no-gl) .grow-back h2{color:#F5F0E4}
html:is(.no-js,.no-gl) .grow-back .wo-say{color:#B9C6D9;opacity:1;
  margin-inline:auto}

/* 4 · the reel: the photograph and the site it became SIDE BY SIDE, the
   pair the animated page shows as one picture turning into the other */
html:is(.no-js,.no-gl) .work-row,
html:is(.no-js,.no-gl) .work-row:nth-child(even){display:block;min-height:0;
  position:relative;max-width:var(--wrap);margin-inline:auto;
  padding:9vh var(--pad) 5vh}
html:is(.no-js,.no-gl) .turn-at{position:absolute;top:62%;left:0;width:1px;
  height:1px;pointer-events:none}
html:is(.no-js,.no-gl) .shot{position:relative;top:auto;display:grid;
  grid-template-columns:1fr 1fr;gap:14px;width:100%;aspect-ratio:auto;
  margin:0 0 clamp(22px,3.5vh,34px);overflow:visible;border-radius:0;
  background:none}
html:is(.no-js,.no-gl) .shot img{position:static;width:100%;height:100%;
  object-fit:cover;display:block;aspect-ratio:1.164;border-radius:14px;
  opacity:1;transition:none}
html:is(.no-js,.no-gl) .shot.turned .flat-ph,
html:is(.no-js,.no-gl) .shot .flat-st{opacity:1}
@media (max-width:599px){
  html:is(.no-js,.no-gl) .shot{grid-template-columns:1fr}
}
html:is(.no-js,.no-gl) .say{position:static;width:100%;min-height:0;margin:0;
  display:block;text-align:left}
html:is(.no-js,.no-gl) .work-row:nth-child(even) .say{text-align:left}
html:is(.no-js,.no-gl) .work-row:nth-child(even) .say .svc{justify-content:flex-start}
html:is(.no-js,.no-gl) .work-row:nth-child(even) .say .desc,
html:is(.no-js,.no-gl) .work-row:nth-child(even) .say .credit{margin-left:0}
html:is(.no-js,.no-gl) .work-row:last-child .shot{margin-bottom:clamp(22px,3.5vh,34px)}
html:is(.no-js,.no-gl) .work-row:last-child .say{margin-bottom:0;padding-bottom:6vh}

/* 5 · the studio: the BRUSH mark (the site's own, rasterised from
   lsmark.js with its tint pre-composed on the paper) printed as a watermark
   off the right edge */
/* the section paints its OWN paper: it carries z-index 5, which isolates
   its stacking context, so the multiplied watermark below can only blend
   against what the section itself paints — against nothing, the image's
   white ground printed as a raw white box */
html:is(.no-js,.no-gl) .studio{min-height:0;position:relative;overflow:clip;
  background:var(--paper)}
html:is(.no-js,.no-gl) .studio::before{content:'';position:absolute;inset:0;
  /* as large as the section can hold WITHOUT cropping the stroke: the full
     LS stands inside the box (the spray still bleeds off the right edge via
     the position), and the viewport cap keeps a tall stacked section from
     building a mark twice the screen */
  background:url(img/lsmark-wm.jpg) no-repeat calc(100% - 9vw) center
    / auto min(100%, 116vw);
  /* the tint is printed on WHITE and multiplied onto the paper: white times
     paper is exactly paper, so the image's rectangle has no visible edge */
  mix-blend-mode:multiply;pointer-events:none}
html:is(.no-js,.no-gl) .studio-in{min-height:0;position:relative;display:block;
  padding:15vh var(--pad);max-width:var(--wrap);margin-inline:auto}
html:is(.no-js,.no-gl) .studio-copy{margin:0;width:min(640px,100%);transform:none}
html:is(.no-js,.no-gl) .studio-copy .st-brow,
html:is(.no-js,.no-gl) .studio-copy h2,
html:is(.no-js,.no-gl) .studio-copy .st-p{opacity:1;transform:none}

/* 6 · the close: the crowd exactly as the animated page draws it — a
   capture of the particle render, its ground this same paper — standing as
   the BACKDROP of the whole stage, feet on the page's foot, with the ask
   printing over its upper reach: the animated page's own composition. The
   stage is sized off the viewport, so the crowd holds its scale on a phone
   instead of shrinking into a strip. */
html:is(.no-js,.no-gl) .foot-stage{position:relative;display:block;
  min-height:min(94vh,130vw);padding:0;overflow:clip;
  background:var(--paper)}   /* the multiply below needs painted ground */
html:is(.no-js,.no-gl) .foot-say{position:relative;z-index:2;
  padding:12vh var(--pad) 0}
html:is(.no-js,.no-gl) .foot h2{white-space:normal}
html:is(.no-js,.no-gl) .foot-stage::after{content:'';position:absolute;
  inset:0;z-index:1;display:block;width:auto;height:auto;
  /* NO blend mode: the capture's own ground is this paper (multiply squared
     it into a visibly darker plate); its edges are feathered in the file */
  background:url(img/still-crowd.jpg) no-repeat
    right calc(-1 * min(4vw,40px)) bottom 0 / auto min(76%,118vw)}

@media (prefers-reduced-motion: reduce){
  html:is(.no-js,.no-gl) .shot img{transition:none}
}

/* ---------- focus, visible in the brand's own ink ---------- */
/* :focus-visible so pointer clicks stay clean while every keyboard stop
   prints the signal orange, which reads on the paper, the teal and the
   slate alike. The popup's fields carry their own ring in talk.css. */
a:focus-visible,button:focus-visible{
  outline:3px solid var(--signal);outline-offset:3px}

/* ---------- the clients: every mark on one press sheet ---------- */
/* Every mark normalized to the same slate ink at build time (no filters,
   no per-logo scales): a small shared max-width and a taller max-height,
   in fixed-height cells, so a round badge and a wide wordmark carry the
   same weight and centre on the same line.
   The reveal is a PRESS PASS: a diagonal wave crosses the sheet and each
   mark is wiped onto the paper top-to-bottom (clip-path) with a breath of
   settle, the way a roller lays ink. Delays ride the wave (row + column,
   hashed jitter), set per-item in the markup. */
.clients{position:relative;z-index:5;padding:9vh 0 4vh}
/* THE DWELL: once the sheet has printed it PINS, fully resolved, for a
   stretch of scroll before releasing toward the studio — the same hold
   the reel's pictures get. The section carries the extra runway; the
   sheet rides sticky inside it. Desktop only: the stacked sheet is
   taller than a phone's screen and a pin there would hide its foot. */
@media (min-width:901px){
  /* dwell runway trimmed: the sheet still holds after printing, but the
     studio's mark arrives a screen-third sooner behind it */
  .clients{padding-bottom:calc(4vh + 22vh)}
  .cl-in{position:sticky;top:9vh}
}
.cl-in{max-width:var(--wrap);margin-inline:auto;padding:0 var(--pad)}
.cl-brow{margin:0 0 12px;font-family:var(--dtext);font-weight:700;
  text-transform:uppercase;font-size:clamp(11px,1.02vw,calc(14 * var(--u)));
  letter-spacing:.2em;color:var(--slate)}
/* small and centred: a label over the sheet, not a display moment —
   the marks are the show here */
.clients h2{margin:0 0 clamp(24px,4vh,calc(44 * var(--u)));
  font-family:var(--dtext);font-weight:700;text-transform:uppercase;
  font-size:clamp(12px,1.1vw,calc(15.4 * var(--u)));letter-spacing:.24em;
  text-align:center;color:var(--slate)}
.cl-stage{position:relative}
.cl-grid{margin:0;padding:0;list-style:none;
  /* FLEX, centred: the column count follows the width on its own and a
     short last row centres its marks instead of leaving one hanging off
     the corner of a rigid grid */
  display:flex;flex-wrap:wrap;justify-content:center;
  gap:clamp(14px,2.4vh,calc(28 * var(--u))) clamp(14px,2vw,calc(26 * var(--u)))}
.cl-grid li{display:grid;place-items:center;height:calc(56 * var(--u));
  width:calc(150 * var(--u));
  /* hidden until its grains land; the fade matches the grains' own fade
     out, so the arrival and the image cross on the same beat */
  opacity:0;transition:opacity .34s linear}
.cl-grid li.on{opacity:1}
/* half-strength ink at rest, full under the pointer (fade by request) */
.cl-grid img{max-width:calc(120 * var(--u));max-height:calc(42 * var(--u));
  width:auto;height:auto;object-fit:contain;opacity:.62;
  transition:transform .22s ease,opacity .22s ease}
/* compact and portrait marks (badges, monograms, stacked lockups) get a
   taller ceiling so they carry the same weight as the wordmarks */
.cl-grid img.sq{max-height:calc(58 * var(--u))}
.cl-grid li:hover img{transform:scale(1.07);opacity:1}
@media (max-width:900px){
  .clients{padding:7vh 0 4vh}
  .cl-grid{gap:12px 10px}
  .cl-grid li{height:44px;width:104px}
  .cl-grid img{max-width:92px;max-height:32px}
  .cl-grid img.sq{max-height:44px}
}
@media (prefers-reduced-motion:reduce){
  .cl-grid li{opacity:1}
}
html:is(.no-js,.no-gl) .clients{display:none}
