/* ============================================================
   GENERATED — do not edit this file.
   It is the homepage's own stylesheet, copied out of index.html
   by _build/build.mjs so every other page shares the approved
   design exactly. Change index.html and rebuild.
   ============================================================ */
/* ============================================================
   TOKENS
   Palette sampled from the Connecting Digitally brochure PDF.
   Magenta #DD1267 exact. Canvas pushed toward violet so the page
   and the magenta-light footage read as one place.
   ============================================================ */
/* ============================================================
   THE TONE SYSTEM

   The page is built out of full-bleed colour blocks rather than one
   ground with things on it. Three tones take turns: the brand pink
   flooding an entire screen, a warm bone, and the near-black.

   The trick that makes this cheap: the tones redefine the SAME token
   names the components already use, so every grid, card, form and
   list on the page becomes tone-aware without a single change to its
   own rules. A button on pink knows to go black; a hairline on bone
   knows to go dark. One declaration block per tone, not one per
   component.
   ============================================================ */
:root{
  --ink:#0C0913;      /* their near-black */
  --bone:#F3EFE9;     /* a warm off-white, not a grey */
  --bone-2:#E9E3D9;
  --flood:#DD1267;    /* their rani pink, used as a ground and not an accent */
  --flood-2:#B60E54;
  --paper:#FFFFFF;

  --canvas:#0C0913;
  --canvas-2:#0A0810;
  --panel:#16111F;
  --panel-2:#1E1729;
  --accent:#DD1267;
  --accent-hover:#FF3B84;
  --accent-muted:rgba(221,18,103,.22);
  --accent-glow:rgba(221,18,103,.55);

  --text-primary:#F4F1F6;   /* 17.6:1 on canvas */
  --text-secondary:#A39BAE; /*  7.4:1 on canvas */
  --text-faint:#8C8399;     /*  5.2:1 on canvas, so it is safe for small text too */

  --line:rgba(244,241,246,.09);   /* decorative hairlines */
  --border-ui:#6E6579;            /* interactive borders, 3.35:1 on panel */

  --ease-out:cubic-bezier(.16,1,.3,1);
  --ease-io:cubic-bezier(.65,0,.35,1);

  /* one grain tile, reused everywhere. it is the single cheapest thing
     that separates a premium studio surface from a plain CSS gradient */
  --grain:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
  /* the dot matrix that gives the void depth */
  --dots:radial-gradient(circle at 1px 1px,rgba(244,241,246,.16) 1px,transparent 0);

  --tshadow:0 1px 2px rgba(5,5,10,.95),0 3px 12px rgba(5,5,10,.78),0 10px 44px rgba(5,5,10,.8);

  --gutter:clamp(18px,5vw,72px);
  --spine-x:44px;

  --display:'Archivo',system-ui,-apple-system,sans-serif;
  --body:'Manrope',system-ui,-apple-system,sans-serif;
  --mono:'JetBrains Mono',ui-monospace,'SF Mono',Menlo,monospace;
}

/* ---- the three tones ----
   Contrast measured, not assumed. White on the pink is 4.84:1, which
   carries body text; the near-black on the pink is 3.98:1, which is
   display sizes only, so it is the accent and never the copy. */
[data-tone]{background:var(--canvas);color:var(--text-primary)}

[data-tone="ink"]{
  --canvas:#0C0913; --canvas-2:#08060E; --panel:#16111F; --panel-2:#1E1729;
  --text-primary:#F3EFE9; --text-secondary:#A79FB0; --text-faint:#8C8399;
  --line:rgba(243,239,233,.11); --border-ui:#6E6579;
  --accent:#DD1267; --accent-hover:#FF3B84;
  --btn-bg:#DD1267; --btn-fg:#FFFFFF; --btn-bg-h:#FF3B84;
  --veil:rgba(243,239,233,.05);
}
[data-tone="bone"]{
  --canvas:#F3EFE9; --canvas-2:#E9E3D9; --panel:#FFFFFF; --panel-2:#EFE9E0;
  --text-primary:#0C0913; --text-secondary:#5B5366; --text-faint:#6B6376;
  --line:rgba(12,9,19,.14); --border-ui:#B7AFA4;
  --accent:#C00E57; --accent-hover:#DD1267;
  --btn-bg:#0C0913; --btn-fg:#F3EFE9; --btn-bg-h:#DD1267;
  --veil:rgba(12,9,19,.045);
}
[data-tone="flood"]{
  --canvas:#DD1267; --canvas-2:#C40F5B; --panel:#C40F5B; --panel-2:#B60E54;
  --text-primary:#FFFFFF; --text-secondary:rgba(255,255,255,.82); --text-faint:rgba(255,255,255,.72);
  --line:rgba(255,255,255,.26); --border-ui:rgba(255,255,255,.62);
  --accent:#0C0913; --accent-hover:#0C0913;
  --btn-bg:#0C0913; --btn-fg:#FFFFFF; --btn-bg-h:#FFFFFF;
  --veil:rgba(255,255,255,.09);
}

/* the grain and dot overlays are built out of light; on a light ground
   they have to invert or they simply vanish */
[data-tone="bone"]{--dots:radial-gradient(circle at 1px 1px,rgba(12,9,19,.15) 1px,transparent 0)}
[data-tone="flood"]{--dots:radial-gradient(circle at 1px 1px,rgba(255,255,255,.22) 1px,transparent 0)}
[data-tone="bone"] .grain,[data-tone="bone"] .grainover{opacity:.05;mix-blend-mode:multiply}

/* ---- THE DISPLAY VOICE ----
   Archivo carries a width axis from 62 to 125. The page has been using
   the expanded end; the giant type now uses the condensed end instead.
   Same typeface, opposite register: tall, tight, and packed, so a
   sentence can run at 200px and still hold a line. */
.mega{
  font-family:var(--display);
  /* 62 is the narrow end of Archivo's own width axis. Measured: the same
     word runs 759px here against 1471px at the wide end, so this is a
     genuinely different cut of their existing typeface rather than a
     second font download. */
  font-variation-settings:'wdth' 62,'wght' 800;
  font-weight:800;
  font-size:clamp(60px,14.6vw,252px);
  line-height:.84;
  letter-spacing:-.032em;
  text-transform:uppercase;
  margin:0;
}
.mega--tight{line-height:.80;letter-spacing:-.042em}
.big{
  font-family:var(--display);
  font-variation-settings:'wdth' 74,'wght' 800;
  font-weight:800;
  font-size:clamp(38px,6.6vw,104px);
  line-height:.9;letter-spacing:-.032em;text-transform:uppercase;margin:0;
}

/* ============================================================
   THE TYPE HERO
   Two enormous words with the argument set small between them, and
   the mark held in a white pocket at the side. No canvas here: the
   type IS the picture.
   ============================================================ */
.thero{position:relative;min-height:100svh;display:flex;flex-direction:column;justify-content:center;
  padding:clamp(104px,14vh,180px) var(--gutter) clamp(48px,8vh,96px);overflow:hidden}
.thero-top{display:flex;align-items:flex-start;justify-content:space-between;gap:clamp(16px,4vw,64px)}
.thero .mega{position:relative;z-index:2}

/* ---- THE HERO: TYPE AS IMAGE ----
   Depth without an object. Three layers lean against the pointer at
   three different rates, which is all parallax has ever been, and it
   paints on the first frame instead of waiting for a GPU context. */
.thero{--px:0;--py:0}

/* the brand word, enormous, cropped by the frame, almost invisible.
   It is the backdrop and the tie back to the opening sequence. */
/* The whole word, or none of it. Sized off the viewport width so
   CONNECTING always reads end to end instead of being cropped to a
   meaningless fragment; the 10 characters at this width work out to
   roughly 9.4vw each. It is cropped vertically, never horizontally. */
.wordbg{
  position:absolute;z-index:0;pointer-events:none;user-select:none;
  left:50%;bottom:-6%;
  font-family:var(--display);font-variation-settings:'wdth' 62,'wght' 800;font-weight:800;
  font-size:min(15.6vw,240px);line-height:.8;letter-spacing:-.04em;text-transform:uppercase;
  /* barely there. It is a texture in the room, not a second headline. */
  color:var(--text-primary);opacity:.03;white-space:nowrap;
  transform:translate3d(calc(-50% + var(--px) * -22px),calc(var(--py) * -12px),0);
  transition:transform .7s cubic-bezier(.22,1,.36,1);
}

.thero-in{position:relative;z-index:2;max-width:min(92ch,92vw)}

/* ---- the two columns ----
   The headline takes its size from the column it actually has, not from
   the window, which is why it steps down here: sharing the screen with
   the object is the composition, so the type is set to fit it rather
   than the object being shrunk to fit the type. */
/* ---- the animation, behind everything ----
   The canvas is the whole screen. The type sits on the left of it with
   its own gradient underneath, so the headline gets full contrast while
   the right-hand half of the frame — where the field, the network and
   the mark actually live — stays untouched. */
.thero > .hero3d,.thero > .scrim,.thero > .vignette,.thero > .readside{z-index:0}
.readside{
  position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(100deg,rgba(9,7,15,.94) 0%,rgba(9,7,15,.84) 26%,rgba(9,7,15,.42) 50%,rgba(9,7,15,0) 70%);
}

/* ---- the strip along the bottom of the hero ---- */
.heroclients{
  position:absolute;z-index:3;left:0;right:0;
  bottom:clamp(18px,3.2vh,40px);
  padding:16px 0 0;
  border-top:1px solid var(--line);
  margin-inline:var(--gutter);
  display:flex;align-items:baseline;justify-content:space-between;
  flex-wrap:wrap;gap:8px clamp(18px,3vw,44px);
}
.hc-side{display:flex;align-items:baseline;flex-wrap:wrap;gap:6px clamp(14px,2vw,30px)}
/* a dot between the items rather than a pipe: a pipe reads as a divider,
   a dot reads as a list. The label is not part of that list. */
.hc-side > a + a::before,.hc-side > a + span::before{content:"·";margin-right:clamp(14px,2vw,30px);color:var(--accent);opacity:.7}
.heroclients .hc-l{
  font:500 9.5px/1 var(--mono);letter-spacing:.22em;text-transform:uppercase;
  color:var(--accent);
}
.heroclients .hc-n{
  font:500 11.5px/1.4 var(--mono);letter-spacing:.12em;text-transform:uppercase;
  color:var(--text-secondary);opacity:.82;white-space:nowrap;text-decoration:none;
  transition:color .3s var(--ease-out),opacity .3s var(--ease-out);
}
a.hc-n:hover{color:var(--text-primary);opacity:1}
.hc-side--r a.hc-n{color:var(--text-secondary)}
/* it is a footnote to the hero, not a second list: below the tablet
   breakpoint the screen is needed for the headline and the buttons */
@media (max-width:980px){ .heroclients{display:none} }
@media (max-height:720px) and (min-width:981px){ .heroclients{display:none} }
@media (max-width:980px){
  /* on a phone the type is centred over the frame rather than beside it,
     so the dimming has to come from the bottom instead of the side */
  .readside{background:linear-gradient(to bottom,rgba(9,7,15,.55) 0%,rgba(9,7,15,.30) 30%,rgba(9,7,15,.78) 74%,rgba(9,7,15,.94) 100%)}
}
@media (min-width:981px){
  /* back to the page's own gutter: pulled hard left the block lost its
     relationship with the nav and the sections under it, and the screen
     stopped reading as one composition */
  .thero-in{max-width:none;width:100%;margin-inline:0}
  .thero-col{min-width:0;max-width:min(56%,720px)}
}

.thero-head{
  margin:clamp(16px,2.6vh,30px) 0 0;
  transform:translate3d(calc(var(--px) * 9px),calc(var(--py) * 6px),0);
  transition:transform .7s cubic-bezier(.22,1,.36,1);
}

/* Every pair occupies the same grid cell. The block is therefore always
   as tall as the tallest pair, whatever is showing, so a change cannot
   move the index or the buttons underneath it by a single pixel. */
.hpairs{display:grid}
.hpair{grid-area:1/1;display:block}
.thero-head .hl{
  display:block;white-space:nowrap;font-style:normal;
  font-family:var(--display);font-variation-settings:'wdth' 62,'wght' 800;font-weight:800;
  font-size:clamp(34px,5.45vw,96px);line-height:.88;letter-spacing:-.035em;text-transform:uppercase;
}
.thero-head .hl--pop{color:var(--accent)}

/* The change is a relay, not a crossfade. Run as a crossfade the two
   pairs are both half-legible for a third of a second, stacked in the
   same cell, and the result reads as a smudge rather than as a change.
   The outgoing pair lifts and clears first; the incoming one starts a
   quarter of a second later, from below, second line a beat behind the
   first. Opacity and transform only, so no frame of it costs layout. */
.hpair{pointer-events:none}
.hpair .hl{
  opacity:0;transform:translate3d(0,.42em,0);
  transition:opacity .48s var(--ease-out),transform .62s cubic-bezier(.22,1,.36,1);
}
.hpair.is-on .hl{opacity:1;transform:none;transition-delay:.24s}
.hpair.is-on .hl:nth-child(2){transition-delay:.31s}
.hpair.is-out .hl{
  opacity:0;transform:translate3d(0,-.30em,0);
  transition-duration:.30s,.36s;transition-delay:0s;
}
.hpair.is-out .hl:nth-child(2){transition-delay:.05s}

/* the services, set as an index rather than as cards */
.disc{
  list-style:none;margin:clamp(20px,3vh,36px) 0 0;padding:0;
  display:flex;flex-wrap:wrap;gap:6px 18px;
  transform:translate3d(calc(var(--px) * 4px),calc(var(--py) * 3px),0);
  transition:transform .7s cubic-bezier(.22,1,.36,1);
}
.disc li{
  display:flex;align-items:baseline;gap:7px;
  font:500 11.5px/1.4 var(--mono);letter-spacing:.14em;text-transform:uppercase;
  color:var(--text-faint);transition:color .55s var(--ease-out);
}
.disc li b{font-size:9.5px;opacity:.6;font-weight:500}
.disc li.on{color:var(--text-primary)}
.disc li.on b{color:var(--accent);opacity:1}

@media (prefers-reduced-motion: reduce){
  .wordbg,.thero-head,.disc,.loopwrap{transition:none}
  .hpair,.hpair .hl{transition:none}
}
.btn--big{padding:17px 30px;font-size:15px}

@media (max-width:980px){
  .thero{justify-content:flex-end}
  .thero-in{max-width:none}
  /* on a phone the word cannot be both legible and subtle at any size
     that fits, so it goes and the animation carries the frame instead */
  .wordbg{display:none}
  /* the type sits low so the animation has the top two thirds of the
     screen to itself, which is where it is actually happening */
  .thero{padding-bottom:clamp(30px,5vh,60px)}
}

/* the pocket: the reference holds an illustration in a white blob, so
   this holds their actual mark, turning slowly on its own */
.pocket{
  flex:0 0 auto;width:clamp(110px,17vw,242px);aspect-ratio:7/5;
  background:var(--paper);border-radius:999px;
  display:grid;place-items:center;margin-top:clamp(6px,1.4vw,22px);
  box-shadow:0 30px 80px -40px rgba(0,0,0,.5);
  animation:pocketfloat 7s var(--ease-io) infinite;
}
/* the mark does NOT spin. It is a logo, and a logo caught mid-rotation
   reads as a different letterform. It breathes instead. */
.pocket img{width:52%;height:auto}
@keyframes pocketfloat{0%,100%{transform:translateY(0) rotate(-1.2deg)}50%{transform:translateY(-10px) rotate(1.2deg)}}
body.paused .pocket{animation-play-state:paused}

/* ---- the statement, lit word by word as it is scrolled past ----
   Each word carries its own place in the line, and the block carries
   how far the reader has come. The whole effect is one multiply in
   CSS: no per-word write from script, no layout thrash. */
.statement{
  margin:clamp(26px,5vh,56px) 0;max-width:min(92vw,1180px);
  font-family:var(--display);font-variation-settings:'wdth' 96,'wght' 600;font-weight:600;
  font-size:clamp(21px,3.1vw,46px);line-height:1.22;letter-spacing:-.018em;
}
.statement .w{display:inline-block;white-space:pre}
.statement .w > i{
  font-style:normal;
  --lit:clamp(0,(var(--p,0) - var(--wi,0)) * 7,1);
  color:color-mix(in srgb,var(--text-primary) calc(22% + 78% * var(--lit)),transparent);
  transition:color .18s linear;
}
@supports not (color: color-mix(in srgb,red,blue)){
  .statement .w > i{color:var(--text-primary);opacity:calc(.3 + .7 * var(--lit))}
}
.thero-foot{display:flex;align-items:center;justify-content:space-between;gap:24px;flex-wrap:wrap;margin-top:clamp(26px,4vh,46px)}
.thero-foot .cta-row{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.scrollcue{font:500 10.5px/1 var(--mono);letter-spacing:.2em;text-transform:uppercase;color:var(--text-secondary);display:flex;align-items:center;gap:10px}
.scrollcue i{display:block;width:34px;height:1px;background:currentColor;transform-origin:left;animation:cue 2.4s var(--ease-io) infinite}
@keyframes cue{0%,100%{transform:scaleX(.3)}50%{transform:scaleX(1)}}

/* ---- the hero on a phone ---- */
@media (max-width:860px){
  .thero{justify-content:center;padding-top:clamp(92px,13vh,140px);padding-bottom:clamp(26px,4.5vh,54px)}
  /* Sized off the longest line in the set, CONVERSIONS ARE NOT., which
     at this width decides the size for all seven. 11.4vw fits a 390px
     phone with four pixels to spare and overflows a 320px one by one,
     so it is set here at the width that still clears on the narrowest
     screen anybody is still using. */
  .thero-head .hl{font-size:clamp(30px,11.0vw,68px);line-height:.9}
  .thero .chip{margin-bottom:16px}
  .disc{gap:5px 14px;margin-top:clamp(18px,2.6vh,30px)}
  .disc li{font-size:10.5px;letter-spacing:.11em}
  .thero-foot{margin-top:clamp(22px,3.6vh,40px);flex-direction:column;align-items:stretch;gap:16px}
}

/* ---- the buttons, fixed at the cause ----
   Two pill buttons side by side stop fitting somewhere around 380px and
   silently wrap, which reads as a mistake rather than a decision, and a
   longer label makes it worse. Below 560px they become full-width and
   deliberately stacked instead. Nothing is shrunk to make it fit: the
   label stays the size it should be and the layout changes around it. */
@media (max-width:560px){
  .thero-foot .cta-row{flex-direction:column;align-items:stretch;gap:10px;width:100%}
  .thero-foot .cta-row .btn{width:100%;justify-content:center}
  .thero-foot{align-items:stretch}
  .scrollcue{justify-content:flex-start}
}
/* and the label itself never breaks mid-phrase */
.btn{white-space:nowrap}
@media (max-width:560px){ .btn{white-space:normal} }

/* ============================================================
   THE BAND: one wide statement holding a whole screen
   ============================================================ */
.band-say{min-height:64svh;display:grid;place-items:center;text-align:center;padding:clamp(72px,12vh,150px) var(--gutter)}
.band-say .mega{max-width:22ch}

/* ============================================================
   THE HORIZONTAL RAIL
   A pinned screen the work travels across sideways while the page
   scrolls down. On a phone it is a plain swipeable row instead:
   hijacking a thumb's vertical scroll to move things sideways is
   the fastest way to make a phone feel broken.
   ============================================================ */
.rail-sec{position:relative}
/* the bar is fixed over the top of this, so the pinned screen starts
   below it rather than underneath it */
.rail-pin{position:sticky;top:0;height:100svh;overflow:hidden;display:flex;flex-direction:column;justify-content:center;
  padding-top:clamp(58px,8vh,92px)}
.rail-head{padding:0 var(--gutter) clamp(16px,2.6vh,30px);display:flex;align-items:baseline;justify-content:space-between;gap:20px;flex-wrap:wrap}
.rail-head .big{font-size:clamp(30px,4.4vw,66px)}
.rail-track{display:flex;gap:clamp(16px,2.4vw,38px);padding-inline:var(--gutter);will-change:transform}
.rail-card{
  flex:0 0 auto;width:clamp(196px,23.5vw,332px);text-decoration:none;color:inherit;
}
/* these are spans, and aspect-ratio does nothing to an inline box, so
   they are told to be blocks before they are told to be 4:5 */
.rail-card .shot{
  display:block;
  position:relative;aspect-ratio:4/5;border-radius:16px;overflow:hidden;
  background:var(--panel);border:1px solid var(--line);
}
.rail-card .shot img{width:100%;height:100%;object-fit:cover;transition:transform 1.1s var(--ease-out)}
.rail-card:hover .shot img{transform:scale(1.05)}
.rail-card .cap{display:flex;align-items:baseline;justify-content:space-between;gap:14px;margin-top:14px}
.rail-card .nm{font-family:var(--display);font-variation-settings:'wdth' 82,'wght' 800;font-weight:800;
  font-size:clamp(17px,1.7vw,27px);letter-spacing:-.01em;text-transform:uppercase;line-height:1}
.rail-card .cat{font:500 10px/1.4 var(--mono);letter-spacing:.17em;text-transform:uppercase;color:var(--text-secondary);text-align:right;max-width:13ch}
.rail-prog{margin:clamp(18px,3vh,34px) var(--gutter) 0;height:2px;background:var(--line);position:relative;overflow:hidden;border-radius:2px}
.rail-prog i{position:absolute;inset:0;background:var(--text-primary);transform-origin:left;transform:scaleX(0)}

@media (max-width:860px){
  .rail-sec{height:auto!important}
  .rail-pin{position:static;height:auto;padding-block:clamp(56px,9vh,96px)}
  .rail-track{overflow-x:auto;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;
    transform:none!important;padding-bottom:6px;scrollbar-width:none;
    /* without this the snap point aligns the first card to the very edge
       of the scroll port and swallows the gutter, so the card arrives
       already clipped */
    scroll-padding-inline:var(--gutter)}
  .rail-track::-webkit-scrollbar{display:none}
  .rail-card{width:min(74vw,320px);scroll-snap-align:start}
  .rail-prog{display:none}
}

/* ---- WHERE A NAV JUMP LANDS ----
   The header is fixed and 79px tall, so a bare hash jump puts the top of
   the target underneath it. Every nav destination reserves that height
   plus a little air.

   This was true of all five links from the start and simply did not show:
   the big sections carry enough top padding that the part hidden behind
   the bar was empty anyway. The clients band is a heading with a marquee
   under it and nothing to spare, so it landed straight behind the bar and
   made the existing bug visible. */
#services,#work,#clients,#about,#start{scroll-margin-top:96px}

/* ============================================================
   THE OPENING
   A pink panel over a page that is already finished, holding one word
   in ten languages. It lifts as a curtain: the whole panel slides up on a
   transform and the word rides up a little faster inside it. Nothing here
   touches layout.
   ============================================================ */
.intro{
  position:fixed;inset:0;z-index:200;
  background:var(--flood);color:#fff;
  display:grid;place-items:center;
  /* ---- THE CURTAIN SLIDES, IT DOES NOT GET CLIPPED ----
     This was clip-path:inset(0 0 100% 0), which never moved anything: the
     panel stayed exactly where it was and its bottom edge was cut upward
     until nothing was left. The pink vanished in place and the name sat
     still while it happened, which is why it read as a cut rather than as
     an exit, however smooth the curve was.

     A transform moves the whole layer — panel, name and sub-line together
     — up and off the top of the screen, uncovering the page underneath as
     it goes. Same duration, same easing curve as before. It also runs on
     the compositor rather than repainting the panel every frame, so it
     stays smooth on a phone while the hero is starting up behind it. */
  transform:translateY(0);
  /* 0.8s, matched to the reference reveal. At 1s the curtain had a slack
     patch in the middle of its travel; 0.8s on this curve keeps the whole
     movement under tension from the first frame to the last, which is what
     reads as deliberate rather than merely slow. The curve itself is
     unchanged — it was already the same one. */
  transition:transform .8s cubic-bezier(.76,0,.24,1);
  will-change:transform;
  /* a script-free page never sees this panel at all */
  display:none;
}
.js .intro{display:grid}
.intro-in{text-align:center;padding-inline:var(--gutter);
  /* the same .8s as the panel, so the name and the sheet carrying it come
     to rest on the same frame instead of one trailing the other */
  transition:transform .8s cubic-bezier(.76,0,.24,1),opacity .44s ease-out}
.intro-word{
  margin:0;font-family:var(--display);font-variation-settings:'wdth' 62,'wght' 800;font-weight:800;
  /* Sized off the LONGEST state, which is the name at the end, and held
     on one line throughout. Sizing it off "Connecting" and letting the
     name shrink to fit would have changed scale on the last beat, which
     reads as a fault rather than as an arrival. */
  font-size:clamp(38px,8.8vw,142px);line-height:.9;letter-spacing:-.03em;white-space:nowrap;
  /* the non-Latin scripts fall through to whatever the device has, and
     every one of them is checked for a real glyph before it is used */
  font-family:'Archivo',system-ui,'Noto Sans','Noto Sans Bengali','Noto Sans Devanagari','Noto Sans Arabic','Noto Sans CJK SC',sans-serif;
  min-height:1.1em;
}
.intro-sub{
  margin:clamp(14px,2.4vh,26px) 0 0;font:500 10.5px/1 var(--mono);
  letter-spacing:.24em;text-transform:uppercase;color:rgba(255,255,255,.72);
}
.intro.lift{transform:translateY(-100%)}
/* the name rides up slightly faster than the panel carrying it, which is
   what gives the exit its depth. The opacity:0 that used to be here has
   gone: fading the name out mid-slide is the "simply disappears" part —
   it should stay legible the whole way up and leave with the panel. */
.intro.lift .intro-in{transform:translateY(-18%)}
/* the page behind settles the last few percent as the curtain goes up */
.introing{overflow:hidden}
.reveal-scale{transform:scale(1.045);transition:transform .98s cubic-bezier(.76,0,.24,1)}
.reveal-scale.done{transform:none}

/* ============================================================
   THE SIX MOVEMENTS
   A pinned screen where the giant word changes as you scroll and the
   real work is named underneath it. Only one movement is ever drawn;
   the rest are stacked on top of each other at zero opacity, so the
   whole sequence costs one screen of paint however many there are.
   ============================================================ */
.mvsec{position:relative}
.mv-pin{position:sticky;top:0;height:100svh;overflow:hidden;
  display:flex;flex-direction:column;justify-content:center;
  padding:clamp(72px,10vh,120px) var(--gutter) clamp(28px,5vh,60px)}
.mv-top{display:flex;align-items:center;gap:clamp(14px,2.4vw,34px);margin-bottom:clamp(14px,2.4vh,30px)}
.mv-count{margin:0;font:500 11px/1 var(--mono);letter-spacing:.2em;color:var(--text-secondary);display:flex;align-items:baseline;gap:8px;flex:0 0 auto}
.mv-count i{font-family:var(--display);font-variation-settings:'wdth' 100,'wght' 800;font-style:normal;
  font-size:clamp(22px,2.6vw,34px);line-height:1;color:var(--accent);letter-spacing:-.02em}
.mv-meter{flex:1;height:2px;background:var(--line);border-radius:2px;overflow:hidden}
.mv-meter i{display:block;height:100%;background:var(--accent);transform-origin:left;transform:scaleX(0)}

.mv-stack{position:relative;flex:1;min-height:0}
.mv{
  position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;
  opacity:var(--a,0);
  /* the outgoing word sinks, the incoming one rises: they never cross */
  transform:translate3d(0,calc((1 - var(--a,0)) * var(--dir,1) * 42px),0);
  pointer-events:none;
}
.mv[style*="--a:1"],.mv.live{pointer-events:auto}
.mv .mega{font-size:clamp(52px,11.4vw,196px);margin-bottom:clamp(12px,2vh,26px)}
.mv-list{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:8px 10px}
.mv-list li{
  font:500 12.5px/1 var(--mono);letter-spacing:.11em;text-transform:uppercase;
  border:1px solid var(--line);border-radius:999px;padding:9px 14px;color:var(--text-primary);
  /* each one arrives a beat after the last */
  opacity:clamp(0,(var(--a,0) - .18) * 3,1);
  transform:translate3d(0,calc((1 - clamp(0,(var(--a,0) - .18) * 3,1)) * 10px),0);
  transition:opacity .1s linear;
}
.mv-list li:nth-child(2){--d:1} .mv-list li:nth-child(3){--d:2} .mv-list li:nth-child(4){--d:3}
.mv-say{margin:clamp(16px,2.6vh,30px) 0 0;max-width:46ch;color:var(--text-secondary);
  font-size:clamp(15px,1.25vw,18px);line-height:1.6;
  opacity:clamp(0,(var(--a,0) - .34) * 3,1)}

@media (max-width:860px){
  .mv .mega{font-size:clamp(46px,16vw,104px)}
  .mv-list li{font-size:11px;padding:8px 12px}
  .mv-pin{justify-content:flex-start;padding-top:clamp(84px,12vh,130px)}
  .mv-stack{flex:0 0 auto;min-height:60svh}
}

/* ============================================================
   QUOTES
   ============================================================ */
.quotes{display:flex;gap:clamp(16px,2vw,30px);overflow-x:auto;scroll-snap-type:x mandatory;
  padding:0 var(--gutter) 8px;scrollbar-width:none;-webkit-overflow-scrolling:touch}
.quotes::-webkit-scrollbar{display:none}
.quote{
  /* A <figure> carries the user agent's own margin: 1em 40px. That was
     adding eighty pixels between every card on top of the flex gap,
     which is why only two and a half ever fitted however narrow the
     cards were made. */
  margin:0;
  flex:0 0 auto;width:min(86vw,560px);scroll-snap-align:center;
  background:var(--panel);border:1px solid var(--line);border-radius:18px;
  padding:clamp(24px,3.4vw,44px);display:flex;flex-direction:column;gap:18px;
}
.quote blockquote{margin:0;font-size:clamp(16px,1.35vw,20px);line-height:1.6}
.quote figcaption{margin-top:auto;display:flex;flex-direction:column;gap:2px}
.quote figcaption strong{font-family:var(--display);font-variation-settings:'wdth' 100,'wght' 700;font-weight:700;font-size:16px}
.quote figcaption em{font:500 10.5px/1.5 var(--mono);letter-spacing:.16em;text-transform:uppercase;font-style:normal;color:var(--text-secondary)}
/* the card the reader is actually on stands up out of the row */
.quote{transition:transform .7s var(--ease-out),background .5s var(--ease-out)}
.quote.near{background:var(--panel-2)}
.quotes{padding-block:6px 14px}
.qnav{margin:clamp(20px,3vh,34px) var(--gutter) 0;height:2px;background:var(--line);border-radius:2px;overflow:hidden;max-width:1320px;margin-inline:auto}
.qnav i{display:block;height:100%;background:var(--text-primary);transform-origin:left;transform:scaleX(.25)}
@media (max-width:860px){ .quote{width:min(84vw,430px)} }

/* ============================================================
   THE CLOSING STATEMENT
   Stacked words that arrive one line at a time.
   ============================================================ */
.bigcta{padding:clamp(88px,15vh,190px) var(--gutter);text-align:left}
.bigcta .line{
  display:block;overflow:hidden;
}
.bigcta .line > span{
  display:block;
  transform:translateY(102%);
  transition:transform .95s var(--ease-out);
  transition-delay:calc(var(--i,0) * 70ms);
}
.bigcta.on .line > span{transform:translateY(0)}
.bigcta .after{display:flex;align-items:center;gap:20px;flex-wrap:wrap;margin-top:clamp(30px,5vh,58px)}

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

html,body{overflow-x:hidden;overflow-x:clip}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}

body{
  margin:0;
  background:var(--canvas);
  color:var(--text-primary);
  font-family:var(--body);
  font-weight:400;
  font-size:clamp(15px,.55vw + 13px,17px);
  line-height:1.65;
  -webkit-font-smoothing:antialiased;

  /* ---- THE REVEAL, TAKEN OFF THE SCRIPT ----
     This was opacity:0 waiting for a .ready class that JavaScript adds
     at the END of a 160KB inline script. Nothing on the page painted
     until that whole script had downloaded, parsed and run — so on a
     throttled phone the first paint was four seconds, and the largest
     paint could not even be a candidate before then.

     It is the same fade, driven by an animation instead, so it starts
     on the first frame the browser composites and owes nothing to the
     script. `both` holds the first keyframe through the delay so there
     is still no flash of a half-built page.

     .ready is left working as a belt and braces: JavaScript still adds
     it, and a page whose animation never ran for any reason is still
     revealed rather than left invisible. */
  opacity:0;
  animation:bodyReveal .7s var(--ease-out) .04s both;
}
@keyframes bodyReveal{ from{opacity:0} to{opacity:1} }
body.ready{opacity:1;animation:none}

img{max-width:100%;display:block}
a{color:inherit}
button{font:inherit;color:inherit}

:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:3px}

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

.skip{position:absolute;left:12px;top:-60px;z-index:200;background:var(--accent);color:#fff;padding:10px 16px;border-radius:8px;font:500 14px var(--body);text-decoration:none;transition:top .25s var(--ease-out)}
.skip:focus{top:12px}

/* ============================================================
   THE FIXED ENVIRONMENT LAYER
   One place behind everything, drifting well over 60s.
   ============================================================ */
.env{position:fixed;inset:0;z-index:0;pointer-events:none;background:var(--canvas-2);overflow:hidden}
.env i{position:absolute;display:block;border-radius:50%;filter:blur(110px);opacity:.34;will-change:transform}
.env i:nth-child(1){width:52vw;height:52vw;left:-14vw;top:4vh;background:radial-gradient(circle,rgba(221,18,103,.26),transparent 68%);animation:drift-a 96s var(--ease-io) -22s infinite alternate}
.env i:nth-child(2){width:44vw;height:44vw;right:-12vw;top:46vh;background:radial-gradient(circle,rgba(142,17,71,.3),transparent 70%);animation:drift-b 124s var(--ease-io) -51s infinite alternate}
.env i:nth-child(3){width:36vw;height:36vw;left:38vw;bottom:-12vh;background:radial-gradient(circle,rgba(221,18,103,.16),transparent 72%);animation:drift-c 148s var(--ease-io) -70s infinite alternate}
@keyframes drift-a{from{transform:translate3d(0,0,0) scale(1)}to{transform:translate3d(7vw,9vh,0) scale(1.16)}}
@keyframes drift-b{from{transform:translate3d(0,0,0) scale(1.1)}to{transform:translate3d(-8vw,-11vh,0) scale(.92)}}
@keyframes drift-c{from{transform:translate3d(0,0,0) scale(.95)}to{transform:translate3d(5vw,-7vh,0) scale(1.2)}}

/* the dot matrix: depth for free, drifting a whisper over two minutes */
.env .dots{
  position:absolute;inset:-60px;
  background-image:var(--dots);
  background-size:38px 38px;
  opacity:.5;
  -webkit-mask-image:radial-gradient(ellipse 90% 80% at 55% 45%,#000 20%,transparent 78%);
  mask-image:radial-gradient(ellipse 90% 80% at 55% 45%,#000 20%,transparent 78%);
  animation:dots 140s linear -40s infinite;
}
@keyframes dots{from{transform:translate3d(0,0,0)}to{transform:translate3d(-38px,-38px,0)}}

.env::after{
  content:"";position:absolute;inset:0;opacity:.16;background-image:var(--grain);
}

/* ============================================================
   NAV
   ============================================================ */
/* Scoped with :where() rather than by raising it to #nav. There is a
   second <nav> on the page now — the small-screen menu — and a bare
   element selector was handing it this one's position:fixed, its row
   layout and its gutters. :where() contributes nothing to
   specificity, so every rule written against `nav` later in this
   sheet still wins or loses exactly as it did before. */
nav:where(#nav){
  position:fixed;inset:0 0 auto 0;z-index:90;
  display:flex;align-items:center;gap:24px;
  padding:14px var(--gutter);
  transition:background .45s var(--ease-out),backdrop-filter .45s var(--ease-out),border-color .45s var(--ease-out);
  border-bottom:1px solid transparent;
}
/* the bar reads whichever block is under it and re-colours to match,
   so it is never pink on pink or black on black. It carries no ground
   of its own until you have scrolled. */
nav:where(#nav){color:var(--text-primary);transition:background .45s var(--ease-out),color .4s var(--ease-out),border-color .45s var(--ease-out)}
nav.stuck{backdrop-filter:blur(16px);border-bottom-color:var(--line)}
nav[data-tone="ink"].stuck{background:rgba(10,8,16,.74)}
nav[data-tone="bone"].stuck{background:rgba(243,239,233,.80)}
nav[data-tone="flood"].stuck{background:rgba(190,15,88,.80)}
/* the lockup is drawn in the pink, which disappears on a pink ground:
   on the flood it is knocked out to white instead */
nav[data-tone="flood"] .brand img{filter:brightness(0) invert(1)}
nav[data-tone="bone"] .brand:hover img{filter:drop-shadow(0 0 14px rgba(221,18,103,.45))}

/* ---- the spine is retired ----
   It was the signature of the single-ground design. In a page built out
   of colour blocks a pink thread runs invisible over the pink sections
   and reads as a stray rule over the bone ones, so it goes. The code
   that drew it checks for this and does nothing. */
.spine-wrap{display:none!important}
/* A colour block has to reach both edges of the screen or it is a card,
   not a block. The old layout capped the whole below-fold column at
   1400px and centred it, which left a strip of the body colour showing
   down each side of every tone. The cap moves inward, onto the reading
   column, where it was doing the real work anyway. */
.below{max-width:none!important;margin:0!important}
.below{--pad-l:var(--gutter)!important}
.wrap,.crgrid,.films,.printgrid{max-width:1320px;margin-inline:auto}
/* The rail used to break out of the section's gutters with a negative
   margin to run edge to edge. Measured at 929px it was leaving the
   element 92px wider than the screen: harmless while overflow is
   clipped, but it is exactly the shape of bug that let scrollIntoView
   shove the whole page sideways on a phone. The rail now lives inside
   the gutters like everything else, its cards line up with the
   headings above them, and a whole class of bug goes away. */
@media (min-width:861px){ .rail-track{padding-inline:0} }

/* ---- two more things the audit turned up ----
   Footer links were 23px tall, well under a comfortable thumb. And the
   figure behind section 04 is laid out full-bleed with 100vw, which is
   always a scrollbar wider than the page can actually show, so the
   frame around it now clips. (The chip's 8.6px is fixed at its source
   further down, because a second rule up here would lose on order.) */
footer a{padding-block:9px;margin-bottom:0}
/* The tap-target fix turned every footer link into an inline-block,
   which quietly beat the display:block further down and ran the email
   and the phone number together on one line. Each link is its own row
   again; the box that makes it tappable is as wide as its own text. */
footer .fgrid a{display:block;width:fit-content;min-height:22px}
#made .bleed{overflow:hidden}
/* an inline link cannot be made taller by padding it, so the two that
   matter are given a box of their own first */
.fineprint a{display:inline-block;padding-block:10px}
nav .brand{padding-block:7px}
.rail-card .cat{font-size:11px}
/* (the film captions are fixed at their source further down, for the
   same source-order reason as the chip) */

/* the quotes section, styled and ready for the day the real words arrive */
.quotesec{padding-block:clamp(72px,11vh,140px)}
.quotesec .wrap{margin-bottom:clamp(26px,4vh,48px)}

/* On the flood the accent is the near-black, which is 3.98:1 against the
   pink: fine for display sizes, not for a 12px link. Small links there
   go white and carry an underline instead of relying on colour alone. */
[data-tone="flood"] a:not(.btn):not(.brand):not(.rail-card){color:#fff;text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:1px}
[data-tone="flood"] a:not(.btn):not(.brand):hover{text-decoration-thickness:2px}
.brand{display:flex;align-items:center;gap:12px;text-decoration:none;flex:0 0 auto}
.brand img{width:46px;height:auto;transition:transform .5s var(--ease-out),filter .5s var(--ease-out)}
.brand:hover img{transform:scale(1.06);filter:drop-shadow(0 0 14px rgba(221,18,103,.55))}
.brand b{font:500 10.5px/1.35 var(--mono);letter-spacing:.2em;text-transform:uppercase;color:var(--text-secondary);white-space:nowrap;transition:color .4s var(--ease-out)}
.brand:hover b{color:var(--text-primary)}
.navlinks{display:flex;gap:26px;margin-left:auto;align-items:baseline}
.navlinks a{font:500 13px var(--body);color:var(--text-secondary);text-decoration:none;position:relative;padding-block:4px;transition:color .3s var(--ease-out)}
.navlinks a::after{content:"";position:absolute;left:0;right:0;bottom:0;height:1px;background:var(--accent);transform:scaleX(0);transform-origin:left;transition:transform .4s var(--ease-out)}
.navlinks a:hover{color:var(--text-primary)}
.navlinks a:hover::after{transform:scaleX(1)}
.navcta{margin-left:18px}

/* ---- the menu button ----
   Two rules that become a cross. It only exists where the links do
   not, so the two are never both on screen arguing about which is
   the navigation. */
.menubtn{
  display:none;width:44px;height:44px;margin-left:12px;flex:none;
  align-items:center;justify-content:center;flex-direction:column;gap:5px;
  background:none;border:0;cursor:pointer;padding:0;
}
.menubtn i{display:block;width:20px;height:1.5px;background:var(--text-primary);border-radius:2px;
  transition:transform .42s var(--ease-out),opacity .3s var(--ease-out)}
.menubtn[aria-expanded="true"] i:first-child{transform:translateY(3.25px) rotate(45deg)}
.menubtn[aria-expanded="true"] i:last-child{transform:translateY(-3.25px) rotate(-45deg)}

/* with the links hidden, nothing carried margin-left:auto any more, so the
   button sat next to the logo instead of at the right edge */
@media (max-width:900px){
  .navlinks{display:none}
  nav:where(#nav) .btn{margin-left:auto!important}
  .menubtn{display:flex;margin-left:12px}
}
/* on a narrow phone the wordmark, the call to action and the menu
   cannot all have their space. The mark alone still says who this is,
   and the two things you can press keep their full size. */
@media (max-width:560px){ .brand b{display:none} }
@media (max-width:420px){
  nav:where(#nav){padding-inline:16px}
  nav:where(#nav) .btn{padding:12px 16px;font-size:13px}
  .brand b{font-size:9.5px;letter-spacing:.16em}
  .brand img{width:38px}
}

/* ============================================================
   BUTTONS
   ============================================================ */
/* the button takes its colours from whichever tone it is standing on:
   pink on the dark, black on the bone, black on the pink. A pink button
   on a pink ground would be a hole, not a button. */
.btn{
  --bg:var(--btn-bg,var(--accent));
  display:inline-flex;align-items:center;justify-content:center;gap:9px;
  padding:13px 24px;border-radius:999px;border:0;
  background:var(--bg);color:var(--btn-fg,#fff);
  font:600 14px var(--body);letter-spacing:.01em;
  text-decoration:none;cursor:pointer;position:relative;
  transition:transform .4s var(--ease-out),background .3s var(--ease-out),color .3s var(--ease-out),box-shadow .45s var(--ease-out);
  text-shadow:none;
}
.btn::after{content:"";position:absolute;inset:0;border-radius:inherit;box-shadow:0 0 0 0 var(--accent-glow),0 12px 40px -8px var(--accent-glow);opacity:0;transition:opacity .45s var(--ease-out);pointer-events:none}
.btn:hover{background:var(--btn-bg-h,var(--accent-hover));transform:translateY(-2px)}
[data-tone="flood"] .btn:hover{color:var(--flood)}
[data-tone="bone"] .btn:hover{color:#fff}
.btn:hover::after{opacity:1}
.btn:active{transform:translateY(0)}
/* the arrow travels out of the button and a second one arrives behind it */
.btn .arrow{position:relative;display:inline-block;width:1em;height:1em;overflow:hidden;line-height:1}
.btn .arrow::before,.btn .arrow::after{
  content:"\203A";position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  transition:transform .48s var(--ease-out);
}
.btn .arrow::after{transform:translateX(-140%)}
.btn:hover .arrow::before{transform:translateX(140%)}
.btn:hover .arrow::after{transform:translateX(0)}

.btn.ghost{background:transparent;color:var(--text-primary);border:1px solid var(--border-ui)}
.btn.ghost:hover{background:var(--veil);border-color:currentColor;color:var(--text-primary)}
.btn.ghost::after{display:none}

@media (pointer:coarse){
  .btn{min-height:44px;display:inline-flex;align-items:center;justify-content:center}
  .navlinks a{min-height:44px;display:inline-flex;align-items:center}
}

/* ============================================================
   THE HERO: a tall pinned region with a sticky stage
   ============================================================ */
/* The scroll distance the four acts are spread across. Shorter means the
   animation moves further per turn of the wheel, so the journey keeps up
   with somebody scrolling at a normal pace instead of making them work
   for it. Everything below is written in progress, not pixels, so this
   one number is the whole pacing control. */
.hero{position:relative;height:440vh;z-index:1;padding:0}
/* the room the scene sits in: a warm pocket behind the rings and cold
   corners, so the 3D has somewhere to be rather than floating on flat black */
.stage{
  position:sticky;top:0;height:100vh;height:100svh;overflow:hidden;
  background-color:var(--canvas-2);
  background-image:
    radial-gradient(ellipse 58% 52% at 68% 42%,rgba(94,12,52,.34) 0%,rgba(46,8,30,.16) 45%,transparent 74%),
    radial-gradient(ellipse 80% 70% at 12% 78%,rgba(22,14,36,.8) 0%,transparent 70%);
}
@media (max-width:720px){
  .stage{background-image:
    radial-gradient(ellipse 84% 34% at 52% 24%,rgba(94,12,52,.34) 0%,rgba(46,8,30,.16) 48%,transparent 76%),
    radial-gradient(ellipse 96% 56% at 50% 88%,rgba(22,14,36,.82) 0%,transparent 72%);}
}

/* The designed poster art, painted in CSS so the hero is a composed
   image even with zero asset files present. The real poster rides on
   .pimg ABOVE it, so a missing file never wipes the art out. */
.pimg{position:absolute;inset:0;background-size:cover;background-position:50% 50%}
.poster{
  position:absolute;inset:0;
  background-color:#0A0810;
  background-image:
    radial-gradient(2px 2px at 71% 22%,rgba(255,99,160,.95),transparent 60%),
    radial-gradient(3px 3px at 78% 48%,rgba(221,18,103,.95),transparent 62%),
    radial-gradient(2px 2px at 64% 68%,rgba(255,140,190,.8),transparent 60%),
    radial-gradient(4px 4px at 86% 34%,rgba(221,18,103,.85),transparent 64%),
    radial-gradient(2px 2px at 92% 62%,rgba(255,99,160,.7),transparent 60%),
    radial-gradient(2px 2px at 57% 38%,rgba(221,18,103,.6),transparent 60%),
    radial-gradient(3px 3px at 69% 82%,rgba(255,120,175,.7),transparent 62%),
    radial-gradient(2px 2px at 82% 14%,rgba(221,18,103,.5),transparent 60%),
    radial-gradient(2px 2px at 47% 56%,rgba(221,18,103,.42),transparent 60%),
    radial-gradient(1.5px 1.5px at 38% 28%,rgba(255,120,175,.3),transparent 60%),
    radial-gradient(1.5px 1.5px at 30% 72%,rgba(221,18,103,.24),transparent 60%),
    radial-gradient(ellipse 70% 80% at 80% 45%,rgba(120,14,62,.5),transparent 70%),
    radial-gradient(ellipse 90% 90% at 12% 50%,rgba(18,12,26,.9),transparent 75%),
    linear-gradient(110deg,#0A0810 0%,#120B18 45%,#1A0C1E 100%);
}
/* ---- THE MARK IN REAL 3D ----
   Two rings, lit and shaded by a shader written for this page, drifting
   apart at the top of the scroll and threading through each other by the
   end. Real geometry rather than a video: sharp at any pixel density, no
   720p ceiling, no download, and it never falls out of sync with the
   scroll because the scroll IS the timeline. */
.hero3d{
  position:absolute;inset:0;width:100%;height:100%;display:block;
  opacity:0;transition:opacity 1.1s var(--ease-out);
}
.hero3d.on{opacity:1}

/* 1. the global base scrim: no frame is ever raw behind the page */
/* with the field rendered in code rather than generated, brightness is under
   our control, so the base scrim goes back to a light vignette and the type
   field is allowed to actually be seen */
.scrim{position:absolute;inset:0;pointer-events:none;background:radial-gradient(ellipse 124% 96% at 50% 46%,rgba(10,10,18,0) 42%,rgba(10,10,18,.46) 100%)}
.vignette{position:absolute;inset:0;pointer-events:none;background:linear-gradient(to bottom,rgba(10,8,16,.72) 0%,rgba(10,8,16,0) 20%,rgba(10,8,16,0) 70%,rgba(10,8,16,.8) 100%)}


/* ============================================================
   CAPTION BANDS
   ============================================================ */
.bands{position:absolute;inset:0;z-index:5;pointer-events:none}
.band{position:absolute;opacity:0;text-shadow:var(--tshadow)}
.band .btn{text-shadow:none}

.band--left{left:var(--gutter);top:50%;transform:translateY(-50%);width:min(46vw,620px)}
.band--center{left:50%;transform:translateX(-50%);bottom:13vh;width:min(92vw,860px);text-align:center}

/* 2. the per-band scrim, riding the band's --k. Two-sided by position:
      the left bands dim only their own column, the settle band dims low
      and centred, so the action lane stays bright. */
/* Peak alphas tuned against the WORST frame of the delivered footage, not the
   average, and re-audited after every change. The gradient still dies out
   before the edges so the corners of the film stay live. */
.band::before{
  content:"";position:absolute;inset:-26% -26%;pointer-events:none;z-index:-1;
  opacity:calc(.25 + .75*var(--k,1));
}
.band--left::before{background:radial-gradient(ellipse 80% 70% at 40% 50%,rgba(4,3,8,.86) 0%,rgba(4,3,8,.74) 36%,rgba(4,3,8,.44) 60%,rgba(4,3,8,.14) 76%,rgba(4,3,8,0) 88%)}
.band--center::before{inset:-40% -26%;background:radial-gradient(ellipse 74% 64% at 50% 54%,rgba(4,3,8,.86) 0%,rgba(4,3,8,.74) 36%,rgba(4,3,8,.44) 60%,rgba(4,3,8,.14) 76%,rgba(4,3,8,0) 88%)}

.hline{
  font-family:var(--display);font-variation-settings:'wdth' 112,'wght' 700;
  font-weight:700;
  font-size:clamp(34px,5.6vw,86px);line-height:.98;letter-spacing:-.035em;
  margin:0;max-width:15ch;
}
/* slimmer at the settle: at this size a lighter weight reads far more
   elegant, and the tracking opens a touch to carry it */
.band--center .hline{
  font-variation-settings:'wdth' 116,'wght' 450;font-weight:450;
  font-size:clamp(40px,8vw,124px);max-width:none;margin-inline:auto;letter-spacing:-.028em;
}
.band .sub{
  font-size:clamp(15px,1.35vw,19px);color:var(--text-primary);opacity:.9;
  margin:18px auto 0;max-width:48ch;line-height:1.55;
}
.band .cta-row{display:flex;gap:12px;justify-content:center;flex-wrap:wrap;margin-top:26px;pointer-events:auto}

/* 4. the chip scrim, for small text over footage */
.chip{
  display:inline-block;background:rgba(8,8,15,.55);border:1px solid rgba(96,96,126,.3);
  border-radius:10px;backdrop-filter:blur(10px);
  text-shadow:0 1px 3px rgba(5,5,10,.85);
  font:500 10.5px/1.5 var(--mono);letter-spacing:.18em;text-transform:uppercase;
  color:var(--text-secondary);padding:7px 12px;margin:0 0 20px;max-width:100%;
}
/* was 8.6px here, which is below anything anyone should be asked to
   read. The tracking gives back the width the extra size costs. */
@media (max-width:560px){.chip{font-size:10px;letter-spacing:.1em;padding:7px 11px}}

/* ---- text choreography: transform + opacity only, scrubbed off --k ---- */
.w{display:inline-block;white-space:pre}
.c{display:inline-block;will-change:transform,opacity}

/* (a) scatter: characters fly in from seeded offsets. echoes points of light drifting */
[data-entrance="scatter"] .c{
  --kc:clamp(0,(var(--k,0) - var(--th,0))*2.6,1);
  opacity:var(--kc);
  transform:translate(calc((1 - var(--kc))*var(--jx,0px)),calc((1 - var(--kc))*var(--jy,0px))) rotate(calc((1 - var(--kc))*var(--jr,0deg)));
}
/* (i) weave: characters arrive alternating from above and below. echoes threads crossing */
[data-entrance="weave"] .c{
  --kc:clamp(0,(var(--k,0) - var(--th,0))*2.8,1);
  opacity:var(--kc);
  transform:translateY(calc((1 - var(--kc))*var(--jy,0px)));
}
/* (d) word-punch with overshoot. echoes the snap as a link lands */
[data-entrance="punch"] .w{
  --kc:clamp(0,(var(--k,0) - var(--th,0))*3,1);
  --ks:clamp(0,(var(--k,0) - var(--th,0) - var(--sd,.1))*var(--ss,3.2),1);
  opacity:var(--kc);
  transform:scale(calc(0.6 + (0.4 + var(--ov,.12))*var(--kc) - var(--ov,.12)*var(--ks)));
}
[data-entrance="punch"] .w.em{--ov:.24;--sd:.16;--ss:2.6}
/* (e) word-by-word rise into a staged settle. echoes the arrival at rest */
[data-entrance="rise"] .w{
  --kc:clamp(0,(var(--k,0) - var(--th,0))*2.6,1);
  opacity:var(--kc);
  transform:translateY(calc((1 - var(--kc))*26px));
}
[data-entrance="rise"] .sub{--ks:clamp(0,(var(--k,0) - .62)*4,1);opacity:calc(.9*var(--ks));transform:translateY(calc((1 - var(--ks))*14px))}
[data-entrance="rise"] .cta-row{--kb:clamp(0,(var(--k,0) - .76)*5,1);opacity:var(--kb);transform:translateY(calc((1 - var(--kb))*16px))}

/* the hero progress readout */
.hud{position:absolute;left:var(--gutter);bottom:calc(var(--gutter) - 2px);z-index:6;display:flex;align-items:center;gap:12px}
.hud .bar{width:min(26vw,200px);height:2px;background:rgba(244,241,246,.16);overflow:hidden;border-radius:2px}
.hud .bar i{display:block;height:100%;background:var(--accent);transform-origin:left;transform:scaleX(0)}
.hud .pct{font:500 10.5px/1 var(--mono);letter-spacing:.14em;color:var(--text-secondary);min-width:4ch}

/* a scrub-capable but short window has no room for the overlays */
@media (max-height:560px){.hud{display:none}}

/* ============================================================
   THE STATIC HERO
   A designed layout, not an apology.
   ============================================================ */
.static-hero{display:none;position:relative;min-height:100svh;align-items:center;padding:120px var(--gutter) 72px;overflow:hidden}
.static-hero .poster{position:absolute;inset:0}
.static-hero .sh-in{position:relative;z-index:2;width:100%}
.static-hero .hline{max-width:14ch;font-size:clamp(34px,10vw,60px)}
.static-hero .sub{margin:20px 0 0;max-width:40ch;color:var(--text-secondary);font-size:16.5px}
.static-hero .cta-row{display:flex;gap:12px;flex-wrap:wrap;margin-top:30px}
.static-hero .sh-scrim{position:absolute;inset:0;background:linear-gradient(100deg,rgba(8,6,13,.94) 0%,rgba(8,6,13,.82) 46%,rgba(8,6,13,.32) 100%)}

/* ---- THE STATIC-HERO GATES ----
   These must stay character-for-character identical to the GATES array in
   the script below.

   The phone-width gates are deliberately GONE. They existed to stop phones
   downloading a heavy scrub video. There is no video any more: the hero is
   drawn by the browser, so phones get the real animation too, tuned for
   them below. What still gets the still hero is reduced motion, and a
   phone held sideways, which has no room for the journey. ---- */
@media (orientation: landscape) and (pointer: coarse) and (max-height: 560px),
       (prefers-reduced-motion: reduce){
  .hero{height:auto}
  .stage{display:none}
  .static-hero{display:flex}
}
/* and a browser with no WebGL at all takes the same road: the designed
   still hero, never a blank rectangle where the scene should have been */
.hero.no3d{height:auto}
.hero.no3d .stage{display:none}
.hero.no3d .static-hero{display:flex}

/* ============================================================
   THE HERO IN PORTRAIT
   Its own composition, not the desktop one shrunk. The rings own
   the top half, the argument sits along the bottom, and neither
   is ever asked to be legible on top of the other.

   The query has to stay in step with the `portrait` flag in the
   scene code below, or a portrait tablet would get the left-column
   text layout with the centred ring composition and they would
   sit on top of each other.
   ============================================================ */
@media (max-width:720px), (max-aspect-ratio: 19/20){
  /* a thumb covers more of a phone screen per swipe than a wheel does of
     a desktop one, so the journey is shorter again here */
  .hero{height:310vh}

  /* A portrait screen gets a split, not a shrunk desktop: the rings own
     the top half, the argument sits in the bottom half, and neither is
     ever asked to be legible on top of the other. */
  .band--left{
    left:var(--gutter);right:var(--gutter);width:auto;text-align:center;
    top:auto;bottom:clamp(112px,25vh,214px);transform:none;
  }
  .band--left .chip{margin-inline:auto}
  .band--left::before{
    inset:-30% -30%;
    background:radial-gradient(ellipse 84% 66% at 50% 52%,rgba(4,3,8,.92) 0%,rgba(4,3,8,.8) 34%,rgba(4,3,8,.5) 58%,rgba(4,3,8,.16) 76%,rgba(4,3,8,0) 90%);
  }
  .hline{font-size:clamp(31px,8.6vw,46px);max-width:none;letter-spacing:-.03em;line-height:1.02}
  .band--center{top:auto;bottom:clamp(112px,25vh,214px);transform:translateX(-50%);width:calc(100% - var(--gutter)*2)}
  .band--center .hline{font-size:clamp(34px,10.5vw,56px);letter-spacing:-.035em}
  .band .sub{font-size:15px;max-width:34ch}
  .band .cta-row{margin-top:22px}

  .hud .bar{width:38vw}
}

/* The static-hero gates are re-stated AFTER the phone block above, because
   that block sets a tall scroll height and equal-specificity rules are won
   by source order. Without this, a phone with reduced motion kept 440vh of
   scroll wrapped around a single static screen. */
@media (orientation: landscape) and (pointer: coarse) and (max-height: 560px),
       (prefers-reduced-motion: reduce){
  .hero{height:auto}
  .stage{display:none}
  .static-hero{display:flex}
}

/* ============================================================
   BELOW THE FOLD
   The whole below-fold block is one frame, so the spine and the
   content stay together at any screen width.
   ============================================================ */
.below{position:relative;z-index:2;max-width:1400px;margin:0 auto;--pad-l:var(--gutter)}
@media (min-width:1181px){ .below{--pad-l:calc(var(--gutter) + 72px)} }
section{position:relative;padding-block:clamp(66px,10vh,150px);padding-inline:var(--pad-l) var(--gutter)}
.wrap{position:relative}

.kicker{display:flex;align-items:baseline;gap:12px;margin:0 0 22px;font:500 11px/1 var(--mono);letter-spacing:.2em;text-transform:uppercase;color:var(--text-secondary)}
.kicker b{font-family:var(--display);font-variation-settings:'wdth' 112,'wght' 800;font-size:26px;line-height:1;color:var(--accent);letter-spacing:-.02em}

h2{
  font-family:var(--display);font-variation-settings:'wdth' 112,'wght' 700;font-weight:700;
  font-size:clamp(34px,5.4vw,82px);line-height:.99;letter-spacing:-.038em;margin:0;max-width:16ch;
}
/* a heading that is meant to be read as one phrase, and is short enough
   to be one, gets the width it needs rather than the 16ch default */
.nowrap-h{max-width:none}
@media (min-width:861px){ .nowrap-h{white-space:nowrap} }
.lede{margin:26px 0 0;color:var(--text-secondary);font-size:clamp(16px,1.25vw,19px);max-width:54ch;line-height:1.62;font-weight:400}

/* ---- THE RIBBON: the site's signature element ----
   Your mark is two rings locked through each other. Stretched into a path,
   it becomes one continuous magenta ribbon running the whole page, threading
   through a ring pair at every section. It means something rather than
   decorating, and it is drawn, not generated. */
.spine-wrap{position:absolute;left:calc(var(--gutter) - 40px);top:0;bottom:0;width:120px;pointer-events:none;z-index:1}
.spine{display:block;overflow:visible}
/* a drawn thread, not a rail: narrow enough to read as a detail, with a
   gradient and a glow so it reads as light rather than as a border */
.spine .rib{
  fill:none;stroke:url(#ribgrad);stroke-width:5;stroke-linecap:round;
  filter:drop-shadow(0 0 10px rgba(221,18,103,.55));
}
.spine .rib-core{fill:none;stroke:rgba(255,205,225,.75);stroke-width:1;stroke-linecap:round}
/* the undrawn track stays faintly visible, so the leading edge reads as
   a line being drawn rather than a line that stops */
.spine-ghost{fill:none;stroke:rgba(244,241,246,.1);stroke-width:5;stroke-linecap:round;
  stroke-dasharray:none!important;stroke-dashoffset:0!important}

/* the ring pair sits ON the ribbon, and the ribbon runs behind it, so it
   reads as threaded rather than stuck on top */
.node{
  position:absolute;left:60px;width:58px;height:45px;margin:-22px 0 0 -29px;
  opacity:.34;transition:opacity .8s var(--ease-out),transform .8s var(--ease-out),filter .8s var(--ease-out);
  transform:scale(.8);filter:grayscale(1) brightness(.75);
}
.node img{width:100%;height:auto;display:block}
.node.lit{opacity:1;transform:scale(1);filter:grayscale(0) brightness(1) drop-shadow(0 0 18px rgba(221,18,103,.6))}
/* the thread runs behind the mark, so it reads as threaded through it */
.node::before{
  content:"";position:absolute;left:50%;top:50%;width:92px;height:92px;margin:-46px 0 0 -46px;
  border-radius:50%;background:radial-gradient(circle,var(--canvas) 34%,rgba(12,9,19,0) 72%);
  z-index:-1;
}
@media (max-width:1180px){.spine-wrap{display:none}}

/* entrance choreography */
.reveal{opacity:0;transform:translateY(26px);transition:opacity .85s var(--ease-out),transform .85s var(--ease-out)}
.reveal.in{opacity:1;transform:none}

/* ---- SCROLL REVEAL, FOR THINGS THAT ALREADY HAVE A TRANSFORM ----
   A second reveal that runs as an ANIMATION rather than a transition,
   and takes itself off the element the moment it finishes.

   That matters: the film cards lift on hover, the tabs nudge, the rail
   images scale. A transition-based reveal has to hold `transform:none`
   in its resting state to stay put, and `.rv.in` beats `.film:hover` on
   specificity — so every one of those hovers would quietly stop working.
   Animating and then removing the classes leaves the element in exactly
   the state it was authored in, carrying nothing.

   opacity 0 -> 1, 30px up, 0.98 -> 1. `both` fill holds the first frame
   through the stagger delay so nothing flashes in before its turn. */
@keyframes rvIn{
  from{opacity:0;transform:translateY(30px) scale(.98)}
  to  {opacity:1;transform:translateY(0) scale(1)}
}
.rv{opacity:0}
.rv.in{
  animation:rvIn .72s cubic-bezier(.22,.61,.36,1) both;
  animation-delay:calc(var(--d, 0) * 68ms);
}
/* the images inside a revealing card settle a touch later and a touch
   softer, so the picture arrives after the card rather than with it.
   Nothing is cropped, scaled or moved from where it was laid out. */
@keyframes rvImg{ from{opacity:0;transform:translateY(12px)} to{opacity:1;transform:translateY(0)} }
.rv-img{opacity:0}
.rv-img.in{
  animation:rvImg .82s cubic-bezier(.22,.61,.36,1) both;
  animation-delay:calc(var(--d, 0) * 68ms + 90ms);
}
.stag > *{opacity:0;transform:translateY(22px);transition:opacity .75s var(--ease-out),transform .75s var(--ease-out)}
.stag.in > *{opacity:1;transform:none}
.stag.in > *:nth-child(1){transition-delay:0s}
.stag.in > *:nth-child(2){transition-delay:.07s}
.stag.in > *:nth-child(3){transition-delay:.14s}
.stag.in > *:nth-child(4){transition-delay:.21s}
.stag.in > *:nth-child(5){transition-delay:.28s}
.stag.in > *:nth-child(6){transition-delay:.35s}
.stag.in > *:nth-child(7){transition-delay:.42s}
.stag.in > *:nth-child(8){transition-delay:.49s}
/* retire the stagger once it has played, or every later hover lags forever.
   the selector repeats :nth-child so it matches the specificity it retires. */
.stag.done > *:nth-child(n){transition-delay:0s!important}

/* ============================================================
   01 THE LOOP  (signature section + the interactive moment)
   ============================================================ */
#loop .cols{display:grid;grid-template-columns:minmax(0,400px) minmax(0,1fr);gap:clamp(34px,5vw,72px);align-items:start;margin-top:54px}
@media (max-width:980px){#loop .cols{grid-template-columns:1fr}}

.loopstage{position:relative;aspect-ratio:4/3;max-width:100%}
/* the loop stays with you while you read the six moves */
@media (min-width:981px){.loopstage{position:sticky;top:112px}}
@media (max-width:980px){.loopstage{max-width:380px;margin-inline:auto}}
.loopstage svg{width:100%;height:100%;display:block;overflow:visible}
/* Every stroke here was written in literal near-white while this section
   was still dark, and then the section was recoloured to bone and nobody
   re-read the diagram: the ring and the unlit nodes were white on cream,
   which is to say invisible. That is most of why it read as a loose star
   of magenta lines. They are tone tokens now, so the ring is legible
   whichever way the section is coloured. */
.loop-plane{fill:var(--accent);opacity:.035;stroke:none}
.loop-rail{fill:none;stroke:var(--text-primary);stroke-opacity:.16;stroke-width:1.1}
.loop-chords{fill:none;stroke:var(--accent);stroke-width:.7;opacity:0}
/* opacity on these two is written from script as an INLINE style, not as
   a presentation attribute: a presentation attribute loses to any rule in
   a stylesheet, so the two lines above and below would have pinned both
   layers at zero forever and the bug would have looked like dead code. */
.loop-arc{fill:none;stroke:var(--accent);stroke-linecap:round;stroke-linejoin:round}
.loop-dot{fill:var(--canvas);stroke:var(--text-primary);stroke-opacity:.32;stroke-width:1.5;
  transition:fill .5s var(--ease-out),stroke .5s var(--ease-out),stroke-opacity .5s var(--ease-out)}
.loop-dot.on{fill:var(--accent);stroke:var(--accent);stroke-opacity:1}
.loop-core{fill:none;stroke:var(--accent);stroke-width:1;opacity:calc(var(--hold,0)*.32)}
.loop-hub{fill:var(--accent);opacity:calc(var(--hold,0)*.8)}
.loop-charge{opacity:0}
.loop-charge circle{fill:var(--accent)}
.loop-charge .halo{opacity:.13}

/* the press-and-hold button's styles went with the button itself */

.svcs{list-style:none;margin:0;padding:0;border-top:1px solid var(--line)}
.svc{
  border-bottom:1px solid var(--line);padding:15px 4px 13px 0;display:grid;
  grid-template-columns:32px minmax(0,1fr);gap:14px;align-items:baseline;
  --lit:0;
}
.svc .n{font:500 10.5px/1.6 var(--mono);color:var(--text-faint);transition:color .5s var(--ease-out)}
/* resting state is fully readable on its own: the hold adds emphasis,
   never legibility. text-secondary is 7.4:1, text-primary is 17.6:1. */
.svc h3{margin:0;font-family:var(--display);font-variation-settings:'wdth' 112,'wght' 700;font-weight:700;font-size:clamp(17px,1.5vw,21px);letter-spacing:-.01em;line-height:1.25;
  color:color-mix(in srgb,var(--text-primary) calc(var(--lit)*100%),var(--text-secondary));
  transition:color .1s linear}
.svc p{margin:4px 0 0;color:var(--text-secondary);font-size:14.5px;line-height:1.5;max-width:52ch;opacity:calc(.82 + .18*var(--lit))}
.svc .dash{display:block;height:1px;background:var(--accent);transform:scaleX(var(--lit));transform-origin:left;grid-column:1 / -1;margin-top:12px;opacity:.85}
.svc.on .n{color:var(--accent)}
/* the charge going round the ring lights the row it has just reached, so
   the diagram and the list are visibly the same six things and not two
   separate ornaments sitting next to each other */
.svc{position:relative}
/* the wash sits UNDER the row's own content but over the page, so the
   text is never tinted by it — a negative z-index would have dropped it
   behind the section's background and out of sight altogether */
.svc > *{position:relative;z-index:1}
.svc::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;opacity:0;
  background:linear-gradient(90deg,color-mix(in srgb,var(--accent) 13%,transparent),transparent 58%);
  transition:opacity .75s var(--ease-out)}
.svc.hit::before{opacity:1;transition-duration:.16s}
@media (prefers-reduced-motion:reduce){ .svc::before{display:none} }

.loop-done{margin:26px 0 0;font-family:var(--display);font-variation-settings:'wdth' 118,'wght' 800;font-weight:800;font-size:clamp(19px,2.1vw,27px);letter-spacing:-.02em;color:var(--accent);
  opacity:0;transform:translateY(10px);transition:opacity .7s var(--ease-out),transform .7s var(--ease-out)}
.loop-done.show{opacity:1;transform:none}

/* ============================================================
   02 THE WORK  (a different skeleton: full-bleed marquee)
   ============================================================ */
#work{padding-inline:0}
#work .wrap{padding-inline:var(--pad-l) var(--gutter)}
.mq{position:relative;margin:44px 0 0;padding-block:18px;border-block:1px solid var(--line);overflow:hidden;display:grid;gap:6px;-webkit-mask-image:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent);mask-image:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent)}
.mq-track{display:flex;width:max-content;animation:mq 104s linear infinite}
/* the second row runs the other way, which is what stops two lines of
   the same names reading as one wide block sliding past */
.mq-track--back{animation:mq-back 118s linear infinite;opacity:.5}
@keyframes mq{to{transform:translate3d(-50%,0,0)}}
@keyframes mq-back{from{transform:translate3d(-50%,0,0)}to{transform:translate3d(0,0,0)}}
.mq-group{display:flex;align-items:center;flex:0 0 auto}
.mq-group span{
  font-family:var(--display);font-variation-settings:'wdth' 112,'wght' 800;font-weight:800;
  font-size:clamp(19px,2.2vw,32px);letter-spacing:-.018em;white-space:nowrap;
  padding-inline:clamp(24px,3vw,52px);color:var(--text-primary);opacity:.92;
}
.mq-group span.more{color:var(--accent);opacity:1}
.mq-group i{flex:0 0 auto;width:6px;height:6px;border-radius:50%;background:var(--accent);opacity:.5}
@media (prefers-reduced-motion:reduce){ .mq-track{animation:none} .mq-track--back{display:none} }

/* a shared sub-head with a counter, the way a studio indexes its own work */
.subhead{
  display:flex;align-items:baseline;gap:14px;margin:clamp(48px,7vh,84px) 0 24px;
  font:500 11px/1 var(--mono);letter-spacing:.2em;text-transform:uppercase;color:var(--text-secondary);
}
.subhead::after{content:"";flex:1;height:1px;background:var(--line)}
.subhead em{order:3;font-style:normal;color:var(--accent);letter-spacing:.14em}

/* ---- THE PORTFOLIO GRIDS ----
   Three across on desktop, two on tablet, one on phone. Every tile is built
   from the PORTFOLIO manifest at the top of the script, so adding, removing
   or replacing work is one line of data and never a layout change. */
.crgrid,.printgrid{
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(14px,1.6vw,22px);
  padding-inline:var(--pad-l) var(--gutter);
}
@media (max-width:900px){.crgrid,.printgrid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:540px){.crgrid,.printgrid{grid-template-columns:1fr}}

.tile{
  position:relative;display:block;overflow:hidden;border-radius:12px;
  border:1px solid var(--line);background:var(--panel);text-decoration:none;
  transition:transform .55s var(--ease-out),border-color .45s var(--ease-out),box-shadow .55s var(--ease-out);
}
.tile img{width:100%;height:100%;object-fit:cover;display:block;
  transition:transform .8s var(--ease-out),filter .6s var(--ease-out);filter:saturate(.9) contrast(1.02)}
.tile::after{content:"";position:absolute;inset:0;background-image:var(--grain);opacity:.08;pointer-events:none;mix-blend-mode:overlay}
.tile:hover{transform:translateY(-6px);border-color:var(--accent);box-shadow:0 24px 54px -28px rgba(221,18,103,.55)}
.tile:hover img{transform:scale(1.05);filter:saturate(1.06) contrast(1.04)}
.crgrid .tile{aspect-ratio:1}
.printgrid .tile{aspect-ratio:3/4;background:#1a1216}
.printgrid .tile img{object-fit:contain}

/* ---- FILM ----
   One landscape feature, then the vertical social films beside it. Nothing
   downloads until someone presses play. */
/* The feature runs full width and the vertical films sit in one row
   beneath it, so neither column is left hanging with dead space.

   Four across: the film strip holds eight and the influencer grid six,
   and four divides the eight into two clean rows. Six across would put
   each poster near 180px, which is too small to read a burnt-in caption
   on; three would run them close to 680px tall, which is most of a
   screen for one still. */
.films{display:grid;grid-template-columns:1fr;gap:clamp(16px,2vw,26px);padding-inline:var(--pad-l) var(--gutter)}
.vstrip{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:clamp(12px,1.4vw,20px)}
@media (max-width:1180px){.vstrip{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media (max-width:640px){.vstrip{grid-template-columns:repeat(2,minmax(0,1fr))}}
.film{
  position:relative;display:block;overflow:hidden;border-radius:14px;
  border:1px solid var(--line);background:#0A0810;
  transition:transform .55s var(--ease-out),border-color .45s var(--ease-out),box-shadow .55s var(--ease-out);
}
@media (hover:hover){
  .film:hover{transform:translateY(-5px);border-color:var(--accent);box-shadow:0 26px 60px -30px rgba(221,18,103,.55)}
}
/* Both the poster and the video are absolutely positioned and fill the card.
   Previously the video was appended in normal flow AFTER the poster, so it
   landed below the card and overflow:hidden clipped it away: the audio played
   and the picture was nowhere. Everything here stacks in the same box. */
.film > img,.film > video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
.film > video{z-index:3;background:#0A0810}
.film.playing > img{opacity:0;pointer-events:none}
.film--wide{aspect-ratio:16/9}
.film--tall{aspect-ratio:9/16}
/* a real button, sitting over the poster, rather than the whole card being a
   button with a video trapped inside it */
.film .play{
  position:absolute;left:0;top:0;width:100%;height:100%;z-index:4;
  border:0;background:transparent;cursor:pointer;padding:0;
  display:flex;align-items:center;justify-content:center;
  -webkit-tap-highlight-color:transparent;
}
.film .play i{
  width:62px;height:62px;border-radius:50%;
  background:rgba(12,9,19,.62);border:1px solid rgba(244,241,246,.45);backdrop-filter:blur(8px);
  display:flex;align-items:center;justify-content:center;
  transition:background .4s var(--ease-out),border-color .4s var(--ease-out),transform .5s var(--ease-out);
}
@media (hover:hover){
  .film:hover .play i{background:var(--accent);border-color:var(--accent);transform:scale(1.08)}
}
.film .play:active i{transform:scale(.94)}
.film .play svg{width:20px;height:20px;margin-left:3px;fill:#fff}
.film.playing .play{display:none}
@media (pointer:coarse){ .film .play i{width:66px;height:66px} }
.film .cap{
  position:absolute;left:0;right:0;bottom:0;padding:38px 16px 13px;pointer-events:none;z-index:5;
  /* A straight fade from opaque to nothing only works while the caption
     is one line. The moment a title wraps, the box grows, the gradient
     stretches with it, and the FIRST line ends up sitting at about forty
     per cent scrim over whatever the poster frame happens to be — which
     on a bright frame is barely readable and looks like the text has
     been cut off. The floor is held almost solid through the whole text
     area and only the top slice fades. */
  background:linear-gradient(to top,rgba(6,4,11,.95) 0%,rgba(6,4,11,.88) 52%,rgba(6,4,11,0) 100%);
  font:500 11px/1.4 var(--mono);letter-spacing:.14em;text-transform:uppercase;
  /* NOT var(--text-primary). This caption sits on a scrim that is always
     dark, whatever the section around it is doing — but the token follows
     the section, and this one lives inside a bone-toned block where
     --text-primary resolves to near-black. Every film and creator caption
     on the page was therefore dark grey on near-black and effectively
     invisible. A fixed light value is the only correct thing here,
     because the surface it sits on is fixed too. */
  color:#F3EFE9;
  transition:opacity .4s var(--ease-out);
}
.film.playing .cap{opacity:0}
/* was 9.5px, the smallest type left on the page */
.film--tall .cap{font-size:10.5px;letter-spacing:.06em;padding:30px 11px 10px}
.film--tall .play i{width:48px;height:48px}
.film--tall .play svg{width:16px;height:16px}

/* the live sites, each in a restrained browser frame */
.sites{display:grid;grid-template-columns:repeat(auto-fit,minmax(380px,1fr));gap:clamp(18px,2.4vw,34px);padding-inline:var(--pad-l) var(--gutter)}
@media (max-width:560px){.sites{grid-template-columns:1fr}}
.site{text-decoration:none;display:block;group:site}
.site .frame{
  display:block;position:relative;border-radius:14px;overflow:hidden;
  border:1px solid var(--line);background:var(--panel-2);
  transition:transform .6s var(--ease-out),border-color .5s var(--ease-out),box-shadow .6s var(--ease-out);
}
.site:hover .frame{transform:translateY(-6px);border-color:var(--accent);box-shadow:0 26px 60px -30px rgba(221,18,103,.6)}
.site .bar{display:flex;align-items:center;gap:6px;padding:10px 14px;background:rgba(244,241,246,.05);border-bottom:1px solid var(--line)}
.site .bar b{width:8px;height:8px;border-radius:50%;background:rgba(244,241,246,.2)}
.site .bar u{margin-left:10px;font:400 10.5px var(--mono);color:var(--text-faint);text-decoration:none;letter-spacing:.04em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.site .frame img{width:100%;aspect-ratio:16/10;object-fit:cover;object-position:50% 0;display:block;transition:transform .9s var(--ease-out)}
.site:hover .frame img{transform:scale(1.035)}
.site .frame--soon{min-height:190px}
.site .soon{
  display:flex;align-items:center;justify-content:center;aspect-ratio:16/10;
  font:500 11px var(--mono);letter-spacing:.2em;text-transform:uppercase;color:var(--text-faint);
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 250'%3E%3Cg stroke='%23DD1267' stroke-width='.6' fill='none' opacity='.4'%3E%3Cpath d='M70 60 170 40 250 100 340 70 300 190 190 220 110 160Z'/%3E%3C/g%3E%3Cg fill='%23FF3B84' opacity='.75'%3E%3Ccircle cx='70' cy='60' r='2.6'/%3E%3Ccircle cx='170' cy='40' r='3.2'/%3E%3Ccircle cx='250' cy='100' r='3.8'/%3E%3Ccircle cx='340' cy='70' r='2.4'/%3E%3Ccircle cx='110' cy='160' r='2.8'/%3E%3Ccircle cx='190' cy='220' r='2.6'/%3E%3Ccircle cx='300' cy='190' r='3.4'/%3E%3C/g%3E%3C/svg%3E") center/cover,
    linear-gradient(130deg,#0A0810,#160C1C);
}
.site .meta{display:flex;align-items:baseline;gap:12px;padding:16px 2px 0;flex-wrap:wrap}
.site .meta strong{font-family:var(--display);font-variation-settings:'wdth' 110,'wght' 700;font-weight:700;font-size:clamp(17px,1.5vw,21px);letter-spacing:-.018em;color:var(--text-primary);transition:color .4s var(--ease-out)}
.site:hover .meta strong{color:var(--accent)}
.site .meta em{font-style:normal;font-size:13.5px;color:var(--text-secondary)}
.site .meta .go{margin-left:auto;font:400 11px var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--text-faint);transition:color .4s var(--ease-out)}
.site:hover .meta .go{color:var(--accent)}
/* a card that is not a link should not behave like one: no lift, no
   pointer, no pink on hover promising somewhere to go */
.site.is-offline{cursor:default}
.site.is-offline:hover .frame{transform:none;border-color:var(--line);box-shadow:none}
.site.is-offline:hover .meta strong,.site.is-offline:hover .meta .go{color:inherit}
.site.is-offline .meta .go{color:var(--text-faint)}

#work .linklist{max-width:620px}
.linklist{list-style:none;margin:0;padding:0}
.linklist li + li{margin-top:2px}
.linklist a{
  display:flex;align-items:baseline;justify-content:space-between;gap:14px;text-decoration:none;
  padding:13px 2px;border-bottom:1px solid var(--line);
  font-family:var(--display);font-variation-settings:'wdth' 110,'wght' 700;font-weight:700;font-size:clamp(16px,1.5vw,20px);letter-spacing:-.015em;
  transition:color .35s var(--ease-out),padding-left .45s var(--ease-out),border-color .35s var(--ease-out);
}
.linklist a span{font:400 11px var(--mono);letter-spacing:.1em;color:var(--text-faint);transition:color .35s var(--ease-out)}
.linklist a:hover{color:var(--accent);padding-left:10px;border-bottom-color:var(--accent-muted)}
.linklist a:hover span{color:var(--accent)}

/* ============================================================
   03 HOW IT RUNS  (a different skeleton again: alternating bands)
   ============================================================ */
.step{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:clamp(26px,4vw,64px);align-items:center;padding-block:clamp(30px,5vh,56px);border-top:1px solid var(--line)}
.step:nth-child(even) .step-txt{order:2}
@media (max-width:860px){.step{grid-template-columns:1fr}.step:nth-child(even) .step-txt{order:0}}
.step-txt h3{margin:0;font-family:var(--display);font-variation-settings:'wdth' 122,'wght' 900;font-weight:900;font-size:clamp(30px,4.6vw,58px);letter-spacing:-.03em;line-height:1}
.step-txt .num{font:500 11px/1 var(--mono);letter-spacing:.22em;color:var(--accent);display:block;margin-bottom:14px}
.step-txt p{margin:16px 0 0;color:var(--text-secondary);font-size:clamp(15px,1.2vw,17px);max-width:46ch;line-height:1.6}
/* the designed image fallback: the site's own link motif, drawn as SVG,
   so a missing or slow file is never an empty box */
.fig{
  position:relative;aspect-ratio:16/11;border-radius:14px;overflow:hidden;border:1px solid var(--line);
  background-color:#0A0810;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 275'%3E%3Cg stroke='%23DD1267' stroke-width='.7' fill='none' opacity='.55'%3E%3Cpath d='M58 72 146 38 232 96 318 54 292 196 186 232 104 168Z'/%3E%3Cpath d='M104 168 232 96M58 72 104 168M232 96 292 196M146 38 232 96M318 54 232 96'/%3E%3C/g%3E%3Cg fill='%23FF3B84'%3E%3Ccircle cx='58' cy='72' r='3'/%3E%3Ccircle cx='146' cy='38' r='3.8'/%3E%3Ccircle cx='232' cy='96' r='4.6'/%3E%3Ccircle cx='318' cy='54' r='2.8'/%3E%3Ccircle cx='104' cy='168' r='3.4'/%3E%3Ccircle cx='186' cy='232' r='3'/%3E%3Ccircle cx='292' cy='196' r='4'/%3E%3Ccircle cx='30' cy='212' r='1.7' opacity='.6'/%3E%3Ccircle cx='362' cy='142' r='1.9' opacity='.6'/%3E%3Ccircle cx='210' cy='18' r='1.5' opacity='.5'/%3E%3Ccircle cx='78' cy='252' r='1.5' opacity='.5'/%3E%3Ccircle cx='348' cy='244' r='1.3' opacity='.45'/%3E%3C/g%3E%3C/svg%3E"),
    radial-gradient(ellipse 80% 80% at 52% 48%,rgba(120,14,62,.5),transparent 72%),
    linear-gradient(125deg,#0A0810,#160C1C 60%,#1E0D22);
  background-size:cover,auto,auto;
  background-position:50% 50%;
}
.fig img{width:100%;height:100%;object-fit:cover}
/* The three process figures are DRAWN rather than photographed. They
   replaced three stills lifted from the old four-act scene, which were
   dark, near-empty and said nothing about Map, Link or Compound — and
   were the same floating-particle look that was thrown out of the hero.
   Vector costs nothing, stays crisp at any size, and can be about the
   step it sits beside. The frame's own fallback diagram is switched off
   underneath them, or it reads as noise behind the drawing. */
.fig--draw{
  background-image:
    radial-gradient(ellipse 80% 80% at 52% 48%,rgba(120,14,62,.5),transparent 72%),
    linear-gradient(125deg,#0A0810,#160C1C 60%,#1E0D22);
  background-size:auto,auto;
}
.fig .stepfig{position:absolute;inset:0;width:100%;height:100%;z-index:1}
/* The rendered version of the same two figures. It sits over the drawing
   and only becomes visible once a WebGL context actually exists, so a
   machine that cannot run it keeps the drawing rather than a hole.
   The drawing is hidden only when the canvas is confirmed live. */
.fig .stepgl{position:absolute;inset:0;width:100%;height:100%;z-index:2;opacity:0;
  transition:opacity 1.1s var(--ease-out)}
.fig .stepgl.on{opacity:1}
.fig.has-gl .stepfig{opacity:0}
@media (prefers-reduced-motion:reduce){ .fig .stepgl{transition:none} }
.fig::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(ellipse 90% 70% at 50% 50%,transparent 40%,rgba(8,6,13,.55) 100%)}
/* grain rides on its own layer at whisper level, never as a blend on the scrim */
.fig::before{content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  background-image:var(--grain);opacity:.09;mix-blend-mode:overlay}
.fig .glow{position:absolute;inset:0;background:radial-gradient(circle at 50% 50%,var(--accent-glow),transparent 62%);opacity:0;animation:breathe 9s var(--ease-io) -3s infinite;mix-blend-mode:screen;pointer-events:none}
@keyframes breathe{0%,100%{opacity:0}50%{opacity:.2}}

/* ============================================================
   04 HOW THIS PAGE WAS MADE  (full bleed, no cards)
   ============================================================ */
/* The section rule gives every section 10vh of padding top and bottom.
   Here that padding was the section's own ink showing above and below a
   full-bleed image, which read as two black bars stuck to the picture
   rather than as spacing. The bleed IS the section: it starts at the
   top edge and finishes at the bottom one. */
#made{padding:0;position:relative;overflow:hidden}
#made .bleed{position:relative;min-height:min(58vh,500px);display:flex;align-items:center;
  padding-block:clamp(52px,8vh,96px);padding-inline:var(--pad-l) var(--gutter)}
/* the background and the sweep break out to the true viewport edges while
   the text stays locked to the page's own left edge */
#made .bleed > .bg{position:absolute;top:0;bottom:0;left:calc(50% - 50vw);width:100vw;overflow:hidden;
  border-block:1px solid var(--line)}
#made .sweep{left:calc(50% - 50vw);right:auto;width:100vw;inset-block:-30%}
#made .bleed > .bg .fig{position:absolute;inset:0;aspect-ratio:auto;border:0;border-radius:0}
#made .bleed > .bg::after{content:"";position:absolute;inset:0;background:linear-gradient(95deg,rgba(8,6,13,.95) 0%,rgba(8,6,13,.86) 44%,rgba(8,6,13,.5) 100%)}

/* THE SWEEP: one large grainy ribbon crossing the page, the loud moment.
   It appears once, so it lands. Everything else stays quiet around it. */
.sweep{position:absolute;inset:-30% -14%;z-index:1;pointer-events:none;overflow:hidden}
.sweep svg{width:100%;height:100%;display:block;opacity:.9}
.sweep .band-a{fill:url(#sweepgrad)}
.sweep .grainover{
  position:absolute;inset:0;background-image:var(--grain);opacity:.14;
  mix-blend-mode:overlay;pointer-events:none;
}
@media (max-width:820px){.sweep{inset:-20% -30%}}
#made .bleed .inner{position:relative;z-index:3;width:100%}
#made .bleed .inner::before{
  content:"";position:absolute;inset:-14% -30% -14% -12%;z-index:-1;pointer-events:none;
  background:radial-gradient(ellipse 62% 76% at 26% 50%,rgba(8,6,13,.94) 0%,rgba(8,6,13,.74) 48%,rgba(8,6,13,0) 78%);
}
#made p{margin:22px 0 0;color:var(--text-secondary);font-size:clamp(15.5px,1.25vw,18px);max-width:58ch;line-height:1.68}

/* ============================================================
   05 THE AWKWARD QUESTIONS  (quiet stacked list)
   ============================================================ */
.qs{margin-top:44px;border-top:1px solid var(--line)}
.q{border-bottom:1px solid var(--line)}
.q button{
  width:100%;background:none;border:0;cursor:pointer;text-align:left;
  display:flex;align-items:baseline;justify-content:space-between;gap:20px;
  padding:22px 2px;
  font-family:var(--display);font-variation-settings:'wdth' 110,'wght' 700;font-weight:700;
  font-size:clamp(17px,1.75vw,24px);letter-spacing:-.018em;line-height:1.3;
  transition:color .35s var(--ease-out)
}
.q button:hover{color:var(--accent)}
.q .pm{flex:0 0 auto;width:15px;height:15px;position:relative;align-self:center}
.q .pm::before,.q .pm::after{content:"";position:absolute;inset:50% 0 auto;height:1.5px;background:currentColor;transform-origin:center;transition:transform .45s var(--ease-out);border-radius:2px}
.q .pm::after{transform:rotate(90deg)}
.q[open] .pm::after,.q.open .pm::after{transform:rotate(0)}
.q .a{overflow:hidden;height:0;transition:height .55s var(--ease-out)}
.q .a p{margin:0 0 24px;color:var(--text-secondary);max-width:62ch;font-size:clamp(15px,1.15vw,16.5px);line-height:1.68;
  opacity:0;transform:translateY(8px);transition:opacity .5s var(--ease-out) .06s,transform .5s var(--ease-out) .06s}
.q.open .a p{opacity:1;transform:none}
.q.open button{color:var(--accent)}

/* ============================================================
   06 START  (the single call to action)
   ============================================================ */
#start{padding-bottom:clamp(90px,12vh,150px)}
.startcard{
  max-width:760px;margin:0 auto;text-align:center;
  background:var(--panel);border:1px solid var(--line);border-radius:22px;
  padding:clamp(28px,5vw,60px);position:relative;overflow:hidden;
}
.startcard::before{content:"";position:absolute;inset:-1px;border-radius:inherit;pointer-events:none;
  background:radial-gradient(ellipse 70% 100% at 50% 0%,var(--accent-muted),transparent 62%)}
.startcard > *{position:relative}
.startcard h2{font-size:clamp(27px,3.4vw,44px);max-width:19ch}
.startcard h2{margin-inline:auto;max-width:16ch}
.startcard .lede{margin-inline:auto;text-align:center;max-width:40ch}
form{margin-top:34px;display:grid;gap:12px;text-align:left}
.row2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
@media (max-width:640px){.row2{grid-template-columns:1fr}}
label{display:block;font:500 10.5px/1 var(--mono);letter-spacing:.16em;text-transform:uppercase;color:var(--text-secondary);margin-bottom:8px}
input,textarea{
  width:100%;background:var(--panel-2);color:var(--text-primary);
  border:1px solid var(--border-ui);border-radius:11px;padding:13px 15px;
  font:400 15px var(--body);
  transition:border-color .35s var(--ease-out),background .35s var(--ease-out)
}
input::placeholder,textarea::placeholder{color:var(--text-secondary);opacity:.75}
input:focus,textarea:focus{outline:none;border-color:var(--accent);background:#221A2E}
textarea{resize:vertical;min-height:104px;line-height:1.55}
form .btn{width:100%;margin-top:6px;padding-block:16px}
.fineprint{margin:14px 0 0;font-size:13px;color:var(--text-secondary);text-align:center}
.fineprint a{color:var(--accent);text-decoration:none;border-bottom:1px solid var(--accent-muted)}
.fineprint a:hover{border-bottom-color:var(--accent)}
.sent{display:none;text-align:center;padding:22px 0 4px}
.sent.show{display:block;animation:pop .6s var(--ease-out)}
@keyframes pop{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
.sent .tick{width:54px;height:54px;margin:0 auto 16px;color:var(--accent)}
.sent p{margin:0;color:var(--text-secondary);max-width:44ch;margin-inline:auto}
.sent strong{display:block;color:var(--text-primary);font-family:var(--display);font-variation-settings:'wdth' 114,'wght' 800;font-weight:800;font-size:clamp(21px,2.4vw,29px);letter-spacing:-.02em;margin-bottom:10px}
form.gone{display:none}

/* ============================================================
   THE ARCHIVE'S TABS
   Pill controls carrying their own count, taking their colours from
   whichever tone they are standing on like every other button here.
   ============================================================ */
.tabs{display:flex;flex-wrap:wrap;gap:7px;margin:22px 0 0}
.tab{
  display:inline-flex;align-items:center;gap:8px;
  padding:12px 15px;min-height:44px;border-radius:999px;
  border:1px solid var(--border-ui);background:none;cursor:pointer;
  color:var(--text-secondary);font:500 13.5px var(--body);letter-spacing:.004em;
  transition:background .32s var(--ease-out),border-color .32s var(--ease-out),color .32s var(--ease-out),transform .32s var(--ease-out);
}
.tab em{font:500 10px/1 var(--mono);letter-spacing:.11em;text-transform:uppercase;font-style:normal;color:var(--text-faint);transition:color .32s var(--ease-out),opacity .32s var(--ease-out)}
.tab:hover{color:var(--text-primary);transform:translateY(-1px)}
.tab[aria-selected="true"]{background:var(--btn-bg,var(--accent));border-color:var(--btn-bg,var(--accent));color:var(--btn-fg,#fff)}
.tab[aria-selected="true"] em{color:inherit;opacity:.68}
.pan[hidden]{display:none}
/* the panel used to start immediately under the tabs, which put the
   television commercial's frame hard against the word above it */
.pan{animation:panin .5s var(--ease-out) both;margin-top:clamp(20px,3vh,38px)}
@keyframes panin{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){ .pan{animation:none} .tab:hover{transform:none} }
/* Six categories stacked into six full-width rows on a phone is a menu,
   not a filter, and it pushed the work itself off the screen. They stay
   on one line and scroll sideways instead, which is the one place a
   sideways scroll is the right answer: a short row of chips. */
@media (max-width:700px){
  .tabs{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;-webkit-overflow-scrolling:touch;padding-bottom:6px}
  .tabs::-webkit-scrollbar{display:none}
  .tab{flex:0 0 auto}
}

/* ---- the brochures ----
   Real client documents. The tile shows the cover and the link opens the
   PDF itself, so a visitor pages through the thing that went to print
   rather than looking at a photograph of its first page. Each one was
   rasterised page by page and rebuilt at reading resolution: the print
   masters run to 37 and 58 MB apiece, which is the right size to send a
   printer and the wrong size to put behind a link. */
.brochgrid{
  --cols:3;
  display:grid;grid-template-columns:repeat(var(--cols),minmax(0,1fr));
  gap:clamp(16px,2vw,28px);
  padding-inline:var(--pad-l) var(--gutter);
  max-width:1320px;margin-inline:auto;
}
/* The column count is set per grid rather than left to a single number,
   because the two grids that use this hold different amounts: four
   brochures and three newspaper pages. Three columns would strand the
   fourth brochure alone on a second row, which is the one thing a shelf
   of documents must not do. Four across while there is room, then two
   and two — never three and one. */
#brochgrid{--cols:4}
@media (max-width:1180px){ #brochgrid{--cols:2} }
@media (max-width:860px){ .brochgrid{--cols:2} }
@media (max-width:540px){ .brochgrid{--cols:1} }
.broch{display:flex;flex-direction:column;gap:14px;text-decoration:none;color:inherit}
.broch-shot{
  /* the single track is minmax(0,1fr), not auto. An auto row sizes
     itself to its content, so the image's height:100% resolved against a
     row that had already grown to fit the image — and the tallest page,
     a full broadsheet, overflowed and was clipped by the overflow:hidden
     below. A 1fr row is the tile's own height, so contain has something
     real to contain against. */
  display:grid;grid-template:minmax(0,1fr) / minmax(0,1fr);place-items:center;
  overflow:hidden;border-radius:12px;border:1px solid var(--line);
  background:var(--panel);aspect-ratio:3/4;padding:10px;
  transition:transform .55s var(--ease-out),border-color .4s var(--ease-out),box-shadow .5s var(--ease-out);
}
/* Contain, not cover: these are documents. One of the brochures is a
   landscape catalogue and one of the newspaper pages is a full broadsheet
   taller than the tile. Cropping either cuts the masthead or the title
   off, which is the one job a cover has.

   width/height 100% rather than max-width/max-height. The tile is a grid
   with place-items:center and its own aspect-ratio, so a max-height of
   100% resolves against a row that has already grown to fit the image —
   circular, and it let the tallest page overflow and get clipped by the
   tile's own overflow:hidden. Filling the box and letting object-fit do
   the letterboxing is bounded by construction. */
.broch-shot img{width:100%;height:100%;object-fit:contain;display:block;
  border-radius:4px;transition:transform 1.1s var(--ease-out)}
.broch:hover .broch-shot{transform:translateY(-6px);border-color:var(--accent);box-shadow:0 24px 54px -28px rgba(221,18,103,.55)}
.broch:hover .broch-shot img{transform:scale(1.04)}
.broch-meta{display:flex;flex-direction:column;gap:3px}
.broch-meta strong{font-family:var(--display);font-variation-settings:'wdth' 110,'wght' 700;font-weight:700;font-size:clamp(16px,1.5vw,20px);letter-spacing:-.015em}
.broch-meta em{font:500 10.5px/1.5 var(--mono);letter-spacing:.16em;text-transform:uppercase;font-style:normal;color:var(--text-secondary)}
/* A press card carries one line a brochure does not: what kind of
   appearance it was. An editorial feature and a full-page advertisement
   are not the same claim about a client, and an archive that blurs the
   two is quietly taking credit for a story somebody else chose to run. */
.broch-meta .kind{margin-top:2px;font:500 10px/1.5 var(--mono);letter-spacing:.14em;text-transform:uppercase;font-style:normal;color:var(--accent)}
.broch-meta .go{margin-top:6px;font:600 13px var(--body);color:var(--accent)}
.broch:hover .broch-meta .go{text-decoration:underline;text-underline-offset:3px}

/* the creator films are all the same vertical shape, so they get an even
   grid rather than the feature-plus-strip the TV work uses */
.infgrid{
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(14px,1.7vw,22px);
}
@media (max-width:860px){ .infgrid{grid-template-columns:1fr 1fr} }
@media (max-width:460px){ .infgrid{grid-template-columns:1fr} }
.infgrid .film--tall{width:auto;flex:none}

/* ============================================================
   CASE STUDIES
   Editorial, not cards. The client's name holds still in the left
   column while the argument scrolls past it, which is the one thing
   a case study needs and a grid of thumbnails cannot do.
   ============================================================ */
.cases{list-style:none;margin:clamp(30px,4.6vh,54px) 0 0;padding:0}
.case{
  display:grid;grid-template-columns:minmax(210px,28%) 1fr;gap:clamp(22px,4vw,60px);
  padding-block:clamp(36px,5.2vh,60px);border-top:1px solid var(--line);
}
.case:last-child{border-bottom:1px solid var(--line)}
.case-id{position:sticky;top:clamp(92px,13vh,132px);align-self:start}
.case-id .num{display:block;font:500 11px/1 var(--mono);letter-spacing:.2em;color:var(--accent);margin-bottom:14px}
.case-name{
  font-family:var(--display);font-variation-settings:'wdth' 84,'wght' 800;font-weight:800;
  font-size:clamp(23px,2.5vw,36px);line-height:1.02;letter-spacing:-.03em;margin:0;
}
.case-sector{margin:12px 0 0;font:500 10px/1.6 var(--mono);letter-spacing:.13em;text-transform:uppercase;color:var(--text-secondary)}

.case-body{display:grid;gap:clamp(20px,2.4vw,30px);min-width:0}
.case-f{display:grid;grid-template-columns:112px 1fr;gap:clamp(14px,2vw,26px);align-items:baseline}
.case-f > span{font:500 10px/1.8 var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--text-faint)}
.case-f p{margin:0;font-size:clamp(14.5px,1.1vw,16px);line-height:1.68;color:var(--text-secondary);max-width:60ch}
.case-f ul{margin:0;padding:0;list-style:none;display:grid;gap:8px}
.case-f ul li{position:relative;padding-left:19px;font-size:14.5px;line-height:1.6;color:var(--text-secondary)}
.case-f ul li::before{content:"";position:absolute;left:0;top:.66em;width:8px;height:1px;background:var(--accent)}
/* The only place a result is allowed to speak. Where the client has
   written a review it speaks in their words; where they have not, it
   speaks in ours — and in neither case in a number we made up.

   All four now carry the client's name underneath, so the four read as
   one set. The two that are quotations use <cite>, which is what <cite>
   is for; the two that are our own account of the work use <b> and are
   not wrapped in a blockquote. It looks identical and it is styled
   identically — but the markup never tells a machine that Monotel or
   The Yellow Turtle said a sentence they did not write. */
.case-f blockquote,.case-f .res{
  margin:0;font-size:clamp(16px,1.45vw,20px);line-height:1.5;
  color:var(--text-primary);letter-spacing:-.012em;max-width:48ch;
}
.case-f cite,.case-f .by{display:block;margin-top:13px;font:500 10px/1 var(--mono);letter-spacing:.16em;text-transform:uppercase;color:var(--accent);font-style:normal;font-weight:500}

/* ---- THE DECK ----
   Each case pins under the header and the next one rides up and covers
   it, so the four are dealt rather than scrolled past.

   Three things have to be true or it falls apart, and all three are
   easy to miss:

   1. Every card needs an OPAQUE background. A sticky element with a
      transparent one does not hide anything; you get four sets of type
      printed on top of each other.
   2. Every card must be the SAME height. The incoming card only covers
      the one beneath from its own top edge down — so a short card
      landing on a tall one leaves the tall one's last paragraph poking
      out underneath, which looks like a bug rather than a deck.
      --deck-h is set to clear the tallest of the four.
   3. The whole card must fit between the header and the bottom of the
      window, or the reader can never see the end of one. That is why
      this is gated on height as well as width: on a short laptop the
      deck is simply not used and the cases run as they always did.

   The 18px step keeps a sliver of each previous card showing, which is
   what makes it read as a stack rather than as one card being replaced. */
@media (min-width:1000px) and (min-height:790px){
  /* Every one of these four numbers is a height budget, and they have to
     add up: --deck-top + three × --deck-step + --deck-h must clear the
     bottom of the shortest window this rule is allowed to run in. At the
     790px gate that is 80 + 45 + 460 = 585, leaving 205px. Loosen any one
     of them and the fourth card's last line falls off the screen where
     it cannot be scrolled to, because the card is pinned.

     --deck-h was 630px, which was the right answer for copy that is no
     longer there. Every card is forced to this height, so when the case
     text was cut the cards did not shrink: they kept 279 pixels of empty
     card under the last line, and the deck still cost two and a half
     thousand pixels of scrolling to read four short cards.

     Measured against the new copy the tallest of the four needs 407px.
     460 clears it with 53px in hand for a font that loads late or a
     line that wraps differently, and takes 760px out of the scroll. */
  .cases{--deck-top:clamp(80px,8.6vh,92px);--deck-step:15px;--deck-h:460px;--deck-pad:clamp(14px,1.6vw,24px)}
  .case:nth-child(1){--i:0} .case:nth-child(2){--i:1}
  .case:nth-child(3){--i:2} .case:nth-child(4){--i:3}
  .case{
    position:sticky;top:calc(var(--deck-top) + var(--i,0) * var(--deck-step));
    /* height, not min-height. min-height lets the tall ones stay tall,
       and a tall card under a short one is the exact failure this is
       meant to avoid. */
    height:var(--deck-h);align-content:start;
    background:var(--canvas);
    padding-block:clamp(22px,2.7vh,30px);
    padding-inline:var(--deck-pad);margin-inline:calc(-1 * var(--deck-pad));
    /* the shadow sits ABOVE the top edge only, so a card reads as
       lifted over the one it is covering and nothing else changes */
    box-shadow:0 -15px 28px -20px rgba(12,9,19,.32);
  }
  /* The four fields sit closer together than they do in a column that
     can run as long as it likes, and they stay at the top of the card.

     Spreading them to fill the height was tried and is worse: every card
     is forced to the same height, so on the short ones space-between
     opened two-hundred-pixel voids between the fields and pressed the
     client's name flat against the bottom edge, where the next card in
     the deck crops it. Air under a short card reads as a card. Air
     between its own paragraphs reads as a bug. */
  .case-body{gap:clamp(12px,1.35vw,17px);align-content:start}
  /* the name no longer needs its own sticky: the whole card is pinned */
  .case-id{position:static}
  /* the deck needs room to be dealt in. Without it the last card is
     pinned for barely a moment before the marquee arrives. */
  .cases{padding-bottom:clamp(60px,9vh,120px)}
}
@media (prefers-reduced-motion:reduce){
  .case{position:static;box-shadow:none;min-height:0}
}

@media (max-width:900px){
  .case{grid-template-columns:1fr;gap:20px;padding-block:34px}
  .case-id{position:static}
  .case-f{grid-template-columns:1fr;gap:9px}
  .case-f > span{line-height:1.2}
}

/* ============================================================
   THE MENU
   Only reachable below 900px, where the inline links are gone. Same
   <dialog> machinery as the briefing, for the same reason: the focus
   trap, the escape key and the inert page behind it are all free.
   ============================================================ */
.menu{
  position:fixed;inset:0;margin:0;padding:0;border:0;
  width:100vw;max-width:100vw;height:100dvh;max-height:100dvh;
  background:transparent;overflow:hidden;color-scheme:dark;
}
.menu[open]{display:block}
.menu::backdrop{background:rgba(5,4,9,.6);opacity:0;transition:opacity .4s var(--ease-out)}
.menu.on::backdrop{opacity:1}
.menu-in{
  position:relative;height:100%;display:flex;flex-direction:column;
  padding:calc(var(--gutter)) var(--gutter) max(26px,env(safe-area-inset-bottom));
  background:linear-gradient(165deg,#150F1F 0%,#0A0810 62%);
  color:var(--text-primary);
  clip-path:inset(0 0 100% 0);
  transition:clip-path .62s var(--ease-out);
}
.menu.on .menu-in{clip-path:inset(0 0 0 0)}
.menu-in::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(ellipse 80% 44% at 100% 0%,rgba(221,18,103,.2),transparent 62%)}
.menu-in > *{position:relative;z-index:1}

.menu-nav{display:flex;flex-direction:column;margin-top:clamp(40px,9vh,86px)}
.menu-nav a{
  display:flex;align-items:baseline;gap:16px;text-decoration:none;
  padding-block:clamp(9px,1.6vh,15px);border-bottom:1px solid var(--line);
  font-family:var(--display);font-variation-settings:'wdth' 84,'wght' 700;font-weight:700;
  font-size:clamp(30px,9.4vw,46px);line-height:1.04;letter-spacing:-.03em;
  color:var(--text-primary);
  opacity:0;transform:translateY(14px);
  transition:opacity .5s var(--ease-out),transform .55s var(--ease-out),color .3s var(--ease-out);
  transition-delay:calc(var(--i) * 52ms + 130ms);
}
.menu.on .menu-nav a{opacity:1;transform:none}
.menu-nav a:active{color:var(--accent)}
.menu-nav b{font:500 10.5px/1 var(--mono);letter-spacing:.18em;color:var(--accent);flex:none}

.menu-foot{margin-top:auto;padding-top:26px;
  opacity:0;transition:opacity .5s var(--ease-out) .42s}
.menu.on .menu-foot{opacity:1}
.menu-foot .btn{width:100%;justify-content:center;padding-block:16px}
.menu-contact{display:flex;flex-wrap:wrap;gap:6px 22px;margin-top:20px}
.menu-contact a{font-size:14px;color:var(--text-secondary);text-decoration:none;padding-block:9px;min-height:22px}
.menu-contact a:hover{color:var(--text-primary)}

@media (prefers-reduced-motion:reduce){
  .menu::backdrop,.menu-in,.menu-nav a,.menu-foot{transition:none}
  .menu-in{clip-path:none}
}

/* ============================================================
   06 WHAT WE BELIEVE
   The one section on the page with nothing for sale in it. Two lines
   of argument, three positions and a question, set in the condensed
   display cut so it reads as the house voice rather than as copy.
   ============================================================ */
#about{padding-block:clamp(84px,13vh,168px)}
.creed{
  font-family:var(--display);font-variation-settings:'wdth' 74,'wght' 800;font-weight:800;
  font-size:clamp(25px,4.9vw,72px);line-height:1;letter-spacing:-.032em;
  margin:26px 0 0;max-width:none;
}
/* Each sentence now has to survive on one line, which is twice the
   characters the old half-lines carried. Rather than shrink the
   statement, the width axis goes to the condensed end of Archivo — the
   same 62 the hero headline uses — so it keeps its size and still fits.
   Below the breakpoint it is allowed to wrap; two lines on a phone is
   the right answer there and nothing has to be cut to get it. */
@media (min-width:861px){
  .creed{font-variation-settings:'wdth' 62,'wght' 800;font-size:clamp(34px,4.55vw,66px)}
}
.creed span{display:block}
/* the two words the sentence turns on */
.creed em{font-style:normal;color:var(--accent)}
.creed-end{
  margin:28px 0 0;max-width:34ch;
  font-size:clamp(16px,1.4vw,21px);line-height:1.55;color:var(--text-secondary);
}
.creed-end b{color:var(--text-primary);font-weight:600}

.stances{
  list-style:none;margin:clamp(46px,7vh,84px) 0 0;padding:0;
  display:grid;gap:clamp(24px,2.8vw,40px);grid-template-columns:repeat(3,1fr);
}
.stances li{border-top:1px solid var(--line);padding-top:20px}
.stances li b{display:block;font:500 10.5px/1 var(--mono);letter-spacing:.2em;color:var(--accent);margin-bottom:16px}
.stances h3{
  font-family:var(--display);font-variation-settings:'wdth' 96,'wght' 700;font-weight:700;
  font-size:clamp(18px,1.55vw,23px);line-height:1.14;letter-spacing:-.018em;margin:0 0 12px;
}
.stances p{margin:0;color:var(--text-secondary);font-size:14.5px;line-height:1.66}
@media (max-width:920px){ .stances{grid-template-columns:1fr 1fr} }
@media (max-width:620px){ .stances{grid-template-columns:1fr;gap:24px} }

.about-q{
  margin:clamp(42px,6vh,70px) 0 0;padding-top:clamp(26px,4vh,42px);border-top:1px solid var(--line);
  font-size:clamp(17px,1.7vw,25px);line-height:1.44;max-width:36ch;letter-spacing:-.012em;
}
.about-q b{font-weight:600;color:var(--text-primary)}

/* The reviews block is moving up the page, from outside the below-fold
   frame to inside it, where --pad-l is defined and would have handed
   it 72px of inline padding it has never had. Its own padding is
   stated here so the move changes nothing about how it looks: the
   quote row still runs the full width of the screen. */
.quotesec{padding-inline:0}

/* ---- the reviews stay one moving line ----
   A two by two grid did put all four on screen at once, and it cost a
   screen and a half of page to do it: four long quotes stacked two deep
   is a wall. Back to one row that moves, with the cards narrower and the
   quotes set smaller so more than two are legible at a time, and the
   progress bar under it saying how much is left. */
@media (min-width:1000px){
  /* Four cards and three gaps, sized off the row itself so that all
     four are always on the screen at once with the last one a little
     short of the edge. A fixed pixel width could not do this: at 1440
     it left the fourth card entirely outside the viewport. */
  .quote{width:calc((100% - 3 * var(--qgap)) / 3.62);padding:clamp(18px,1.9vw,26px)}
  .quote blockquote{font-size:clamp(14px,1vw,15.5px);line-height:1.56}
  .quotes{--qgap:clamp(14px,1.5vw,22px);gap:var(--qgap)}
}

/* ============================================================
   THE PICTURE, FULL SIZE
   A photograph opens on top of the page it belongs to, not as a raw
   file in another tab. Same stage as the two panels below: a fixed
   dialog filling the viewport, scoped to [open] so a closed one stays
   closed, and a backdrop that fades rather than snapping on.
   ============================================================ */
.lbox{
  position:fixed;inset:0;margin:0;padding:0;border:0;
  width:100vw;max-width:100vw;height:100dvh;max-height:100dvh;
  background:transparent;overflow:hidden;color-scheme:dark;
}
.lbox[open]{display:flex;align-items:center;justify-content:center}
.lbox::backdrop{background:rgba(5,4,9,.92);opacity:0;transition:opacity .34s var(--ease-out)}
.lbox.on::backdrop{opacity:1}
.lbox-fig{
  margin:0;display:flex;flex-direction:column;align-items:center;gap:14px;
  max-width:100vw;padding:clamp(52px,7vh,74px) clamp(12px,4vw,64px) clamp(18px,3vh,34px);
  opacity:0;transform:scale(.985);
  transition:opacity .34s var(--ease-out),transform .34s var(--ease-out);
}
.lbox.on .lbox-fig{opacity:1;transform:none}
/* the picture decides its own size inside what is left of the screen,
   and never grows past its own pixels — an upscaled JPEG on a black
   field looks like a mistake */
.lbox-fig img{
  display:block;max-width:100%;max-height:calc(100dvh - clamp(120px,18vh,170px));
  width:auto;height:auto;object-fit:contain;border-radius:6px;
  background:var(--panel);
}
.lbox-cap{
  display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;justify-content:center;
  font:500 10.5px/1.5 var(--mono);letter-spacing:.16em;text-transform:uppercase;
  color:rgba(255,255,255,.72);text-align:center;max-width:70ch;
}
.lbox-cap i{font-style:normal;color:rgba(255,255,255,.42)}

/* the close button, and the two arrows. All three are the same object:
   a ring that only fills in on hover, like every other control here */
.lbox-x,.lbox-arrow{
  position:absolute;z-index:2;display:flex;align-items:center;justify-content:center;
  width:46px;height:46px;border-radius:50%;cursor:pointer;padding:0;
  background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.22);
  transition:background .3s var(--ease-out),border-color .3s var(--ease-out),transform .3s var(--ease-out);
}
.lbox-x:hover,.lbox-arrow:hover{background:var(--accent);border-color:var(--accent)}
.lbox-x{top:clamp(10px,2vh,20px);right:clamp(10px,2.4vw,22px)}
.lbox-x span{position:absolute;width:16px;height:1.5px;background:#fff;border-radius:2px}
.lbox-x span:first-child{transform:rotate(45deg)}
.lbox-x span:last-child{transform:rotate(-45deg)}
.lbox-arrow{top:50%;margin-top:-23px}
.lbox-prev{left:clamp(8px,2vw,26px)}
.lbox-next{right:clamp(8px,2vw,26px)}
/* a chevron drawn from one corner of a square, so there is no icon file
   and no second element to keep centred */
.lbox-arrow::before{
  content:"";width:11px;height:11px;border-top:1.5px solid #fff;border-right:1.5px solid #fff;
}
.lbox-prev::before{transform:rotate(-135deg);margin-left:4px}
.lbox-next::before{transform:rotate(45deg);margin-right:4px}
.lbox-arrow[hidden]{display:none}
/* on a phone the arrows would sit on top of the picture, so they go to
   the bottom corners where the caption already is; a swipe is the real
   gesture there and these are the fallback */
@media (max-width:640px){
  .lbox-arrow{top:auto;bottom:clamp(10px,2vh,18px);margin-top:0;width:42px;height:42px}
  .lbox-fig{padding-bottom:clamp(62px,9vh,80px)}
}
@media (prefers-reduced-motion:reduce){
  .lbox::backdrop,.lbox-fig{transition:none}
  .lbox-fig{transform:none}
}

/* ============================================================
   THE BRAND BRIEFING
   The primary call to action does not open a contact form. It opens a
   briefing: three short moves, with a sheet down the side that writes
   itself out of the answers as they are given. By the last screen the
   visitor is reading the brief they just wrote, not trusting a submit
   button.

   Built on <dialog>, so the focus trap, the escape key and the inert
   background are the browser's job instead of three hundred lines of
   mine. If the browser has no showModal, the button keeps its old
   behaviour and goes to the form; nothing is ever dead.
   ============================================================ */
/* the dialog element is only the stage: it fills the viewport, centres
   the panel and does nothing else. The display has to be scoped to
   [open] or it would beat the user agent's rule that keeps a closed
   dialog hidden, and the panel would sit on the page permanently. */
.brief{
  position:fixed;inset:0;margin:0;padding:0;border:0;
  width:100vw;max-width:100vw;height:100dvh;max-height:100dvh;
  background:transparent;overflow:hidden;color-scheme:dark;
}
.brief[open]{display:flex;align-items:center;justify-content:center;padding:clamp(14px,3.4vh,40px)}
/* .btn and .brief-foot both set a display, which would otherwise win
   against the hidden attribute's UA rule and leave the buttons on
   screen while the script thinks they are gone */
.brief [hidden]{display:none!important}
.brief::backdrop{background:rgba(5,4,9,.74);opacity:0;transition:opacity .5s var(--ease-out)}
.brief.on::backdrop{opacity:1}
@supports (backdrop-filter:blur(2px)){ .brief::backdrop{backdrop-filter:blur(7px) saturate(.72)} }

/* a panel, not a takeover. The page stays visible around its edges,
   which is what keeps this feeling like a drawer the site opened
   rather than a second website that just loaded. */
.brief-shell{
  position:relative;
  width:100%;max-width:1160px;height:min(720px,100%);
  display:grid;grid-template-columns:minmax(290px,26%) 1fr;
  margin:0;gap:0;text-align:left;
  background:var(--canvas);color:var(--text-primary);
  border:1px solid var(--line);border-radius:24px;overflow:hidden;
  box-shadow:0 50px 130px -28px rgba(0,0,0,.86),0 0 0 1px rgba(255,255,255,.02) inset;
  opacity:0;transform:translateY(22px) scale(.988);transform-origin:50% 100%;
  transition:opacity .45s var(--ease-out),transform .62s var(--ease-out);
}
.brief.on .brief-shell{opacity:1;transform:none}

/* --- the sheet down the side --- */
.brief-aside{
  position:relative;overflow:hidden;display:flex;flex-direction:column;
  padding:clamp(26px,3.4vw,50px);
  background:linear-gradient(168deg,#150F1F 0%,#0A0810 68%);
  border-right:1px solid var(--line);
}
.brief-aside::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(ellipse 90% 60% at 0% 0%,rgba(221,18,103,.17),transparent 62%)}
.brief-aside > *{position:relative;z-index:1}
.brief-eyebrow{margin:0 0 18px;font:500 10.5px/1 var(--mono);letter-spacing:.22em;text-transform:uppercase;color:var(--accent)}
.brief h2{
  font-family:var(--display);font-variation-settings:'wdth' 78,'wght' 800;font-weight:800;
  font-size:clamp(30px,2.9vw,50px);line-height:.92;letter-spacing:-.032em;margin:0;max-width:11ch;
}
.brief-note{margin:20px 0 0;font-size:14px;line-height:1.62;color:var(--text-secondary);max-width:34ch}

/* the brief, assembling itself. Rows appear only once they have an
   answer, so an empty sheet is an empty sheet and never a row of
   placeholder dashes pretending to be content. */
.brief-sheet{margin-top:auto;padding-top:26px}
.brief-sheet-l{margin:0 0 12px;font:500 10px/1 var(--mono);letter-spacing:.2em;text-transform:uppercase;color:var(--text-faint)}
.brief-sum{display:grid;gap:9px;margin:0;padding:0;list-style:none}
.brief-sum li{display:grid;grid-template-columns:62px 1fr;gap:12px;align-items:baseline;
  font-size:13.5px;line-height:1.45;animation:briefrow .5s var(--ease-out) both}
.brief-sum i{font:500 9.5px/1.6 var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--text-faint);font-style:normal}
.brief-sum b{font-weight:600;color:var(--text-primary)}
.brief-sum .empty{grid-template-columns:1fr;color:var(--text-faint);font-size:13px}
@keyframes briefrow{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}

/* the three moves, as a spine */
.brief-nav{list-style:none;margin:30px 0 0;padding:0;display:grid;gap:14px}
.brief-nav li{display:flex;align-items:center;gap:12px;font-size:13.5px;color:var(--text-faint);transition:color .4s var(--ease-out)}
.brief-nav i{font-style:normal}
.brief-nav b{width:24px;height:24px;flex:none;display:grid;place-items:center;border-radius:50%;
  border:1px solid var(--border-ui);font:500 9.5px/1 var(--mono);color:var(--text-faint);
  transition:background .4s var(--ease-out),border-color .4s var(--ease-out),color .4s var(--ease-out)}
.brief-nav li.is-on{color:var(--text-primary)}
.brief-nav li.is-on b{background:var(--accent);border-color:var(--accent);color:#fff}
.brief-nav li.is-done{color:var(--text-secondary)}
.brief-nav li.is-done b{border-color:var(--accent);color:var(--accent)}

/* --- the working column --- */
.brief-main{display:grid;grid-template-rows:1fr auto;min-height:0;position:relative}
/* the move is centred by auto margins rather than by centring the
   scroll container, because a centred flex container clips its own
   overflow at the top and a long move on a short screen would lose
   its heading off the edge. Auto margins collapse to zero the moment
   the content is taller than the box. */
.brief-body{display:flex;flex-direction:column;overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;
  padding:clamp(26px,3.2vw,48px) clamp(20px,3.4vw,56px) clamp(26px,3.2vw,44px)}
.brief-body > .brief-step{margin-block:auto}
.brief-foot{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  border-top:1px solid var(--line);background:var(--canvas);
  padding:16px clamp(20px,3.6vw,60px);
}
.brief-foot .spacer{flex:1 1 auto}
.brief-count{font:500 10.5px/1 var(--mono);letter-spacing:.16em;text-transform:uppercase;color:var(--text-faint)}

.brief-x{
  position:absolute;z-index:4;top:12px;right:12px;width:42px;height:42px;
  display:grid;place-items:center;border:1px solid var(--line);border-radius:50%;
  background:rgba(12,9,19,.72);cursor:pointer;color:var(--text-secondary);
  transition:color .3s var(--ease-out),border-color .3s var(--ease-out),transform .3s var(--ease-out);
}
.brief-x:hover{color:var(--text-primary);border-color:var(--border-ui);transform:rotate(90deg)}
.brief-x svg{width:15px;height:15px}

/* --- a move ---
   Each move is a <section>, which means the page-wide section rule
   was handing it 10vh of vertical padding and a gutter it has no use
   for inside a panel. Said again here rather than renamed, because
   these really are sections and should stay ones. */
.brief-step{display:none;padding:0}
.brief-step.is-on{display:block;animation:briefin .55s var(--ease-out) both}
@keyframes briefin{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:none}}
.brief-step:focus{outline:none}
.brief-h{
  font-family:var(--display);font-variation-settings:'wdth' 100,'wght' 700;font-weight:700;
  font-size:clamp(23px,2.5vw,34px);line-height:1.04;letter-spacing:-.028em;margin:0;max-width:30ch;
  text-wrap:balance;
}
.brief-sub{margin:12px 0 0;color:var(--text-secondary);font-size:15px;line-height:1.6;max-width:50ch}
.brief-fields{display:grid;gap:18px;margin-top:30px}
.brief-fields .hint{margin:7px 0 0;font-size:12.5px;color:var(--text-faint)}

/* the picks. A real checkbox or radio under each one, so the keyboard,
   the screen reader and the form all behave without being taught. */
.chipset{border:0;margin:0;padding:0;min-width:0}
.chipset legend{padding:0;font:500 10.5px/1 var(--mono);letter-spacing:.16em;text-transform:uppercase;color:var(--text-secondary);margin-bottom:14px}
.picks{display:flex;flex-wrap:wrap;gap:9px}
.pick{position:relative;margin:0;display:inline-flex}
.pick input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;margin:0}
/* the page-wide label rule is mono, uppercase and widely tracked,
   which is right for a field label and wrong for a thing you press.
   Both are inherited, so both have to be said again here. */
.pick span{
  display:inline-flex;align-items:center;gap:9px;
  padding:12px 17px;border-radius:999px;border:1px solid var(--border-ui);
  font:500 14px var(--body);color:var(--text-primary);cursor:pointer;
  text-transform:none;letter-spacing:.004em;
  transition:background .32s var(--ease-out),border-color .32s var(--ease-out),color .32s var(--ease-out),transform .32s var(--ease-out);
  -webkit-user-select:none;user-select:none;
}
.pick span::before{content:"";width:7px;height:7px;flex:none;border-radius:50%;background:currentColor;opacity:.26;
  transition:opacity .32s var(--ease-out),background .32s var(--ease-out),transform .32s var(--ease-out)}
.pick:hover span{border-color:var(--text-secondary);transform:translateY(-1px)}
.pick input:checked + span{background:var(--accent);border-color:var(--accent);color:#fff}
.pick input:checked + span::before{opacity:1;background:#fff;transform:scale(1.15)}
.pick input:focus-visible + span{outline:2px solid var(--accent);outline-offset:3px}
.brief-step.is-on .pick{animation:chipin .52s var(--ease-out) both;animation-delay:calc(var(--i,0) * 34ms)}
@keyframes chipin{from{opacity:0;transform:translateY(10px) scale(.97)}to{opacity:1;transform:none}}

/* the briefing's own fields override the page-wide form rules, which
   were written for the one-column enquiry card further up */
#briefform .btn{width:auto;margin-top:0;padding-block:13px}
#briefform label{margin-bottom:8px}
#briefform .pick,#briefform .pick span{margin-bottom:0}
#briefform select{
  width:100%;background:var(--panel-2);color:var(--text-primary);
  border:1px solid var(--border-ui);border-radius:11px;padding:13px 15px;
  font:400 15px var(--body);appearance:none;cursor:pointer;
  background-image:linear-gradient(45deg,transparent 50%,var(--text-secondary) 50%),linear-gradient(135deg,var(--text-secondary) 50%,transparent 50%);
  background-position:calc(100% - 20px) calc(50% + 2px),calc(100% - 14px) calc(50% + 2px);
  background-size:6px 6px,6px 6px;background-repeat:no-repeat;
}
#briefform select:focus{outline:none;border-color:var(--accent)}
#briefform .bad{border-color:var(--accent);background:#25142099}
/* ---- the calendar ---- */
.calrow{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,.72fr);gap:clamp(16px,2.4vw,30px);align-items:start}
@media (max-width:760px){ .calrow{grid-template-columns:1fr} }

/* ---- THE SLOT PICKER ----
   Calendar on the left, the day's half-hour slots on the right. The
   slot column scrolls on its own rather than growing the panel: there
   are sixteen slots between 11:00 and 18:30 and letting them push the
   dialog taller than the screen would bury the booking button, which is
   the one control that has to stay reachable.

   These are real radio inputs under the skin, so arrow keys move
   between them and the choice survives a form read like any other
   field. */
/* the same step off the sub-line that .brief-fields takes on steps 1-3.
   This was the one step that had none, so DAY sat directly against the
   last line of the paragraph and the two read as one block. */
.slotwrap{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,.78fr);gap:clamp(16px,2.4vw,30px);align-items:start;margin-top:30px}
@media (max-width:760px){ .slotwrap{grid-template-columns:1fr} }
.slotcol .slot-tz{font-style:normal;float:right;font:500 10px/1.6 var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--text-faint)}
.slots{
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;
  max-height:266px;overflow-y:auto;padding:2px 4px 2px 2px;scrollbar-width:thin;
}
.slots:empty{display:none}
.slot{
  position:relative;display:block;border:1px solid var(--border-ui);border-radius:10px;
  background:var(--panel-2);cursor:pointer;
  transition:border-color .25s var(--ease-out),background .25s var(--ease-out),color .25s var(--ease-out);
}
.slot input{position:absolute;opacity:0;width:0;height:0}
.slot span{display:block;padding:11px 8px;text-align:center;font:600 13.5px var(--body);color:var(--text-primary)}
@media (hover:hover){ .slot:hover{border-color:var(--accent)} }
.slot:has(input:checked){background:var(--accent);border-color:var(--accent)}
.slot:has(input:checked) span{color:#fff}
.slot input:focus-visible + span{outline:2px solid var(--accent);outline-offset:3px;border-radius:8px}
.slot-empty{margin:10px 0 0;font:500 12.5px var(--body);color:var(--text-faint)}
.slot-empty[hidden]{display:none}
/* the honeypot: pushed off-screen rather than hidden, because a bot that
   skips display:none fields will still fill this one */
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
.cal{border:1px solid var(--border-ui);border-radius:14px;padding:12px 12px 14px;background:var(--panel-2)}
.cal-top{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:10px}
.cal-month{margin:0;flex:1;text-align:center;font:600 13.5px var(--body);letter-spacing:.005em;color:var(--text-primary)}
.cal-nav{
  width:32px;height:32px;flex:none;display:grid;place-items:center;
  border:1px solid var(--line);border-radius:9px;background:none;cursor:pointer;color:var(--text-secondary);
  transition:color .3s var(--ease-out),border-color .3s var(--ease-out),background .3s var(--ease-out);
}
.cal-nav svg{width:9px;height:14px}
.cal-nav:hover:not(:disabled){color:var(--text-primary);border-color:var(--border-ui);background:rgba(255,255,255,.04)}
.cal-nav:disabled{opacity:.3;cursor:default}
.cal-dow,.cal-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:3px}
.cal-dow span{text-align:center;font:500 9.5px/1 var(--mono);letter-spacing:.1em;color:var(--text-faint);padding-bottom:7px}
.cal-day{
  aspect-ratio:1;min-height:34px;display:grid;place-items:center;
  border:0;border-radius:9px;background:none;cursor:pointer;
  font:500 13px var(--body);color:var(--text-primary);
  transition:background .25s var(--ease-out),color .25s var(--ease-out);
}
.cal-day:hover:not(:disabled){background:rgba(255,255,255,.07)}
.cal-day:disabled{color:var(--text-faint);opacity:.34;cursor:default}
.cal-day.is-pick{background:var(--accent);color:#fff;font-weight:600}
.cal-day.is-today:not(.is-pick){box-shadow:inset 0 0 0 1px var(--border-ui)}
.cal-day.pad{visibility:hidden;pointer-events:none}

.brief-err{margin:10px 0 0;font-size:13px;color:#FF7FAE;min-height:0}
.brief-go{text-transform:uppercase;letter-spacing:.07em;font-size:13px;padding-inline:26px}

/* the end of it */
.brief-done{display:none;text-align:center;max-width:44ch;margin:0 auto;padding:clamp(30px,8vh,80px) 0}
.brief-done.show{display:block;animation:briefin .6s var(--ease-out) both}
.brief-done .tick{width:56px;height:56px;margin:0 auto 20px;color:var(--accent)}
.brief-done strong{display:block;font-family:var(--display);font-variation-settings:'wdth' 92,'wght' 800;font-weight:800;
  font-size:clamp(25px,3vw,38px);letter-spacing:-.028em;line-height:1.02;margin-bottom:14px}
.brief-done p{margin:0 auto;color:var(--text-secondary);line-height:1.62}
/* the slot, stated back in the accent so it reads as a fact and not as
   more copy — this is the line somebody screenshots */
.brief-done .done-when{
  color:var(--accent);font:600 15.5px var(--body);letter-spacing:.01em;
  margin:0 auto 12px;padding-bottom:12px;border-bottom:1px solid var(--line);max-width:30ch;
}
.brief-done .row{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:26px}

/* --- the phone --- */
@media (max-width:900px){
  /* on a phone the panel stops being a panel: there is no room to show
     the page around its edges, and a card with 14px of dark either
     side reads as a mistake rather than a frame */
  .brief[open]{padding:0}
  .brief-shell{max-width:none;height:100%;border:0;border-radius:0;box-shadow:none;
    grid-template-columns:1fr;grid-template-rows:auto 1fr}
  .brief-aside{padding:22px var(--gutter) 18px;border-right:0;border-bottom:1px solid var(--line)}
  .brief h2{font-size:clamp(25px,7.4vw,34px);max-width:none;padding-right:52px}
  .brief-note{display:none}
  .brief-eyebrow{margin-bottom:12px}
  /* the spine flattens into three segments of a bar */
  .brief-nav{display:flex;gap:6px;margin-top:16px}
  .brief-nav li{flex:1;height:3px;border-radius:2px;background:var(--line);transition:background .45s var(--ease-out)}
  .brief-nav li.is-on,.brief-nav li.is-done{background:var(--accent)}
  .brief-nav b,.brief-nav i{display:none}
  /* the sheet keeps writing itself, on one line */
  /* the sheet keeps writing itself but is not allowed to push the
     working column off the screen: two lines, and the earliest
     answers are the ones that survive */
  .brief-sheet{margin-top:14px;padding-top:0}
  .brief-sheet-l{display:none}
  .brief-sum{display:flex;flex-wrap:wrap;gap:4px 14px;max-height:41px;overflow:hidden}
  .brief-sum li{display:block;font-size:12.5px;color:var(--text-secondary);
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
  .brief-sum i{display:none}
  .brief-sum b{font-weight:500;color:var(--text-secondary)}
  .brief-sum li + li::before{content:"·";margin-right:14px;color:var(--text-faint)}
  .brief-body{padding:26px var(--gutter) 30px}
  .brief-foot{padding:14px var(--gutter);padding-bottom:max(14px,env(safe-area-inset-bottom))}
  .brief-x{top:10px;right:10px;width:40px;height:40px}
}
@media (max-width:560px){
  /* ---- THE FOOTER, ON ONE ROW ----
     This used to stack into three full-width bands — the counter, then
     Back, then Continue — and measured 164px on a 390x844 screen: a
     fifth of the phone, permanently, on every step. That is what pushed
     the budget step's last field past the fold and got it clipped
     mid-line, which reads as breakage rather than as "scroll down".

     Back and Continue now share one row. Back is sized to its own label
     and Continue takes the rest, so the primary action is still the
     wide one under the thumb and the secondary one is visibly
     secondary. Measured after: 86px, and the step fits without
     scrolling at all.

     BOOK MY BRAND BRIEFING is the longest label and it is the last
     step, where Back is short: it still has room. */
  .brief-foot{flex-wrap:wrap;gap:8px 10px;padding:11px var(--gutter);
    padding-bottom:max(11px,env(safe-area-inset-bottom))}
  .brief-foot .spacer{display:none}
  .brief-count{flex:1 1 100%;order:0;margin:0;font-size:9.5px}
  .brief-foot .btn{justify-content:center}
  #briefback{order:1;flex:0 0 auto;padding-inline:20px}
  #briefnext,#briefgo{order:2;flex:1 1 auto;min-width:0}
  .pick span{padding:11px 15px;font-size:13.5px}

  /* ---- THE SUPPORTING LINE ----
     It sets up the question; it should not be the biggest block on the
     screen. Three lines at 15/1.6 came to 72px above a field that
     needed the room. Same words, one step quieter. */
  .brief-sub{font-size:13.5px;line-height:1.55;margin-top:10px;max-width:42ch}

  /* ---- ROOM BETWEEN THE TWO QUESTIONS ----
     The budget and "what do you want to achieve?" were 18px apart,
     which read as one long field rather than two questions. */
  .brief-fields{gap:26px;margin-top:24px}
  /* step 4 takes the same 24 here, so DAY clears the paragraph above it
     by the same amount every other step clears its own */
  .slotwrap{margin-top:24px}

  /* ---- THE BUDGET, AS ONE COLUMN ----
     Six ranges of nearly equal length in a wrapping row gave five
     ragged lines — four alone at 178, 190, 193 and 193px wide, then two
     sharing the last row at 182 and 149. Measured, not guessed.

     One column makes every option the same width, the same height and
     the same distance apart, with a full-width target for a thumb.
     "Not decided yet" sits in the list like any other answer, which is
     the point of it: nobody should feel stopped for not knowing. */
  .chipset--stack .picks{display:grid;grid-template-columns:1fr;gap:8px}
  .chipset--stack .pick{display:block}
  .chipset--stack .pick span{width:100%;justify-content:flex-start;padding:12px 16px;font-size:14px}

  /* the last field is not left flush against the fold */
  .brief-body{padding-bottom:22px}
  #b-goal{min-height:96px}

  /* ---- STEP 4: ONE SCROLL, NOT TWO ----
     The slot list was a two-column grid capped at 266px with its own
     overflow, sitting inside the panel's own scroller. On a mouse that
     is merely fussy; under a thumb it is a trap — you cannot tell which
     of the two you are dragging, and the inner one swallows the gesture
     until it hits its end.

     Three columns instead of two, and no cap. The times are short
     strings, so a third column costs nothing in legibility and returns
     two rows; losing the cap means the step has exactly one scroll,
     which is the panel's own. */
  .slots{grid-template-columns:repeat(3,minmax(0,1fr));max-height:none;overflow:visible;padding:2px}
  .slot span{padding:11px 6px;font-size:13px}

  /* BOOK MY BRAND BRIEFING beside a Back button is 22 uppercase
     characters in what is left of a 360px screen. At the old padding
     the pair wrapped and the footer went back to 140px on the narrowest
     phones; trimming the inline padding keeps them on one row. */
  .brief-go{padding-inline:14px;letter-spacing:.05em}
}
/* a short screen is the real constraint on a phone in landscape */
@media (max-height:520px){
  .brief-aside{padding-block:14px}
  .brief h2{font-size:22px}
  .brief-sheet{display:none}
}

html.brief-open{overflow:hidden}

@media (prefers-reduced-motion:reduce){
  .brief-shell,.brief::backdrop{transition:none}
  .brief-step.is-on,.brief-step.is-on .pick,.brief-sum li,.brief-done.show{animation:none}
  .brief-x:hover{transform:none}
}

/* ============================================================
   FOOTER
   ============================================================ */
footer{position:relative;z-index:2;background:var(--canvas-2);border-top:1px solid var(--line);
  padding:clamp(46px,7vh,78px) var(--gutter) 0;overflow:hidden}
/* The footer used to centre a 1240px block inside a much wider screen,
   which left the lockup floating in the middle with a field of empty
   dark on both sides. It now starts at the same gutter every other
   section starts at, and the lockup is the size a lockup should be. */
/* Four columns, centred in the page as a block, each one starting at
   the same baseline. Four rather than three because the address column
   was carrying two lines against a field of empty dark. */
.fgrid{max-width:1280px;margin-inline:auto;display:grid;
  grid-template-columns:1.2fr .95fr .9fr .85fr;gap:clamp(20px,2.6vw,44px);align-items:start}
@media (max-width:980px){.fgrid{grid-template-columns:1fr 1fr}}
@media (max-width:520px){.fgrid{grid-template-columns:1fr;text-align:center;justify-items:center}}
footer h3{margin:0 0 14px;font:500 10.5px/1 var(--mono);letter-spacing:.2em;text-transform:uppercase;color:var(--text-secondary)}
footer p,footer a{color:var(--text-secondary);font-size:14.5px;margin:0 0 8px;text-decoration:none;line-height:1.6;display:block}
footer a:hover{color:var(--accent)}
footer .fbrand img{width:clamp(150px,13vw,190px);height:auto;margin-bottom:20px}
footer .fbrand p{max-width:32ch}
/* The tap-target padding stacked on top of the paragraph margin, which
   put a line and a half of air between the email and the phone number.
   One or the other, not both. */
footer .fgrid a{padding-block:6px;margin-bottom:0}
.fhours{margin-top:10px!important;font-size:13px;color:var(--text-faint)}
.fcta{
  margin-top:20px;display:inline-flex;align-items:center;gap:8px;
  background:none;border:1px solid var(--border-ui);border-radius:999px;
  padding:10px 18px;min-height:40px;cursor:pointer;
  font:600 13px var(--body);color:var(--text-primary);
  transition:border-color .3s var(--ease-out),background .3s var(--ease-out);
}
.fcta:hover{border-color:var(--accent);background:rgba(221,18,103,.1)}

/* ---- the accounts ---- */
/* five marks on one line: at 40px with a 10px gap they needed 240px and
   the column gives about 225, so the fifth was wrapping on its own */
/* ---- THE SOCIAL MARKS ----
   Three separate things were wrong here and they compounded.

   1. The rings were not round. width:38px was set, but these are flex
      items and flex-shrink defaults to 1, so they were squeezed to 19px
      in a tight footer column while height stayed 38 — border-radius:50%
      on a 19x38 box is an oval, not a circle. flex:0 0 auto stops the
      squeeze; aspect-ratio keeps it square even if the width ever has to
      give again.

   2. Every glyph sat about 3.5px high. An <svg> is inline by default, so
      it sits on the text baseline and carries the descender gap below it
      — the box is centred but the ink inside it is not. display:block
      takes the svg off the baseline.

   3. The five marks came from five sources and fill their 24x24 boxes
      very differently: Instagram 88%, LinkedIn 74%. At one svg size that
      makes some marks read visibly heavier than others. They are
      normalised at runtime instead — see the builder. */
.fsocial{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:10px}
.fsocial li{margin:0;display:flex}
/* `.fsocial li a` and not `.fsocial a`: the general footer rule further
   down is `footer p,footer a{...display:block}` and it was winning the
   display and padding, which is what pinned every mark to the left edge
   of its ring. The extra element in the selector settles it without
   reaching for !important, and padding/margin are zeroed explicitly
   because that same rule sets both. */
.fsocial li a,.fsocial li span{
  flex:0 0 auto;
  display:inline-flex;align-items:center;justify-content:center;
  width:40px;height:40px;aspect-ratio:1;border-radius:50%;
  padding:0;margin:0;
  border:1px solid var(--line);color:var(--text-secondary);
  transition:color .3s var(--ease-out),border-color .3s var(--ease-out),background .3s var(--ease-out),transform .3s var(--ease-out);
}
.fsocial li a:hover{color:#fff;border-color:var(--accent);background:var(--accent);transform:translateY(-2px)}
/* an account whose link has not been supplied yet is shown, not linked */
.fsocial li span{opacity:.42}
.fsocial svg{display:block;width:20px;height:20px;fill:currentColor}

.fbase{max-width:1280px;margin:40px auto 0;padding-top:20px;border-top:1px solid var(--line);display:flex;gap:16px;justify-content:space-between;flex-wrap:wrap}
.fbase p{font-size:12.5px;margin:0;color:var(--text-faint)}
.fbase p b{color:var(--text-secondary);font-weight:500}

/* ---- the word underneath ----
   Sized off the viewport so it always spans the page, and cropped by
   the footer's own bottom edge so only the top of the letterforms
   shows. It fills the space rather than adding a thing to look at. */
.fword{
  margin:clamp(26px,4vh,54px) 0 0;
  font-family:var(--display);font-variation-settings:'wdth' 62,'wght' 800;font-weight:800;
  /* twenty characters instead of ten, so it is set at half the size to
     span the same width */
  font-size:min(8.1vw,124px);line-height:.74;letter-spacing:-.042em;text-transform:uppercase;
  /* The negative tracking that makes the wordmark sit tight also eats
     the space between the two words, and at this size the gap that
     survives is smaller than the gap inside "Connecting" — so it reads
     as one word. word-spacing pays the tracking back and adds a real
     gap on top, without loosening a single letter. */
  word-spacing:.32em;
  text-align:center;white-space:nowrap;
  color:var(--text-primary);opacity:.042;
  user-select:none;pointer-events:none;
  /* cropped, not squashed: the descender line sits below the footer */
  margin-bottom:-0.22em;
}
@media (max-width:520px){ .fword{font-size:10.4vw;margin-bottom:-0.2em} }

/* ============================================================
   REDUCED MOTION, honored completely
   ============================================================ */
@media (prefers-reduced-motion: reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{
    animation-duration:.001ms!important;animation-iteration-count:1!important;
    transition-duration:.001ms!important;transition-delay:0s!important;
  }
  body{opacity:1}
  .reveal{opacity:1;transform:none}
  .stag > *{opacity:1;transform:none}
  /* the scroll reveals are animations, so they need switching off at the
     source as well as having their duration crushed */
  .rv,.rv-img{opacity:1!important;animation:none!important;transform:none!important}
  .loop-done{opacity:1;transform:none}
  .node{color:var(--accent)}
  .svc{--lit:1}
  .band .sub,.band .cta-row{opacity:1;transform:none}
  /* positional transforms re-applied per breakpoint, never blanket-killed */
  .band--left{transform:translateY(-50%)}
  .band--center{transform:translateX(-50%)}
  @media (max-width:980px){.band--left{transform:translateY(-50%)}}
}

/* paused off-screen and on hidden tabs. hits pseudo-elements too,
   because animation-play-state is not inherited. */
body.paused *,body.paused *::before,body.paused *::after{animation-play-state:paused!important}
.offscreen,.offscreen *,.offscreen *::before,.offscreen *::after{animation-play-state:paused!important}

/* ============================================================
   THE PHONE PASS
   ------------------------------------------------------------
   Every rule below is inside a max-width query, so no desktop
   width can reach any of it. This block is last in the sheet on
   purpose: it has to win against the earlier mobile rules it is
   correcting, without raising specificity or reaching for
   !important to do it.

   Nothing here changes a size, a colour or a position that
   applies above 900px. The desktop layout is not touched.
   ============================================================ */

/* ---- 2. THE HERO'S "TALK TO US" LINE, PUT BACK ----
   It was switched off below 980px on the reasoning that a phone needs
   the screen for the headline and the buttons. On a tall phone that is
   not what happens: the hero is a full 100svh, the argument finishes
   around two thirds down, and because this band is absolutely
   positioned against the bottom it was being hidden from space that
   was simply sitting empty.

   It stays hidden on SHORT screens, where the original reasoning does
   hold and there genuinely is no room. */
@media (max-width:900px) and (min-height:600px){
  /* ---- THE HERO COLUMN SITS DOWN ----
     With the band joining the column, a centred hero split the screen
     evenly between the mark and the copy, and the mark lost. Measured:
     the room between the header and the chip fell from 194px to 158px
     at 390x844, and to 76px on a 375x667 screen, where the mark was
     being pushed back behind the header again.

     Anchoring the column to the bottom of its own padding box is what
     the hero was always described as doing on a tall screen — the scene
     owns the top, the argument runs along the bottom. It hands every
     spare pixel to the mark instead of splitting them, and the copy
     keeps the hero's own bottom padding beneath it.

     The top padding comes down at the same time. With the column
     bottom-anchored that padding is no longer doing any positioning; it
     is only there as the floor that stops a tall column being pushed up
     off the top of a short screen. */
  .thero{
    justify-content:flex-end;
    padding-top:clamp(88px,11vh,120px);
    /* and the column sits lower still. With the band anchored to the
       bottom of this padding, shrinking it moves the rule, the label and
       the whole write-up down together, and hands the space it gives up
       to the top of the screen where the scene lives.

       env(safe-area-inset-bottom) is the part that was missing. 100svh is
       the viewport, but on an iPhone the bottom ~34px of that viewport is
       under the home indicator. Measured at 375x667 the band ended 15px
       above the hero's bottom edge — inside the box on paper, and beneath
       the home bar on the actual phone, which is why the last line of the
       first screen could not be seen. The inset resolves to 0px on
       anything without one, so no other device moves. */
    padding-bottom:calc(clamp(14px,2.2vh,28px) + env(safe-area-inset-bottom,0px));
  }
  .heroclients{
    display:flex;
    flex-direction:column;align-items:flex-start;
    gap:6px;
    padding-top:12px;
    /* ---- IT JOINS THE HERO INSTEAD OF HANGING OFF THE BOTTOM OF IT ----
       It was absolutely positioned against the bottom of a 100svh hero.
       Measured at 390x844 that left a 128px hole between the end of the
       copy and the rule, and put the band itself at 777..825 of 844 —
       inside the box on paper, and under the browser's own toolbar on a
       real phone, which is why it kept disappearing.

       As an ordinary item in the hero's column it sits directly under
       the scroll marker, travels with the copy, and is centred with it
       by the hero's own justify-content. It cannot fall off the bottom
       of the screen because there is no longer a bottom to fall to.

       order:2 because it comes BEFORE the copy in the markup — where it
       has to stay, since the reading order of the page is the order of
       the source, and a phone number should not be announced before the
       headline it belongs to. */
    position:static;
    order:2;
    margin-inline:0;
    margin-top:clamp(16px,2.8vh,30px);
  }
  .hc-side{gap:3px 14px}
  .heroclients .hc-l{font-size:9px}
  .heroclients .hc-n{font-size:10.5px;letter-spacing:.08em;white-space:normal}
  /* the dot is a separator for a row. Stacked, it reads as a stray
     bullet, so it comes off. */
  .hc-side > a + a::before,.hc-side > a + span::before{content:none;margin-right:0}
  /* THE PHONE NUMBER COMES OUT OF THE HERO.
     The band is the line, the label and one way to reach us. The number
     was the third thing on a row that had room for two, so it wrapped
     and turned a two-line band into a three-line one. It stays in the
     footer, in the brief panel and in the structured data; it is only
     the hero that does not need it. With it gone the label and the
     address sit on a single row under the rule, which is the whole
     element the desktop has. */
  .heroclients a.hc-n[href^="tel:"]{display:none}
  /* "We reply within one working day" comes off the phone too. It is the
     desktop band's right-hand half, and stacked under the address on a
     narrow column it is a second line of small print in the one part of
     the hero that should be a rule, a label and a way to reach us. The
     promise is still made in the brief panel, where it is being asked
     for. What is left is the LINE and TALK TO US. */
  .heroclients .hc-side--r{display:none}

  /* ---- THE HERO'S EMAIL, ACTUALLY TAPPABLE ----
     Measured 200x15: a line of mono text with no padding at all, which
     is half the height a thumb needs and the only way to reach us from
     the first screen. The band itself is only 27px tall and the copy
     above it is anchored to it, so padding the link would move the whole
     hero. An overlay grows the hit area instead and moves nothing. */
  .heroclients a.hc-n{position:relative}
  .heroclients a.hc-n::after{content:"";position:absolute;left:0;right:0;top:-15px;bottom:-15px}
  .heroclients .hc-l{font-size:10px}
}

@media (max-width:900px){

  /* ---- 1b. THE HEADER ----
     "Start a project" was centred in the bar for a while, on the
     reasoning that three items with one auto margin left an uneven gap.
     Centred it reads as a third floating object rather than as part of
     the control cluster, so it goes back to the right, next to the
     burger, where the two things you can press sit together and the
     logo has the left of the bar to itself.

     That is the sheet's own earlier rule, so the correction is simply
     to stop overriding it: nothing is declared here. */

  /* ---- 3. 02 THE WORK: the same reveal, told in order ----
     The animation was already firing on a phone — measured, the opacity
     of all three runs 0 to 1. The problem is that it fires on all three
     AT THE SAME INSTANT: the kicker, the headline and the lede sit
     within 140px of each other, the sweep catches them in one frame, and
     a block of text fading up as one slab does not read as an animation
     at all. It reads as the page arriving late.

     Nothing here changes the animation. It is the same .reveal, the same
     26px rise, the same 850ms curve. Each line is simply given its turn,
     so "02 The work" lands, then the headline, then the sentence under
     it, during an ordinary vertical scroll. The whole cascade is over in
     a third of a second. Delays only, and only below 900px. */
  #work > .wrap > .kicker.reveal{transition-delay:0ms}
  #work > .wrap > h2.reveal{transition-delay:105ms}
  #work > .wrap > .lede.reveal{transition-delay:200ms}
  /* the subhead and the first case cross the trigger together for the
     same reason; the rest of the cases are screens apart and fire on
     their own, where this delay is imperceptible */
  #work .cases > .case.reveal{transition-delay:130ms}

  /* ---- and the case itself arrives in parts ----
     A case used to fade up as one rectangle. Its four fields now come in
     turn — Challenge, Approach, What we did, Result — which is the
     "progressively" the rest of this section already does between its
     headings, applied inside the card.

     Tied to the parent's own .in, so it inherits the sweep that cannot
     miss: if the card is revealed, the fields are revealed. */
  #work .case.reveal .case-f{
    opacity:0;transform:translateY(16px);
    transition:opacity .62s var(--ease-out),transform .62s var(--ease-out);
  }
  #work .case.reveal.in .case-f{opacity:1;transform:none}
  #work .case.reveal.in .case-f:nth-child(1){transition-delay:190ms}
  #work .case.reveal.in .case-f:nth-child(2){transition-delay:300ms}
  #work .case.reveal.in .case-f:nth-child(3){transition-delay:410ms}
  #work .case.reveal.in .case-f:nth-child(4){transition-delay:520ms}
  /* anyone who has asked for less motion reads the card, not the
     choreography. This has to be stated here because the blanket rule
     further up targets .reveal itself, and these are its children. */
  @media (prefers-reduced-motion:reduce){
    #work .case.reveal .case-f{opacity:1;transform:none;transition:none}
  }
}

/* ---- THE DECK, ON A PHONE ----
   The one thing the desktop does in this section that the phone did not:
   the four cases are dealt rather than scrolled past. Each pins under the
   header and the next rides up and covers it, which is a continuous
   scroll-linked movement rather than four separate fades — that is the
   "while scrolling" feel that was missing here.

   The same three rules the desktop deck lives by apply, and the third is
   why this is gated: the whole card has to fit between the header and the
   bottom of the window, or a pinned card has a last line that cannot be
   scrolled to.

   THE GATE WAS 840px AND THAT IS WHY NOBODY EVER SAW THIS ON A PHONE.
   840 was read off the device's spec height, but a media query is matched
   against the viewport Safari actually hands the page, and that is the
   device minus the status bar and the address bar. An iPhone 14 is 844pt
   tall and reports roughly 745. A 393x852 phone reports about 753. So the
   query was true in a desktop emulator, where there are no toolbars, and
   false on every real handset — the deck existed and never once ran.

   Re-measured at 390 wide after the copy changes (the old table in this
   comment said 683; the tallest card is now 641):

     card content, tallest of four .............. 641px
     old budget  100svh - 124  at 745 viewport ... 621px   short by 20
     new budget  100svh - 104  at 745 viewport ... 641px   exactly level
     with the padding and gap trimmed below ..... 627px card, 14px spare

   The card height is still taken FROM the viewport rather than fixed, so
   a card can never be taller than the window it has to be read in; the
   trim is what buys the margin to drop the gate to a number real Safari
   reaches. Below 740 the plain list stays, which is the right answer
   there rather than a degraded deck.

   The min-width guard is unchanged: below 370px the copy wraps far enough
   to outgrow even a tall screen. */
@media (max-width:900px) and (min-width:370px) and (min-height:740px){
  .cases{
    --deck-top:76px; --deck-step:6px;
    --deck-h:calc(100svh - 104px);
    --deck-pad:var(--gutter);
    padding-bottom:clamp(40px,7vh,90px);   /* room to deal the last one in */
  }
  .case:nth-child(1){--i:0} .case:nth-child(2){--i:1}
  .case:nth-child(3){--i:2} .case:nth-child(4){--i:3}
  .case{
    position:sticky;top:calc(var(--deck-top) + var(--i,0) * var(--deck-step));
    height:var(--deck-h);align-content:start;
    background:var(--canvas);
    padding-block:18px;
    padding-inline:var(--deck-pad);margin-inline:calc(-1 * var(--deck-pad));
    box-shadow:0 -14px 26px -18px rgba(12,9,19,.30);
  }
  /* the fields sit closer together than they do in a column free to run
     as long as it likes, and they stay at the top of the card. 12px
     rather than 14 is part of the height budget above, not a style
     change: three gaps, six pixels, and it is what lets the deck run on
     a phone that is actually holding a toolbar. */
  .case-body{gap:12px;align-content:start}

  /* A pinned card is a position, not an animation, but it is still
     motion tied to the scroll. Anyone who has asked for less of that
     reads four cards in a row. Restated here because the blanket rule
     further up the sheet cannot reach past this block. */
  @media (prefers-reduced-motion:reduce){
    .case{position:static;height:auto;box-shadow:none;padding-block:34px}
    .cases{padding-bottom:0}
  }
}

/* ---- AND BACK OFF AGAIN ON A NARROW PHONE THAT IS ALSO SHORT ----
   The height gate above assumes the card heights measured at 390 wide.
   They are not the heights at 375 wide: the same copy wraps further and
   the tallest card grows from 627 to 652. Measured at 375x740 the deck
   turned on and cropped the fourth card by 16px — the one failure this
   whole section is gated to avoid, and it only appears in the corner
   where a narrow phone is also short.

   375 is not a rare width. iPhone SE, 13 mini and the X/XS/11 Pro family
   are all 375, so excluding 375 outright would take the deck off a lot of
   real phones that CAN hold it once they are tall enough. This asks for
   the extra height instead: at 775 a 375-wide card gets 671px for 652px
   of copy. Anything narrower than 390 and shorter than that keeps the
   plain list, which is the honest answer rather than a cropped deck. */
@media (max-width:389px) and (max-height:774px){
  .case{
    position:static;height:auto;
    box-shadow:none;
    padding-block:clamp(36px,5.2vh,60px);
    padding-inline:0;margin-inline:0;
  }
  .cases{padding-bottom:0}
}

/* back to every phone, whatever its height */
@media (max-width:900px){

  /* ---- 4 + 5. SELECTED WORK: make the sideways scroll obvious ----
     The rail was already swipeable, but the next card only showed about
     51px of itself, which is close enough to "a card that got cut off"
     to be read as a mistake rather than an invitation. Narrowing the
     card hands that space to the one behind it: roughly 70px of the
     next card now sits at the edge, which is enough to be read as
     another card and not as damage.

     The cards keep their existing design, the row stays horizontal, and
     the reveal animation is the same .rv the rest of the page uses. */
  .rail-card{width:min(70vw,290px)}
  .rail-track{scroll-padding-inline:var(--gutter)}

  /* ---- 6. THE ARCHIVE'S CATEGORIES ----
     The pill row scrolls to the screen edge instead of stopping inside
     the text column. Same first pill position, but the row now visibly
     runs past the edge of the screen, which is the whole cue: the next
     category is cut by the PHONE rather than by a container, and that
     reads as "there is more along here".

     The negative margin is cancelled by equal padding, so the first
     pill stays exactly where it was and nothing moves. */
  .tabs{
    margin-inline:calc(var(--gutter) * -1);
    padding-inline:var(--gutter);
    scroll-padding-inline:var(--gutter);
    overscroll-behavior-x:contain;      /* never hands the swipe to the page */
  }
  /* Smaller type, and the horizontal padding cut harder than the
     vertical. Measured at 390px the row showed one whole pill and most
     of a second, and nothing of the four behind them — which reads as
     two categories with a cropped label, not as a row that runs on.
     Bringing the label down gets a second pill fully on screen and a
     real slice of a third, so the row is visibly a list.

     The vertical padding is deliberately left alone: it is what makes
     these a thumb-sized target, and that is not worth trading for a
     third of a pill. */
  .tab{font-size:10.5px;padding:11px 9px;letter-spacing:.02em}

  /* ---- 8. "IN THEIR WORDS" ----
     The section deliberately has no inline padding so the quote row can
     run the full width of the screen. The heading was inheriting that
     and sitting flush against the left edge with no gutter at all,
     which is what made it look like it was falling off the screen.
     The gutter goes back on the text, and only on the text. */
  .quotesec .wrap{padding-inline:var(--gutter)}

  /* ---- 7. THE MOBILE MENU ----
     Email and WhatsApp stay and stay clickable; the phone number comes
     out of the opened menu only. It is untouched in the hero band, in
     the footer and in the brief panel. */
  .menu-contact a[href^="tel:"]{display:none}
}

/* ---- 6. THE FOOTER, CENTRED ----
   Gated at 520px because that is exactly where the footer grid collapses
   to a single column and turns on text-align:center. Above it the grid is
   still two columns reading left, and centring half of it there would
   make a mess of a layout nobody asked about.

   Why it looked "slightly misaligned" rather than plainly wrong: the
   column ITSELF was centred and the TEXT inside it was centred, but three
   kinds of box in it were not. A block with a max-width, a block with
   width:fit-content and a flex row all keep their own left edge no matter
   what text-align says, because none of them is text. So the description,
   every link, the opening hours and the row of social rings each sat left
   of the centre line the headings were on — a few pixels here, sixty
   there, which reads as a footer that is nearly but not quite square.

   Every rule below is one of those boxes being given the auto side
   margins it needed. No type, no colour, no icon, no content, no order,
   and nothing above 520px. */
@media (max-width:520px){
  footer .fbrand img{
    display:block;margin-inline:auto;
    width:clamp(112px,34vw,150px);height:auto;
  }
  /* the 32ch measure is worth keeping — a centred line 40 characters wide
     is harder to read, not easier — so it is centred rather than widened */
  footer .fbrand p{max-width:32ch;margin-inline:auto}
  footer .fgrid > div{min-width:0}
  /* width:fit-content is what makes the tap target as wide as its own
     text rather than the whole column; these are the boxes it leaves
     hugging the left edge */
  footer .fgrid a{margin-inline:auto}
  footer .fgrid p{margin-inline:auto}
  .fhours{margin-inline:auto}
  .fsocial{justify-content:center}
  .fcta{margin-inline:auto}
  /* two lines that were pushed to opposite ends of the row by
     space-between, which on a phone puts one hard left and one hard right */
  .fbase{flex-direction:column;align-items:center;text-align:center;gap:10px}

  /* ---- THE FOOTER'S LINKS, AS TAP TARGETS ----
     Measured 35px tall. The 6px of padding was set to stop the tap box
     and the paragraph margin stacking into a line and a half of air
     between the email and the phone number — right then, too tight now.
     11px puts every one of them at 45px, over the 44px a thumb needs,
     and the margin is still zero so nothing doubles up. */
  footer .fgrid a{padding-block:11px}
}

/* ---- 12. the narrowest phones ----
   360px is the floor worth designing to. Nothing here restyles
   anything; it only stops the two widest strings on the page from
   forcing the layout wider than the screen. */
@media (max-width:380px){
  /* 74vw left only 45px of the next card showing at 360, which is back
     in "looks broken" territory. 70vw holds the peek at roughly 60px
     all the way down to the narrowest phone worth designing for. */
  .rail-card{width:min(70vw,250px)}
  .tab{font-size:10px;padding:10px 8px}
  .heroclients .hc-n{font-size:10px}
}

/* ============================================================
   THE SITE BEYOND THIS PAGE
   Added with the move from one page to six. Two things only, and
   the new pages share them through assets/site.css, which is this
   stylesheet copied out verbatim by the build: which page you are
   on, and the curtain between pages.
   Nothing above this block was changed to make room for it.
   ============================================================ */

/* ---- which page you are on: the hover underline, held ---- */
.navlinks a[aria-current="page"]{color:var(--text-primary)}
.navlinks a[aria-current="page"]::after{transform:scaleX(1)}
.menu-nav a[aria-current="page"]{color:var(--accent)}

/* ---- between pages: the opening's curtain, used again ----
   It rises from below to cover the page being left, and on the page
   being arrived at it carries on upward, off the top — one continuous
   movement across the change of page, on the same curve and in the
   same pink as the opening. Transform only, so it runs on the
   compositor. The word is the name of the place you are going. */
.pgc{position:fixed;inset:0;z-index:400;background:var(--flood);color:#fff;
  display:grid;place-items:center;pointer-events:none;visibility:hidden;
  transform:translateY(100%);will-change:transform}
.pgc-w{margin:0;padding-inline:var(--gutter);text-align:center;
  font-family:var(--display);font-variation-settings:'wdth' 62,'wght' 800;font-weight:800;
  font-size:clamp(38px,8.8vw,142px);line-height:.9;letter-spacing:-.03em;text-transform:uppercase;white-space:nowrap;
  transition:transform .8s cubic-bezier(.76,0,.24,1)}
.pgc.cover{visibility:visible;pointer-events:auto;transform:translateY(0);
  transition:transform .55s cubic-bezier(.76,0,.24,1)}
.pt-in .pgc{visibility:visible;transform:translateY(0)}
.pt-in .pgc.lift{transform:translateY(-100%);transition:transform .8s cubic-bezier(.76,0,.24,1)}
.pt-in .pgc.lift .pgc-w{transform:translateY(-18%)}
/* a failsafe: if the script never runs, the curtain never strands anyone */
.pt-done .pgc{display:none}
@media (prefers-reduced-motion:reduce){ .pgc{display:none} }
