/* Fonts served from our own domain. They used to come from Google, which
   cost two extra connections and blocked the first paint. */
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:300;font-display:swap;src:url('../assets/fonts/space-grotesk-latin-300-normal.a5de30e3.woff2') format('woff2')}
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:400;font-display:swap;src:url('../assets/fonts/space-grotesk-latin-400-normal.65fd17fc.woff2') format('woff2')}
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:500;font-display:swap;src:url('../assets/fonts/space-grotesk-latin-500-normal.1b1a8131.woff2') format('woff2')}
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:700;font-display:swap;src:url('../assets/fonts/space-grotesk-latin-700-normal.35f8aec5.woff2') format('woff2')}
@font-face{font-family:'EB Garamond';font-style:normal;font-weight:400;font-display:swap;src:url('../assets/fonts/eb-garamond-latin-400-normal.912929db.woff2') format('woff2')}
@font-face{font-family:'EB Garamond';font-style:normal;font-weight:500;font-display:swap;src:url('../assets/fonts/eb-garamond-latin-500-normal.7b252686.woff2') format('woff2')}
@font-face{font-family:'EB Garamond';font-style:italic;font-weight:400;font-display:swap;src:url('../assets/fonts/eb-garamond-latin-400-italic.fc5851c4.woff2') format('woff2')}
@font-face{font-family:'Sora';font-style:normal;font-weight:200;font-display:swap;src:url('../assets/fonts/sora-latin-200-normal.fea99235.woff2') format('woff2')}
@font-face{font-family:'Sora';font-style:normal;font-weight:300;font-display:swap;src:url('../assets/fonts/sora-latin-300-normal.3d1bb919.woff2') format('woff2')}
@font-face{font-family:'Sora';font-style:normal;font-weight:400;font-display:swap;src:url('../assets/fonts/sora-latin-400-normal.98141d7e.woff2') format('woff2')}
@font-face{font-family:'Sora';font-style:normal;font-weight:600;font-display:swap;src:url('../assets/fonts/sora-latin-600-normal.fa9ab76f.woff2') format('woff2')}

/* ============================================================
   sillyhumans — shared styles
   Values extracted from the live Wix build (SingleFile capture)
   ============================================================ */

/* ---- Tokens (real values from the Wix site) ---- */
:root {
  --sh-white:       #FFFFFF;
  --sh-black:       #000000;
  --sh-ink:         #18191A;   /* Wix color_15 / color_37 */
  --sh-bone:        #EEEDED;   /* Wix color_11 / color_36 */
  --sh-grey:        #8A8785;   /* Wix color_13 */
  --sh-grey-mid:    #C0BFBE;   /* Wix color_12 */

  --sh-gutter:      5%;
  --sh-header-h:    clamp(78px, 9.1vw, 170px);  /* fluid, like Wix */

  --font-display:   'Space Grotesk', sans-serif;
  --font-serif:     'EB Garamond', serif;
  --font-alt:       'Sora', sans-serif;
}

/* ---- Reset ---- */
*, *::before, *::after { box-sizing: border-box; }
/* Overscroll (the rubber-band band past the top and bottom of the page) paints
   the canvas background and nothing else — no document content reaches it, so
   a box-shadow or an extra element cannot colour it. The canvas takes its
   colour from <html>, falling back to <body>. Both are black here, and the
   light pages keep their white on <main>, so the band is black on every page.
   One colour is all the canvas gets; green at the foot is not available. */
html {
  -webkit-text-size-adjust: 100%;
  background: #000;
}
body {
  margin: 0;
  font-family: var(--font-display);
  background: var(--sh-black);
  color: var(--sh-white);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
a { text-decoration: none; color: inherit; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

/* ============================================================
   HEADER
   Transparent, sticky, overlays the hero.
   ============================================================ */
.sh-header {
  position: relative;
  z-index: 100;
  height: var(--sh-header-h);
  background: transparent;          /* Wix: --alpha-bg: 0 */
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--sh-gutter);
  color: var(--sh-white);
}

/* ---- Logo (real vector lockup from the site) ---- */
.sh-logo {
  display: block;
  flex: 0 0 auto;
  color: var(--sh-white);
  transition: color var(--t-fast) ease;
}
/* The wordmark is currentColor throughout, so this turns the whole lockup —
   sillyhumans and the Wolf Gang mark — green. :active covers the click and,
   on a phone, the tap. */
.sh-logo:hover,
.sh-logo:active { color: var(--sh-white); }
/* The new mark carries more padding inside its viewBox than the old one:
   measured in pixels its letters are 14% smaller at the same CSS height,
   so matching the old height would have made the logo look like it had
   shrunk. The height is scaled 1.162x to put the LETTERS back where they
   were. The box still comes out slightly narrower, because the new lockup
   is tighter than the old one. */
.sh-logo svg {
  display: block;
  width: auto;
  height: clamp(30px, 2.81vw, 56px);
}

/* ---- Nav pill ----
   Outer: 1px white border on black, fully round.
   Items: equal width (flex:1), full height, 16px Space Grotesk.
   Hover / current: white fill, black text.                        */
.sh-nav {
  display: flex;
  align-items: stretch;
  height: 28px;
  width: clamp(340px, 34vw, 620px);    /* measured 585px @ 1720vw */
  background: var(--sh-black);
  border: 1px solid var(--sh-white);
  border-radius: 100px;
}

.sh-nav__link {
  flex: 1 1 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 16px;
  border-radius: 100px;
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 400;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--sh-white);
  white-space: nowrap;
  transition: background-color 0.25s ease, color 0.25s ease;
}

.sh-nav__link:hover,
/* [aria-current] not [aria-current="page"]: the 18 inner pages mark the
   Projects link aria-current="true" (current section), which is accurate. */
.sh-nav__link[aria-current] {
  background: var(--sh-white);
  color: var(--sh-black);
}

.sh-nav__link:focus-visible {
  outline: 2px solid var(--sh-white);
  outline-offset: 3px;
}

/* ---- Predictor ----
   A fourth identical link would vanish into the pill, and this is the one
   we want found. It carries the studio green, the same signal the
   predictor's own button uses, so the two read as one thing. */
.sh-nav__link--new {
  background: var(--sh-green);
  color: var(--sh-black);
}
.sh-nav__link--new:hover,
.sh-nav__link--new[aria-current] {
  background: var(--sh-green-lit);
  color: var(--sh-black);
}
.sh-mobile-menu nav a.is-new {
  color: var(--sh-green);
}

/* ---- Mobile toggle ---- */
.sh-burger {
  display: none;
  width: 32px;
  height: 32px;
  position: relative;
}
.sh-burger span {
  position: absolute;
  left: 4px;
  right: 4px;
  height: 1.5px;
  background: currentColor;
  transition: transform 0.28s ease, opacity 0.2s ease;
}
.sh-burger span:nth-child(1) { top: 11px; }
.sh-burger span:nth-child(2) { top: 19px; }
body.sh-menu-open .sh-burger span:nth-child(1) { transform: translateY(4px) rotate(45deg); }
body.sh-menu-open .sh-burger span:nth-child(2) { transform: translateY(-4px) rotate(-45deg); }

/* ---- Mobile menu ---- */
.sh-mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 99;
  background: var(--sh-black);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: calc(var(--sh-header-h) + 32px) var(--sh-gutter) 48px;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s ease, visibility 0.3s ease;
}
body.sh-menu-open .sh-mobile-menu { opacity: 1; visibility: visible; }

/* The three sat on line-height alone -- 1.35 at 40px, so 14px of air
   between 40px words. A menu that fills the screen can afford to breathe. */
.sh-mobile-menu nav { display: flex; flex-direction: column; gap: 20px; }
.sh-mobile-menu nav a {
  display: block;
  font-family: var(--font-display);
  font-size: 40px;
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--sh-white);
  transition: color var(--t-fast) ease;
}
/* Nothing happened on tap, so there was no sign the word had registered.
   :active matters more than :hover here -- a finger has no hover. */
.sh-mobile-menu nav a:hover,
.sh-mobile-menu nav a:active,
.sh-mobile-menu nav a:focus-visible,
.sh-mobile-menu__social a:hover,
.sh-mobile-menu__social a:active,
.sh-mobile-menu__social a:focus-visible { color: var(--sh-green); }
.sh-mobile-menu__social a { transition: color var(--t-fast) ease; }

/* Four brand marks on one line, replacing the "IG" / "X" text. The mark is
   18px; the tap target around it is 44px, which is why the gap looks larger
   than it is -- the boxes are already touching. */
.sh-mobile-menu__social { display: flex; align-items: center; gap: 4px; }
.sh-mobile-menu__social a {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; margin-left: -10px;
  color: var(--sh-white);
}
.sh-mobile-menu__social a:first-child { margin-left: -10px; }
.sh-mobile-menu__social svg { width: 18px; height: 18px; display: block; }

/* ---- Responsive ---- */
@media (max-width: 900px) {
  :root { --sh-header-h: 78px; --sh-gutter: 24px; }
  .sh-nav    { display: none; }
  .sh-burger { display: block; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}

/* ============================================================
   PAGE SHELL
   Every size below is taken from the live Wix build.
   Wix scales with --scaling-factor, which resolves to 100vw,
   so its coefficients convert straight to vw.
   ============================================================ */
:root {
  --sh-green: #9EFF00;          /* live: rgb(158,255,0) */
  /* The lit state of anything already green: a hover on a green pill,
     or the press under a finger. It was written out as #B4FF3A in nine
     places and, in one, as #7FCC00 -- a DARKER green, so the lightbox
     close dimmed under the thumb while every other green control
     brightened. One token, one direction. */
  --sh-green-lit: #B4FF3A;
  /* Two durations, everywhere. Colour changes are fast so they feel like a
     response; movement is slower so it reads as motion. Replaces the five
     ad-hoc values the stylesheet had grown. */
  --t-fast: 0.18s;
  --t-slow: 0.4s;
  --sh-edge: 3.125vw;           /* live: 0.03125 * scaling-factor */
  --sh-radius: 16px;
  /* Flow band gap. DOM-measured 18px at a 1173 viewport = 1.53vw (20px @1280),
     which matches the editor's original reading. An earlier 185px value came
     from measuring a screenshot and was wrong — see HANDOFF. */
  --band-gap: 1.53vw;
  /* Black run below the last band, before the white What We Do section.
     DOM-measured 38px at a 1173 viewport = 3.24vw (41px @1280). An earlier
     213px value came from a screenshot where the parallaxed What We Do stack
     was displaced upward — see HANDOFF. */
  --flow-tail: 3.24vw;

  /* ------------------------------------------------------------
     Wix type scale, resolved. --theme-spx-ratio = 100vw/1280,
     so calc(N * spx) == N/12.8 vw. Line-heights are Wix's own.
       font_0  5.391vw / 1em    font_6   1.328vw / 1em
       font_1  16px    / 1.4em  font_7   18px    / 1.1em
       font_2  4.844vw / 1em    font_8   16px    / 1.3em
       font_3  3.594vw / 1em    font_9   14px    / 1.3em
       font_4  3.438vw / 1em    font_10  12px    / 1.4em
       font_5  2.188vw / 1.1em
     ------------------------------------------------------------ */
  --lh-display: 1;      /* font_0-4, font_6 */
  --lh-lead:    1.3;    /* editor: Paragraph 3 = 19px / 1.3em */
  --lh-body:    1.3;    /* font_8, font_9 */

  /* Type scale — every value below read off the editor Text panel at 1280px.
     THE RULE: the panel writes "px*" when a size scales with the viewport and
     plain "px" when it's frozen. Display type scales; everything else is fixed.
     Don't convert a plain-px value to vw, and don't freeze a px* one. */
  --fs-display: 6.719vw;        /* "the flow"          86 px* */
  --fs-h1:      4.844vw;        /* both statements     62 px* */
  --fs-h2:      2.734vw;        /* section heads       35 px* */
  --fs-band:    7.734vw;        /* band words          99 px* (Scale text ON) */
  --fs-lead:    19px;           /* taglines / eyebrow  19 px  */
  --fs-body:    16px;           /* notes, footer links 16 px  */
  --fs-btn:     18px;           /* View Projects       18 px  */
}

.sh-main { position: relative; }
section { position: relative; }

.sh-tagline { font-size: var(--fs-lead); font-weight: 400; line-height: var(--lh-lead); margin: 0; }

/* ============================================================
   HERO
   Every number below is read off the Wix editor at the 1280px
   breakpoint. Elements are absolutely placed, as they are in Wix.
     Section Grid   H 756          -> 59.06vw
     Image (GIF)    X187 Y20  W884 -> 14.61% / 2.65% / 69.06%
     Tagline        X464 Y474 W310 -> 36.25% / 62.70% / 24.22%
   Both sit ~21px left of centre on the live site. Kept.
   ============================================================ */
.hero {
  position: relative;
  height: 59.06vw;
  background: #000;
  overflow: hidden;
}
.hero__gif {
  position: absolute;
  left: 14.61%;
  top: 2.65%;
  width: 69.06%;
  height: auto;
  display: block;
}
.hero__tagline {
  position: absolute;
  left: 36.25%;
  top: 62.70%;
  width: 24.22%;
  margin: 0;
  text-align: center;
}

/* full-bleed film strips */
.filmstrip {
  width: 100%;
  height: 39.06vw;
  background: #000;
  overflow: hidden;
  position: relative;
}
.filmstrip--hero { height: 39.38vw; }   /* editor: 504px @ 1280 */

/* ------------------------------------------------------------
   PARALLAX — confirmed in the Wix editor:
   Section (Gallery) → Scroll → "Parallax on Self".
   Media is oversized inside a fixed window and drifts against
   the scroll. Overhang (25%) exceeds travel (20%) so no edges show.
   ------------------------------------------------------------ */
/* PINNED model — the media is a viewport-height layer that resists the
   scroll, so the section becomes a moving window onto a near-still image.
   Confirmed against a full scroll-through of the live contact hero.
   min-height:100% covers the case of a section taller than the viewport. */
.px {
  position: absolute; left: 0; right: 0; top: 50%;
  height: 100vh; min-height: 100%;
  margin-top: -50vh;                  /* was a -50% inside the transform */
  transform: translate3d(0, 0, 0);
  will-change: transform;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}
/* A pinned layer IS the viewport-height sheet, so the media inside it must
   fill it exactly. .filmstrip video (top:-12.5%; height:125%) ties with
   .px video on specificity and wins on source order, which left the hero
   video 1125px tall inside a 900px layer. The child combinator breaks the
   tie at every width — this used to be patched for phones only. */
.filmstrip--hero .px > video { top: 0; height: 100%; }
.px img, .px video { width: 100%; height: 100%; object-fit: cover; display: block; }



/* ============================================================
   STATEMENTS
   ============================================================ */
.statement {
  min-height: 55.86vw;                     /* editor: 715px @ 1280 */
  display: flex; flex-direction: column; justify-content: center;
  padding: 4vw var(--sh-edge);
  text-align: center;
}
.statement h1,
.statement .statement__h {
  font-size: var(--fs-h1);                 /* live: font_2 = 4.844vw */
  font-weight: 400; line-height: var(--lh-display); letter-spacing: -0.02em;
  margin: 0 auto; max-width: 1162px;       /* live: 1161.95px */
}
.statement__eyebrow { font-size: var(--fs-lead); font-weight: 400; line-height: var(--lh-lead); margin: 0 0 3.4vw; }
.statement__foot {
  margin-top: 3.4vw;
  display: flex; align-items: center; justify-content: center;
  gap: 24px;                               /* editor: item spacing 24px */
  flex-wrap: wrap;
}
.statement__note {
  font-size: var(--fs-body); font-weight: 400; line-height: var(--lh-body); margin: 0;
  text-transform: capitalize;              /* live renders title case */
}

/* light variant — live: What We Do sits on white, 56.25vw tall */
.statement--light {
  background: var(--sh-white); color: var(--sh-ink);
  overflow: hidden;                 /* with data-px-fit nothing reaches this, but belt and braces */
  min-height: 56.25vw;               /* editor: Section H 720 @ 1280 */
  display: flex; flex-direction: column; justify-content: center;
  padding: 0;
}
/* editor: inner Stack X94 Y114 W1091, vertical, item spacing 37px */
.statement--light .statement__inner {
  width: 85.23%;
  margin: 0 auto;
  display: flex; flex-direction: column; align-items: center;
  gap: 37px;
}
.statement--light .statement__eyebrow,
.statement--light .statement__foot { margin: 0; }
.statement--light .statement__note { color: var(--sh-black); }

.sh-cta {
  display: inline-flex; align-items: center; gap: 3.4vw;
  padding-bottom: 6px; border-bottom: 1px solid currentColor;
  /* live: 15px / 700 / lh 1.4 / Arial stack (NOT Space Grotesk) / no tracking */
  font-family: Arial, Helvetica, sans-serif;
  font-size: 15px; font-weight: 700; line-height: 1.4;
  letter-spacing: 0;
  transition: color var(--t-fast) ease;
}
.sh-cta svg { flex: 0 0 auto; transition: transform 0.3s ease; }
.sh-cta:hover svg { transform: translateX(6px); }
/* Only the arrow moved on hover; nothing changed colour, so the link gave
   no sign it was a link. Solid --sh-green, and because the rule and the
   arrow are both drawn in currentColor, the word, the arrow and the
   underline all turn together. */
.sh-cta:hover,
.sh-cta:focus-visible { color: var(--sh-green); }

/* editor: an empty Section, W1280 H500, black, sits between the
   statement and "the flow". 500/1280 = 39.06vw */
.black-gap { height: 39.06vw; background: #000; }

/* ============================================================
   THE FLOW — live: head 7.81vw, bands block 81.45vw
   ============================================================ */
/* .flow is the sticky container: it must END at the bottom of band 3, so the
   tail lives outside it as .flow-tail. */
.flow { padding: 3.07vw 0 0; position: relative; }   /* live: 36px top @1173 */
.flow-tail { height: var(--flow-tail); background: #000; }
/* live: the head is a fixed 955px-tall box at 1173 (81.42vw) with the heading
   sitting 122px (10.40vw) down from its top — most of it is deliberate space. */
/* STICKY, not parallax. "the flow" pins in place as you arrive, the bands
   scroll up and cover it, and it releases at the bottom of band 3 — by then
   it is behind band 3, so the release is never seen.
   A transform here would break sticky, so this element never gets one. */
.flow__head {
  position: sticky; top: 0; z-index: 0;
  text-align: center;
  height: 81.42vw;
  padding-top: 10.40vw;
  margin-bottom: 0;
  box-sizing: border-box;
}
.flow__head h2 {
  font-size: var(--fs-display);            /* live: 6.719vw */
  font-weight: 400; letter-spacing: -0.04em; line-height: 1; margin: 0;
}
.flow__head p { margin: 1.4vw 0 0; }

/* Band geometry — width SCALES, height is FIXED. Measured off the live site
   at a 1728px viewport (2x capture): the block is 903 device px = 451.5 CSS,
   i.e. still 451 at 35% wider than the 1280 breakpoint. An aspect-ratio would
   have made it 609. So: no aspect-ratio, a hard height. */
.band {
  position: relative;
  width: 93.98%;                     /* editor: W 1203 @ 1280 — scales */
  height: 451px;                     /* editor: H 451 — does NOT scale */
  margin: 0 auto var(--band-gap);
  border-radius: 20px;               /* live: 20px, not the 16px site default */
  overflow: hidden; isolation: isolate;
}
/* The head is sticky at the top of the viewport behind the bands. The bands
   covered it, but the gap between them did not, so "the flow" and its
   tagline flashed through the seam on the way past. The wrapper is opaque
   and sits above the head, so the gaps are black and the head is only ever
   seen in its own space. */
.flow__bands { position: relative; z-index: 1; background: #000; }
.band:last-child { margin-bottom: 0; }
/* ------------------------------------------------------------
   Sliding media. A layer that parallaxes must be TALLER than its
   frame, or it exposes an edge; and it must be a child of the
   frame, never the section itself — translating a section makes
   it overlap its neighbours (this is what clipped "Our Projects").
   125% height with -12.5% top gives +/-12.5% of travel each way,
   comfortably more than the +/-52px the live page uses.
   ------------------------------------------------------------ */
.filmstrip img, .filmstrip video, .filmstrip__video,
.band__media {
  position: absolute; left: 0; right: 0;
  top: -12.5%; height: 125%;
  width: 100%; object-fit: cover; display: block;
  will-change: transform;
}
.band__media { background-size: cover; background-position: center; }
.band__inner { will-change: transform;
  position: absolute; inset: 0;
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 24px; padding: 2.6vw 3vw;
}
/* live: 7.734vw / 400 / -0.04em / lh 1 / lowercase / left / ml 4.297vw */
.band__word {
  /* Was var(--fs-band), 7.734vw -- 111px at 1440, eating 43% of the band and
     landing across the subject rather than beside them. 5.8vw puts it at 45%
     on a phone and leaves the picture room to be the picture. */
  font-size: 5.8vw; font-weight: 400; line-height: 1;
  letter-spacing: -0.04em; text-transform: lowercase; text-align: left;
  margin: 0 0 0 4.297vw; color: var(--sh-white);
}
/* The word and the note sit on whatever the photograph happens to be doing
   underneath. On the 'imagination' band that is pale sky, which put white
   99px type at 1.5:1 — below the 3:1 AA floor for large text. A bottom-weighted
   scrim fixes the floor without flattening the image. */
@media (min-width: 751px) {
  .band__inner::before {
    content: "";
    position: absolute;
    /* -28px, not 0. This scrim rides on .band__inner, which parallax
       translates by up to 17px, so a bottom edge flush with the layer sat
       17px above the band for most of the scroll and let a bright strip of
       raw image show along the bottom. The overhang outruns the travel and
       .band clips it, so the gradient now reaches the band's floor at every
       scroll position. */
    inset: 30% 0 -28px;
    /* full density by 90%, since the last 10% is now below the visible edge */
    background: linear-gradient(to bottom,
                rgba(0,0,0,0) 0%, rgba(0,0,0,.14) 38%,
                rgba(0,0,0,.34) 66%, rgba(0,0,0,.52) 90%,
                rgba(0,0,0,.52) 100%);
    pointer-events: none;
  }
}
.band__word, .band__note { position: relative; }

.band__note { display: flex; align-items: center; gap: 1.3vw; }
/* Sized by HEIGHT, not width. All three glyphs are drawn 160 units tall in
   their own viewBox but at different widths -- 91 for the arrow, 160 for the
   star, 121 for the bolt -- so a fixed width rendered the arrow 1.75x taller
   than the star and it read as a different, larger icon. Equal height is what
   the artwork was drawn for. The number is unchanged, so the star keeps the
   size it already had and the other two come back to meet it. */
.band__note svg { height: 2.4vw; width: auto; fill: var(--sh-white); flex: 0 0 auto; }
.band__note p { margin: 0; color: var(--sh-white); }
/* editor: n1 is the ONLY Sora on the homepage — Paragraph 3*, 16px fixed, lh 1.3.
   n2 is Heading 2*, 30px FIXED (panel reads "px", not "px*"), lh 1. */
.band__note .n1 {
  font-family: var(--font-alt);          /* editor: Font = Sora */
  font-size: 16px;                       /* editor: 16 px */
  font-weight: 400; line-height: var(--lh-body);
}
.band__note .n2 {
  font-size: 30px;                       /* editor: 30 px */
  font-weight: 400; line-height: var(--lh-display);
  letter-spacing: -0.05em;               /* live: -1.5px / 30px */
  text-transform: capitalize;            /* live */
}

/* ============================================================
   SECTION HEADS — live: 6.72vw tall, 2.734vw type, left aligned
   ============================================================ */
/* live: section 89px tall @1173 (7.59vw), heading inset 42px left (3.58vw)
   and 29px down (2.47vw) — TOP-aligned, not bottom. Work, Then is shorter. */
/* The band was 7.59vw: 146px at 1920 around 52px type, which measured as
   balanced (47px above, 46px below) but read tight against the full-bleed
   films either side. Raised 15% so the heading breathes; every band moves
   by the same 15% so they stay a set, and the type and left inset are
   untouched, so nothing else on the page shifts. */
.sh-sectionhead {
  background: var(--sh-black);      /* opaque: nothing from the panel above shows through */
  position: relative; z-index: 2;
  min-height: 8.73vw;
  display: flex; align-items: flex-start;
  /* The top inset rises with the band. Left at 2.47vw the extra height
     all landed below the heading, which read as a mistake rather than as
     more air; at 3.0vw the ink sits where it did before, centred. */
  padding: 3vw var(--sh-edge) 0 3.58vw;
}
.sh-sectionhead--muted { min-height: 8.14vw; padding-top: 2.7vw; }
.sh-sectionhead h2 {
  font-size: var(--fs-h2); font-weight: 400; line-height: var(--lh-display);
  letter-spacing: -0.05em; margin: 0;   /* live: -1.604px / 32.07px */
}
.sh-sectionhead--muted h2 { color: var(--sh-grey); }

/* Each of these headings sits above a section that is a preview of a whole
   page, so the heading is the way into it. The link inherits the heading
   outright -- same size, weight and colour -- and only turns green when
   someone reaches for it, so the page reads exactly as it did before.
   Hover is behind (hover: hover) because a tap on iOS leaves :hover stuck
   on afterwards, which would strand one heading green. */
.sh-sectionhead h2 a {
  color: inherit; text-decoration: none;
  transition: color 0.18s ease;
}
@media (hover: hover) {
  .sh-sectionhead h2 a:hover { color: var(--sh-green); }
}
/* --sh-green, not --sh-green-lit. The lit one is a background lift on
   buttons everywhere else in this file; as text it reads washed out, and on
   a phone :active is the only state anyone ever sees. */
.sh-sectionhead h2 a:active { color: var(--sh-green); }
.sh-sectionhead h2 a:focus-visible {
  color: var(--sh-green);
  outline: 2px solid var(--sh-green); outline-offset: 4px;
}

/* ---- 5 FILMS / 12 SEC, in the Film Predictor band only ----
   The two figures the predictor is actually sold on, sitting in the band
   that leads to it. Not a button: the heading beside it is already the
   link, and the predictor section itself is directly below, so a second
   door here would only compete with both.

   The band centres its children instead of top-aligning them, which is
   why this is scoped to --steps rather than applied to .sh-sectionhead:
   the other three bands keep their original top-aligned heading exactly
   as it was.

   Both boxes are the same fixed width, sized for two digits, so 5 and 12
   read as a matching pair rather than one box wider than the other. The
   figures are tabular so the digits sit on the same rhythm. */
/* Packed left, beside the heading -- not pushed to the right edge. The
   "Try Predictor" pill is position: fixed at the bottom right, 198 wide,
   so the right edge of every band sweeps under it as the page scrolls.
   Right-aligned, these figures spent part of that scroll behind it. Left
   of the band they are never near it, at any width.

   padding-top is cleared because this band centres its row rather than
   top-aligning it; the other three keep their own top inset. */
.sh-sectionhead--steps {
  align-items: center; justify-content: flex-start;
  flex-wrap: nowrap; gap: 2vw; padding-top: 0;
}

/* --tap: the whole band is the link, not just the words. The heading's
   anchor stretches an invisible layer over the band, so a tap anywhere
   in the black goes where the heading goes. Markup stays as it is: an
   <a> cannot be nested inside the <a> already on the heading, and this
   needs no second one.

   Opt-in per band rather than applied to all of them, so a band can
   carry a heading link without the whole strip becoming a target. */
.sh-sectionhead--tap h2 a::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
}
.sh-sectionhead--tap { cursor: pointer; }
/* The figures sit above the stretched layer so they stay legible, but
   they must not swallow the tap: pointer-events: none lets it fall
   through to the link underneath. Without this, a tap on the figures
   themselves -- the most obvious thing in the band to aim at -- did
   nothing, while the heading beside them worked. */
.sh-steps { position: relative; z-index: 2; pointer-events: none; }

.sh-steps {
  display: flex; align-items: center; gap: 18px;
  flex: 0 0 auto; margin: 0;
}
.sh-steps__i { display: flex; align-items: center; gap: 8px; white-space: nowrap; }
.sh-steps__i b {
  width: 30px; height: 26px; border-radius: 5px;
  background: var(--sh-green); color: var(--sh-black);
  font-weight: 700; font-size: 14px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto; font-variant-numeric: tabular-nums;
}
.sh-steps__i > span {
  font-size: 13px; color: #fff; font-weight: 400;
  text-transform: uppercase; letter-spacing: 0.1em;
}

/* The hand-off: 5 lights, fades, then 12 lights. Numbering a pair exists
   to show order, and a sequence says "then" in a way two boxes blinking
   together cannot. It also keeps clear of WCAG 2.2.2 -- nothing here
   flashes on and off, it crossfades, and either box is lit at any moment. */
@keyframes sh-steps-handoff {
  0%, 42%  { background: var(--sh-green); color: var(--sh-black); }
  50%, 92% { background: #1c2a00;         color: #4f7a00; }
  100%     { background: var(--sh-green); color: var(--sh-black); }
}
.sh-steps__i b { animation: sh-steps-handoff 2.6s ease-in-out infinite; }
.sh-steps__i:nth-of-type(2) b { animation-delay: 1.3s; }

@media (prefers-reduced-motion: reduce) {
  .sh-steps__i b { animation: none; }
}

/* live: 4.92vw gap between the two project films */
.film-gap { height: 4.92vw; }

/* ============================================================
   EDITORIAL — live: 35.7vw tall
   ============================================================ */
/* overflow-x: clip, because this section was making the whole page
   scroll sideways by 3px on a phone -- the thing in the screenshots
   where the heading slides off and black shows down the sides.

   The cause is the arrows' invisible 44px tap box: the prev arrow sits
   12px from the edge and is 14px wide, so a tap target centred on it
   reaches 3px past the left edge of the page. The target has to stay
   that size to be tappable, so the section clips instead.

   clip rather than hidden: hidden would make this a scroll container
   and the track inside already does its own scrolling. */
.carousel { position: relative; height: 35.7vw; overflow-x: clip; }
/* live: the strip drifts left continuously, it is not a click-through.
   Snap is off — snapping fights a continuous drift and causes stutter. */
.carousel__track {
  display: flex; gap: 4px; height: 100%;
  overflow-x: auto; scroll-behavior: auto;
  padding: 0 var(--sh-edge); scrollbar-width: none;
}
.carousel__track::-webkit-scrollbar { display: none; }
.carousel__track img {
  flex: 0 0 24vw; height: 100%; object-fit: cover;
  display: block; user-select: none; -webkit-user-drag: none;
}
.carousel__btn {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 3;
  /* Capped. At a flat 2.6vw these reached 66px on a 2560 screen -- a
     disc of green the size of a thumbnail around a 9px arrow, sitting
     on top of the editorial pictures. The clamp keeps the vw scaling
     the rest of the page uses and stops it running away: 16px on a
     laptop, 22px at the widest. */
  width: clamp(16px, 1.15vw, 22px); height: clamp(16px, 1.15vw, 22px);
  min-width: 0; min-height: 0;
  border-radius: 50%;
  background: var(--sh-green); color: var(--sh-black);   /* footer green */
  display: grid; place-items: center; box-shadow: 0 2px 10px rgba(0,0,0,0.35);
  transition: transform 0.2s ease, background-color 0.2s ease;
}
.carousel__btn:hover { background: var(--sh-green-lit); transform: translateY(-50%) scale(1.08); }
.carousel__btn:focus-visible { outline: 2px solid var(--sh-green); outline-offset: 3px; }
.carousel__btn svg { width: 7px; height: auto; fill: currentColor; }
.carousel__btn--prev { left: calc(var(--sh-edge) + 10px); }
.carousel__btn--next { right: calc(var(--sh-edge) + 10px); }
.carousel__btn--next svg { transform: scaleX(-1); }

/* ============================================================
   WORK, THEN
   ============================================================ */
.worktiles { display: grid; grid-template-columns: 1fr 1fr; }
.worktile { position: relative; aspect-ratio: 1.02 / 1; overflow: hidden; display: block; }
.worktile img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.worktile::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,0.5) 24%, rgba(0,0,0,0.72) 100%);
}
.worktile__body {
  position: absolute; inset: 0; z-index: 2;
  display: flex; flex-direction: column; justify-content: center;
  align-items: flex-start; padding: 3vw;
}
.worktile h3 {
  /* live: 1.953vw (25px @1280), weight 400, -0.05em — NOT 2.734vw/700 */
  font-size: 1.953vw; font-weight: 400; line-height: var(--lh-display);
  letter-spacing: -0.05em; margin: 0; color: var(--sh-white);
}
/* live: 31.9px tall, 46.27% wide, 0 16px 2px, r100, rgba(24,25,26,.3), 18px #EEEDED */
.worktile__btn {
  position: absolute; right: 3vw; bottom: 3vw;
  width: 46.27%; height: 31.9px;
  display: flex; align-items: center; justify-content: flex-end;
  padding: 0 16px 2px; border-radius: 100px;
  background: rgba(24, 25, 26, 0.3);
  font-size: var(--fs-btn); font-weight: 400;
  line-height: 1.4em; letter-spacing: -0.03em; color: #EEEDED;
  transition: background-color 0.25s ease, color 0.25s ease;
}
.worktile:hover .worktile__btn { background: var(--sh-green); color: var(--sh-ink); }

/* ============================================================
   FOOTER — live: #9EFF00, 3.125vw padding, h2 at 4.844vw
   ============================================================ */
.sh-footer {
  background: var(--sh-green); color: var(--sh-ink);
  /* Measured off the innovation-design capture at 1173: footer box 461.8
     tall, first link row 69.6 down, bottom edge 82 below the CTA. The old
     flat 3.125vw put the links 36.7 down and left the footer 74 short. */
  padding: 5.93vw 3.09vw 6.67vw;
  display: flex; flex-direction: column;
}
.sh-footer__cols {
  /* columns land at 36.2 / 256.6 / 476.5 — 220.4 apart on an 1100 content
     box, i.e. 20%, not the 25% used before */
  display: grid; grid-template-columns: repeat(3, minmax(0, 20%));
  font-size: var(--fs-body); line-height: 1.6;
  min-height: 140px;                        /* live: 140.17px */
}
.sh-footer__cols a { display: block; line-height: 1.6; }
.sh-footer__cols a:hover { text-decoration: underline; }
.sh-footer__place { margin: 0; line-height: 1.6; }

.sh-footer__bottom {
  margin-top: auto; padding-top: 10vw;
  display: flex; align-items: center; justify-content: space-between;
  gap: 24px; flex-wrap: wrap;
}
.sh-footer__bottom h2 {
  display: flex; align-items: center; gap: 2vw;
  font-size: var(--fs-h1);                  /* live: 4.844vw */
  font-weight: 400; letter-spacing: -0.05em; line-height: 1; margin: 0;
}
.sh-footer__bottom h2 svg { width: 2.4vw; height: auto; fill: currentColor; flex: 0 0 auto; }
.sh-footer__contact {
  display: inline-flex; align-items: center; gap: 1.2vw;
  font-size: 2.578vw;               /* live: 33px @ 1280 */
  font-weight: 700; line-height: var(--lh-display);
  letter-spacing: 0;                /* live: none */
}
.sh-footer__contact svg { width: 2.4vw; height: auto; fill: currentColor; flex: 0 0 auto; }
.sh-footer__contact:hover { text-decoration: underline; }

/* ============================================================
   SCROLL REVEAL
   ============================================================ */
/* Wix ships exactly two enter keyframes — these are them, verbatim */
@keyframes motion-fadeIn   { 0% { opacity: 0; } 100% { opacity: 1; } }
@keyframes motion-expandIn { 0% { opacity: 0; transform: scale(0.94); } 100% { opacity: 1; transform: none; } }

.reveal { opacity: 0; }
.reveal.is-in { animation: motion-fadeIn 0.9s cubic-bezier(0.4, 0, 0.2, 1) both; }
.reveal--expand.is-in { animation: motion-expandIn 0.9s cubic-bezier(0.4, 0, 0.2, 1) both; }

/* editor: What We Do Stack > Scroll > "Parallax on Self".
   A text layer, so it drifts gently rather than being oversized. */
.px-soft { will-change: transform; }

/* editor: statement H1 > Scroll > "Expand on Self" (Animation type: In) */
.expand-on-scroll { opacity: 0; transform: scale(0.9); transform-origin: center; }
.expand-on-scroll.is-in {
  animation: motion-expandIn 1s cubic-bezier(0.22, 1, 0.36, 1) both;
}

/* ============================================================
   RESPONSIVE — Wix switches at 1000px and 750px
   ============================================================ */
@media (max-width: 1000px) {
  :root {
    --sh-edge: 24px;
    --fs-display: 46px; --fs-h1: 34px; --fs-h2: 24px;
    --fs-lead: 16px; --fs-body: 15px; --fs-btn: 16px;
  }
  .hero { height: auto; padding: 90px 24px 60px; }
  .hero__gif { position: static; width: 100%; margin: 0 auto; }
  .hero__tagline { position: static; width: 100%; margin-top: 24px; }
  .filmstrip, .filmstrip--hero { height: auto; aspect-ratio: 16 / 9; }
  .statement--light { min-height: 0; padding: 15vw 24px; }
  .black-gap { height: 90px; }
  .flow__head { position: static; height: auto; padding: 60px 0 40px; }
  .band { height: auto; aspect-ratio: 1.9 / 1; margin-bottom: 16vw; }
  .band__inner { flex-direction: column; align-items: flex-start; justify-content: flex-end; gap: 14px; padding: 20px; }
  .band__word { font-size: 30px; }   /* 40 scaled by the same 0.75 */
  .band__note .n2 { font-size: 20px; }
  .band__note svg { height: 22px; width: auto; }
  /* Height here comes from the padding, not min-height, so the same 15%
     lift is applied to the padding instead: 48+16 becomes 55+18. */
  .sh-sectionhead { min-height: 0; padding: 55px 24px 18px; }
  .carousel { height: auto; }
  .carousel__track img { flex-basis: 62vw; height: 74vw; }
  .worktiles { grid-template-columns: 1fr; }
  .worktile__btn { width: auto; left: 24px; right: 24px; bottom: 24px; }
  .sh-footer { padding: 40px 24px; }
  .sh-footer__cols { grid-template-columns: 1fr 1fr; min-height: 0; gap: 24px; }
  .sh-footer__bottom { flex-direction: column; align-items: flex-start; padding-top: 70px; }
  .sh-footer__bottom h2 svg, .sh-footer__contact svg { width: 24px; }
  /* WCAG asks for ~24px minimum; IG / X were 16x17 and 10x17 on a phone */
  .statement__foot a { display: inline-flex; align-items: center;
                       min-width: 44px; min-height: 44px; justify-content: center; }
  .about-cta { min-height: 44px; }
}

/* ============================================================
   CONTACT PAGE
   live values: hero section 48.91vw; headings 4.609vw / 400 / #000;
   form column 50.76% wide, min-height 613.9px;
   inputs r5 · 1px rgba(24,25,26,.6) · placeholder #676B70;
   submit #000 · r100.
   ============================================================ */
/* The white lives on <main>, NOT on <body>. Overscroll paints the canvas
   background, which comes from <html>/<body> — put white on body and the
   rubber-band band at the top and bottom of a light page goes white with it.
   Header and footer carry their own colours, so main is all that needs it. */
.page-light { color: var(--sh-ink); }
.page-light .sh-main { background: var(--sh-white); }
.page-light .sh-header { background: var(--sh-black); }

/* only the homepage floats the header over the hero */
.page-overlay .sh-header { position: absolute; top: 0; left: 0; right: 0; }

/* editor: Section (Gallery) W1280 H627 · background black 100%
   Scroll > Parallax on Self (confirmed in Animations & Effects). */
.contact-hero {
  height: 48.98vw;                       /* editor: 627px @ 1280 */
  overflow: hidden;
  position: relative;
  background: #000;                      /* editor: background colour black, 100% */
}
/* The drift, done by the browser.
   This layer used to be held still by a scroll listener writing a
   transform every frame, at full strength -- a true pin. That is the
   one effect a scroll handler cannot deliver: the layer's whole job is
   to not move while the page does, so every frame the handler is late
   by is a frame the image visibly slides. (Sticky would be the native
   pin, but a sticky child is confined to its parent's box and this
   layer is taller than the section, so its range is zero.)

   A scroll-driven animation runs on the compositor, off the main
   thread, and cannot be late. It buys a drift rather than a literal
   pin, which is what every other section on the site already does.

   The travel is half the overhang, not all of it: this section sits at
   the very top of the page, so it is already on screen when the
   timeline starts and only ever runs the back half of the `cover`
   range -- it spends its overhang in one direction. */
@supports (animation-timeline: view()) {
  .contact-hero { view-timeline-name: --sh-hero; view-timeline-axis: block; }
  .contact-hero .px {
    top: -30vh; bottom: -30vh; height: auto; margin-top: 0;
    animation: px-hero linear both;
    animation-timeline: --sh-hero;
    animation-range: cover 0% cover 100%;
  }
  @keyframes px-hero {
    from { transform: translate3d(0, -14vh, 0); }
    to   { transform: translate3d(0,  14vh, 0); }
  }
  /* A phone's hero box is a third the height of a desktop's, so the same
     overhang would blow the layer up and crop the subject to a sliver. */
  @media (max-width: 750px) {
    .contact-hero .px { top: -20vh; bottom: -20vh; animation-name: px-hero-phone; }
    @keyframes px-hero-phone {
      from { transform: translate3d(0, -10vh, 0); }
      to   { transform: translate3d(0,  10vh, 0); }
    }
  }
  @media (prefers-reduced-motion: reduce) {
    .contact-hero .px { animation: none; }
  }
}
.contact-hero img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* editor: Section Grid W1280 H658 · background white 100% · Columns */
/* All DOM-measured on the live page at 1173: section 658 tall, heading at
   x=40, form column x=642 w=475, i.e. the form is much narrower than the
   50.76% we had inferred from the editor. */
.contact-body {
  display: grid;
  grid-template-columns: 1fr 40.49vw;    /* live: form 475px @1173 */
  gap: 3.125vw;
  min-height: 56.10vw;                   /* live: 658px @1173 */
  padding: 4.26vw 4.77vw 6vw 3.41vw;     /* live: heading y=740 in a section at 690 */
  align-items: start;
}
.contact-body h1,
.contact-body h2 {
  font-weight: 400;
  line-height: var(--lh-display);
  color: #000;
  margin: 0;
}
/* "Get in touch." — comp override 0.0460938 * scaling-factor, .font_0 tracking */
.contact-body h1 {
  font-size: 4.609vw;
  letter-spacing: -0.04em;               /* live: .font_0 = -0.04em */
}
/* "Hello" — measured 62px at a 1173 viewport, so it is FIXED, not scaling.
   (At 4.844vw it would have been 56.8px there.) */
.contact-form h2 {
  font-size: 62px;
  letter-spacing: -0.02em;
}
.contact-form { min-height: 575px; margin-top: 8px; }  /* live: Hello sits 8px below the h1 */
.contact-form h2 { margin-bottom: 2.6vw; }

/* The booking route, under "Get in touch." in the left column. Filled
   black like the submit button so the two read as the same family, and
   first because it is the faster of the two ways to reach anyone. */
.contact-intro { display: block; }
.contact-book {
  margin: 2.4vw 0 0;
  display: flex; flex-wrap: wrap; align-items: center; gap: 14px;
}
.contact-book__btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  min-height: 48px; padding: 12px 26px;
  background: #000; color: var(--sh-bone, #EEEDED);
  border-radius: 100px; text-decoration: none;
  font-family: var(--font-display); font-size: 16px; line-height: 20px;
  transition: background-color 0.2s ease, color 0.2s ease;
}
/* Green dots on the black pill. They inherit currentColor by default,
   which made them white here. On the green buttons elsewhere green on
   green would simply disappear, so those take a deep green instead --
   see the predictor's own rules. */
.contact-book__btn .art-next__dots i { background: var(--sh-green); }
/* Behind (hover: hover) for the same reason the headings are: a tap on
   iOS leaves :hover stuck, which would strand the pill green. */
@media (hover: hover) {
  .contact-book__btn:hover { background: var(--sh-green); color: var(--sh-black); }
  /* On hover the pill turns green, so the dots have to leave it again. */
  .contact-book__btn:hover .art-next__dots i { background: var(--sh-black); }
}
.contact-book__btn:active { background: var(--sh-green); color: var(--sh-black); transition: none; }
.contact-book__btn:focus-visible { outline: 2px solid var(--sh-green); outline-offset: 3px; }
.contact-book span { font-size: 15px; color: #555; max-width: 24ch; }

/* Single column from here down, and the booking line moves to AFTER
   "Hello". On a phone "Get in touch." is hidden outright (see the rule
   that visually-hides it), so "Hello" is the first thing anyone reads
   and the button leading it would arrive before the page had introduced
   itself.

   The two wrappers become display: contents so their four children sit
   directly in the grid and can be ordered. Nothing styles either as a
   box at this width -- .contact-form's min-height is already 0 here --
   so there is nothing to lose. thanks.html shares .contact-body but has
   no .contact-form, so it is untouched. */
@media (max-width: 1000px) {
  .contact-intro, .contact-form { display: contents; }
  .contact-body > h1   { order: 1; margin-bottom: 40px; }
  .contact-form > h2   { order: 2; }
  .contact-book        { order: 3; margin: 0 0 36px; }
  .contact-form > form { order: 4; }
}

@media (max-width: 750px) {
  /* h1 is visually hidden at this width, so it owes no space. */
  .contact-body > h1 { margin-bottom: 0; }
  .contact-book {
    margin: 0 0 calc(38.7 * var(--u));
    gap: calc(12 * var(--u));
  }
  .contact-book__btn { width: 100%; }
  .contact-book span { font-size: calc(14 * var(--u)); max-width: none; }
}

.field { margin-bottom: 27px; }          /* live: --rowSpacing: 27 */
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }  /* live: --columnSpacing: 12 */
.field label {
  display: block;
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: var(--lh-body);
  margin-bottom: 8px;
  color: var(--sh-ink);
}
.field input,
.field select,
.field textarea {
  width: 100%;
  font-family: var(--font-display);
  font-size: var(--fs-body);
  color: var(--sh-ink);
  background: var(--sh-white);           /* live: 255,255,255 */
  border: 1px solid rgba(24, 25, 26, 0.6); /* live: 24,25,26,0.6 @ 1px */
  border-radius: 5px;                    /* live: 5 */
  padding: 14px 16px;
  transition: border-color var(--t-fast) ease;
}
/* The field had no hover at all — the border darkening previews the focus
   state and tells you the box is live before you click it. */
.field input:hover,
.field select:hover,
.field textarea:hover { border-color: var(--sh-ink); }
.field input::placeholder,
.field textarea::placeholder { color: #676B70; }  /* live: 103,107,112 */
.field textarea { min-height: 74px; resize: vertical; }   /* live: 473x74 */
.field select { appearance: none; background-image: none; }
.field-select { position: relative; }
.field-select::after {
  content: "";
  position: absolute; right: 18px; bottom: 22px;
  width: 9px; height: 9px;
  border-right: 1.5px solid var(--sh-ink);
  border-bottom: 1.5px solid var(--sh-ink);
  transform: rotate(45deg);
  pointer-events: none;
}
.field input:focus,
.field select:focus,
.field textarea:focus { outline: 2px solid var(--sh-ink); outline-offset: 1px; }

/* live tokens: bg 0,0,0 · text 238,237,237 · radius 100 · border-width 0
   font 16px/20px space grotesk 400 · explicit padding 11px · width 100%
   hover: bg 158,255,0 (#9EFF00) · text 0,0,0 */
.contact-submit {
  width: 100%;
  margin-top: 27px;
  padding: 11px;
  border: 0;
  background: #000;
  color: var(--sh-bone, #EEEDED);
  border-radius: 100px;
  font-family: var(--font-display);
  font-size: 16px;
  line-height: 20px;
  font-weight: 400;
  letter-spacing: 0;
  cursor: pointer;
  transition: background-color 0.2s ease, color 0.2s ease;
}
.contact-submit:hover { background: var(--sh-green); color: var(--sh-black); }@media (max-width: 1000px) {
  .contact-hero { height: auto; aspect-ratio: 3 / 4; }
  .contact-body { grid-template-columns: 1fr; gap: 40px; min-height: 0; padding: 48px 24px 64px; }
  .contact-form { min-height: 0; }
  .field-row { grid-template-columns: 1fr; gap: 0; }
}
/* live: heading jumps to 0.1023018 * scaling-factor only below 750px */
@media (max-width: 750px) {
  .contact-body h1 { font-size: 10.23vw; }
  .contact-form h2 { font-size: 10.23vw; }
  .field { margin-bottom: 32px; }        /* live: --rowSpacing: 32 under 750 */
}

/* ============================================================
   ABOUT PAGE
   live: --theme-spx-ratio = scaling-factor/1280, so font_2 =
   62 * (100vw/1280) = 4.844vw — same display size as home.
   ============================================================ */
.about-intro {
  padding: 7.6vw var(--sh-edge) 0;       /* live: margin-top 7.596vw */
}
.about-intro__inner {
  width: 81%;                            /* live: 80.99% */
  margin-left: 1.004vw;                  /* live: 0.0100385 * scaling-factor */
}

.about-eyebrow {
  font-size: 14px;                       /* live: font_9 = 14px */
  font-weight: 700;                      /* live: bold */
  line-height: 1.3;
  letter-spacing: -0.02em;
  color: var(--sh-white);
  margin: 0 0 12px;                      /* live: 11.97px */
}
.about-headline {
  font-size: 4.844vw;                    /* live: font_2 = 62 * spx */
  font-weight: 400;                      /* live: overridden to 400 */
  line-height: 1;                        /* live: 1em */
  letter-spacing: -0.05em;               /* live: -0.05em */
  color: var(--sh-white);
  margin: 0 0 53.5px;                    /* live: 53.511px */
}

/* GET STARTED — live: 15px / 700 / lh 1.4 / arrow 22px / hover #13EE78 */
.about-cta {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  /* 20.974 live. The box is also the tap target, and 21px fails WCAG 2.2
     AA (24x24 minimum), so the height becomes a floor the rule is drawn
     under rather than a ceiling: the underline sits where it always did. */
  min-height: 24px;
  font-size: 15px;
  font-weight: 700;
  line-height: 1.4em;
  letter-spacing: 0;
  text-transform: uppercase;
  color: var(--sh-white);
  border-bottom: 1px solid currentColor;
  padding-bottom: 6px;
  transition: color 0.25s ease;
}
.about-cta svg { width: 22px; height: 22px; transition: transform 0.3s ease; }
/* Was #13EE78 — a second green doing the same job as --sh-green.
   Unified, and the arrow now travels the same 6px as .sh-cta. */
.about-cta:hover { color: var(--sh-green); }
.about-cta:hover svg { transform: translateX(6px); }

/* live: 31.77vw of black between the headline and the image */
.about-gap { height: 31.77vw; }

/* ------------------------------------------------------------
   Parallax frame — live section is 39.3vw with a pinned,
   oversized media layer. Contained: no negative page margins.
   ------------------------------------------------------------ */
.parallax-frame {
  height: 39.3vw;                        /* live: 39.3vw */
  position: relative;
  overflow: hidden;
  background: #000;
}
/* Same contract as .px — see that rule. The centring MUST live in margin-top,
   never in the transform: px.js overwrites transform wholesale, so a -50% in
   there is silently dropped and the layer sits half a viewport too low. */
.parallax-frame__media {
  position: absolute; left: 0; right: 0; top: 50%;
  height: 100vh; min-height: 100%;
  margin-top: -50vh;
  transform: translate3d(0, 0, 0);
  will-change: transform;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}
.parallax-frame__media img {
  width: 100%; height: 100%;
  object-fit: cover; display: block;
}

.about-body {
  padding: 7vw var(--sh-edge) 8vw;
}
.about-body__inner {
  width: 67%;                            /* live: longest line 786px @1173 */
  margin-left: 2.76vw;                   /* live: text starts at x=69, less the 3.125vw section padding */
}
.about-body p {
  font-size: 18px;                       /* live: 18px */
  font-weight: 400;
  line-height: 1.4;
  letter-spacing: 0;
  color: var(--sh-white);
  margin: 0 0 1.6em;
}
.about-body p:last-child { margin-bottom: 0; }

@media (max-width: 1000px) {
  .about-intro { padding: 90px 24px 0; }
  .about-intro__inner { width: 100%; }
  .about-headline { font-size: 34px; margin-bottom: 32px; }
  .about-gap { height: 60px; }
  .parallax-frame { height: auto; aspect-ratio: 4 / 3; }
  .about-body { padding: 48px 24px 64px; }
  .about-body__inner { width: 100%; margin-left: 0; }
  .about-body p { font-size: 16px; }
}

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

/* ============================================================
   ABOUT — PROJECTS
   Measured off a live screenshot at a 1728 CSS viewport (3456 device, dpr 2):
     page margin   128 dev  =  64.0 CSS  = 3.70vw
     card width   1053 dev  = 526.5 CSS  = 30.47vw
     gutter         20 dev  =  10.0 CSS
     video height  583 dev  = 291.5 CSS  (aspect 1053/583)
     video → badge  45 dev  =  22.5 CSS  = 1.30vw
     badge height   63 dev  =  31.5 CSS  (14px text + ~7px padding)
     badge inset    51 dev  =  25.5 CSS  = 1.48vw from the card's left edge
     badge → button 298 dev = 149.0 CSS  = 8.62vw
     button width  722 dev  = 361.0 CSS  = 68.6% of the card, centred
     button gap     42 dev  =  21.0 CSS
   Text is 14px FIXED — the 1173 probe read 14px too, so it does not scale.
   ============================================================ */
.about-projects { padding: 0 3.70vw 2.84vw; }
.about-projects__row {
  display: grid;
  /* two ventures, so two across and each one big enough to read as a piece
     of work rather than a thumbnail */
  grid-template-columns: repeat(2, 1fr);
  gap: 28px;
}
/* Each venture is one surface, not four loose pieces stacked on black.
   The old card put the name in a white pill at the same 14px as its
   tagline, so the loudest thing on it was a chip rather than the work. */
.apj {
  min-width: 0; display: flex; flex-direction: column;
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: 18px;
  padding: 14px 14px 22px;
  transition: border-color 0.3s ease, background-color 0.3s ease;
}
/* :focus-within keeps the card lit while a keyboard tabs through its
   links, which hover alone never covered. */
.apj:hover,
.apj:focus-within { border-color: var(--sh-green); background: rgba(158,255,0,0.03); }
.apj__media {
  width: 100%; aspect-ratio: 1053 / 583; overflow: hidden;
  background: #000; border-radius: 10px;
}
.apj__media img, .apj__media video {
  width: 100%; height: 100%; object-fit: cover; display: block;
}

/* The venture's own name, in the page's display voice */
.apj__name {
  font-family: var(--font-display);
  font-size: clamp(26px, 2.5vw, 38px);
  font-weight: 400; line-height: 1; letter-spacing: -0.045em;
  color: var(--sh-white);
  margin: 20px 0 0; padding: 0 8px;
}
.apj__line {
  font-size: 14px; line-height: 1.35; color: rgba(255,255,255,0.6);
  margin: 9px 0 0; padding: 0 8px;
}

.apj__links {
  display: flex; flex-wrap: wrap; align-items: center; gap: 9px;
  margin-top: 20px; padding: 0 8px;
}
.apj__btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.6em;
  width: auto;
  padding: 8px 16px;
  border: 1px solid rgba(255,255,255,0.4);
  border-radius: 100px;
  font-size: 14px; line-height: 1.3;
  color: var(--sh-white);
  transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
/* one action leads, the rest follow */
.apj__btn--pri {
  background: var(--sh-green); border-color: var(--sh-green);
  color: var(--sh-black); font-weight: 500;
}
.apj__btn:hover {
  background: var(--sh-green); border-color: var(--sh-green); color: var(--sh-black);
}

@media (max-width: 1000px) {
  .about-projects { padding: 0 24px 48px; }
  .about-projects__row { grid-template-columns: 1fr; gap: 40px; }
  .apj__links { margin-top: 18px; gap: 10px; }
  .apj__btn { width: auto; }
}

/* ============================================================
   WORK LIST  (Innovation & Design, Integrated Campaigns)
   Captured off the live page at a 1173 viewport, dpr 2.
     header section   0 -> 115.6
     H1 box         135.6, x 55.9, 43.9875px white   (48 / 61 at 1280)
     grid top       222.1   (30 below the heading)
     tile         586.5 x 417.9  = exactly 50vw, ar 1174/836
     grid is FULL BLEED, two columns, no gutter and no row gap
     caption band  bottom 155.8 of the tile, rgba(24,25,26,.6)
       title  +28.7 from band top, x +21.1, 18.3281px, -0.05em, #fff
       sub    +56.5,               x +21.1, 12.8297px, -0.04em, #EEEDED
       CTA    +106.5,              x +443.9 (75.69%), 18px FIXED, #fff
   No parallax on this page: every tile transform sampled at 0.0 across
   15 scroll positions.
   ============================================================ */
.worklist {
  background: var(--sh-black);
  padding: 2.47vw 0 2.558vw 4.7655vw;   /* heading box at 135.6, grid at 222.1 */
}
.worklist__head {
  /* floors keep these legible between the 750 phone breakpoint and the
     desktop sizes; at 1173 the vw value wins and nothing changes */
  font-size: max(28px, 3.75vw);         /* live 43.9875 @1173 = 48 at 1280 */
  font-weight: 400;
  line-height: 1.284;                   /* live box 56.5 / 43.9875 */
  letter-spacing: 0;                    /* live: none on this heading */
  color: var(--sh-white);
  margin: 0;
}

/* Integrated Campaigns differs from Innovation & Design only here:
     heading  138.5 / x 35.5   (vs 135.6 / 55.9)
     grid top 227.9            (vs 222.1)
     caption inset 18.3        (vs 21.1)
   Band height, CTA position and tile size are identical on both. */
.worklist--ic { padding: 2.708vw 0 2.805vw 3.0264vw; }
.wgrid--ic .wtile__band { padding: 2.2421vw 0 0 1.5601vw; }

.wgrid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0;                               /* tiles butt together, full bleed */
  background: var(--sh-black);
}
.wtile {
  position: relative;
  aspect-ratio: 1174 / 836;
  overflow: hidden;
  display: block;
  background: #000;
}
.wtile__media {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; display: block;
}
.wtile__band {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 37.28%;                       /* 155.8 of 417.9 */
  background: rgba(24, 25, 26, 0.6);
  padding: 2.4467vw 0 0 1.7988vw;       /* 28.7 / 21.1 at 1173 */
  box-sizing: border-box;
}
.wtile__title {
  display: block;
  margin: 0;                            /* an <h3> now — kill the UA margins */
  font-size: max(15px, 1.5625vw);       /* 20 at 1280 */
  font-weight: 400; line-height: 1;
  letter-spacing: -0.05em;
  color: var(--sh-white);
}
.wtile__sub {
  display: block;                       /* a <span> now, so the margin applies */
  font-size: max(11px, 1.09375vw);      /* 14 at 1280 */
  font-weight: 400; line-height: 1;
  letter-spacing: -0.04em;
  color: var(--sh-bone);                /* live #EEEDED */
  margin: 0.807vw 0 0;
}
/* A full-width pill, the same object as the home page's "View Projects":
   31.9px tall at 1173, 16px of right padding, text right-aligned, resting
   fill rgba(24,25,26,.3). Live has bare text here; this is a deliberate
   departure so the two grids behave alike. Insets match the band's own
   padding so the pill lines up with the title above it. */
.wtile__cta {
  position: absolute;
  right: 1.7988vw;
  top: 9.0793vw;                        /* 106.5 below the band top */
  /* Exactly the home tile's button: 46.27% wide and a FIXED 31.9px tall.
     The height was 2.72vw, which only equals 31.9 at a 1173 viewport — on a
     wider window it grew to ~54 and the two buttons no longer matched. */
  width: 46.27%; height: 31.9px;
  display: flex; align-items: center; justify-content: flex-end;
  padding: 0 16px 2px;
  border-radius: 100px;
  background: rgba(24, 25, 26, 0.3);
  font-size: var(--fs-btn);
  font-weight: 400; line-height: 1.4em; letter-spacing: -0.03em;
  color: #EEEDED;
  transition: background-color var(--t-fast) ease, color var(--t-fast) ease;
}
/* Integrated Campaigns indents its band slightly less. */
.wgrid--ic .wtile__cta { right: 1.5601vw; }   /* restated in the phone block — two classes beat one */
.wtile:hover .wtile__media { transform: scale(1.03); }
.wtile__media { transition: transform var(--t-slow) cubic-bezier(0.22, 1, 0.36, 1); }

/* "View Project" lights up green on hover, the way the home-page tiles do.
   The pill is a ::before sized by negative inset rather than padding on the
   element, so nothing shifts: at rest the box is there but transparent. */
.wtile:hover .wtile__cta { background: var(--sh-green); color: var(--sh-ink); }

/* ============================================================
   PROJECT PAGE  (innovation-design/* and integrated-campaigns/*)
   Live capture at 1173 (desktop) + four phone captures (mobile).

   DESKTOP, two columns:
     page margin  11.4, columns 563 each, 23.8 between
     title     y 198.0, 56.8172px, RIGHT-aligned, #18191A, lh 1
     subtitle  y 329.0, same size, #8A8785
     credits   y 472.3 / 500.3 / 528.3 (pitch 28), EB Garamond 18px FIXED,
               value italic left, label right
     body      y 622.5, x 58.6, 27.4922px, line-height 24.743 (0.9 — tighter
               than the font size, which is deliberate)
     prev/next y 1065.8
     media col x 598.2, starts y 127.4 (ABOVE the title), 12 between shots
   No parallax: every transform sampled 0.0 across 6 scroll positions.
   ============================================================ */
.proj { background: var(--sh-white); color: var(--sh-ink); }
.proj__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2.029vw;                       /* 23.8 at 1173 */
  /* No padding on the grid: the media column runs to the right edge and to the
     top of the section. Left inset lives on .proj__text instead. */
  padding: 0;
  /* NOT align-items:start. The text column has to fill the row height or the
     sticky block inside it has nowhere to travel. */
  align-items: stretch;
}
/* padding-bottom keeps the sticky block from parking the Prev/Next pills
   flush against the footer once it releases — live leaves only 11.7 there
   and it reads as a collision. */
.proj__text { padding: 7.784vw 0 4.09vw 0.972vw; }
/* The text column pins while the media column keeps scrolling — live has a
   586.5 x 894.1 sticky box starting at the title. The padding stays OUTSIDE
   it so the title pins flush, matching the live box top. */
.proj__sticky { position: sticky; top: 0; }
.proj__title {
  font-size: 4.8438vw;                /* 56.8172 at 1173 = 62 at 1280 */
  font-weight: 500; line-height: 1; letter-spacing: -0.02em;   /* live 400 */
  text-align: right; margin: 0; color: var(--sh-ink);
}
.proj__sub {
  font-size: 4.8438vw; font-weight: 500; line-height: 1;       /* live 400 */
  letter-spacing: -0.02em; text-align: right;
  color: var(--sh-grey);              /* live #8A8785 */
  /* Hairlines top and bottom, same treatment as the phone layout. Live has
     none here, but the rules read as part of the credit stack below and the
     subtitle looked unanchored without them. Padding is in em so the gap
     keeps its proportion to the type at any viewport. */
  margin: 1.483vw 0 0;
  padding: 0.333em 0;
  border-top: 1px solid var(--sh-grey-mid);
  border-bottom: 1px solid var(--sh-grey-mid);
}
/* trimmed from 7.374vw so the first credit rule stays where it was once the
   subtitle gained its own padding and two 1px rules */
.proj__credits { margin-top: 3.973vw; }
.proj__credit {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 1.7vw;
  font-family: var(--font-serif);     /* EB Garamond */
  font-size: 18px;                    /* FIXED — live reads plain px */
  line-height: 19.8px;
  /* row pitch is 28.0: 19.8 line + 7.2 padding + 1 rule, nothing between */
  padding-bottom: 7.2px;
  margin: 0;
  border-bottom: 1px solid rgba(24, 25, 26, 0.85);
}
.proj__cval { font-style: italic; }
.proj__clabel { text-align: right; white-space: nowrap; }
.proj__body {
  margin: 5.644vw 0 0 4.024vw;        /* body box at 622.5, x 58.6 */
  max-width: 38.4vw;                  /* live block 450.4 wide */
}
/* DELIBERATELY NOT LIKE LIVE. Live is 27.4922px on a 24.743 line-height —
   leading tighter than the type size, which is what made this block hard to
   read rather than the size itself. Smaller type AND normal leading here.
   To go back to the live look: font-size 2.3438vw, line-height 0.9,
   margin-bottom 0.3em. */
.proj__body p {
  font-size: 1.72vw;                  /* 20.2 at 1173 (live 27.4922) */
  line-height: 1.32;                  /* live 0.9 */
  font-weight: 400; margin: 0 0 0.9em;
}
.proj__body p:last-child { margin-bottom: 0; }

.proj__nav { display: flex; gap: 2.4vw; margin-top: 6.155vw; }
.proj__btn {
  flex: 1 1 0; min-width: 0;
  display: flex; align-items: center;
  /* 26.2 at 1173 — the live sticky box is 894.1 tall and only closes on
     that number with this button height */
  height: 2.233vw; min-height: 26px;
  padding: 0 1.6vw;
  border-radius: 100px;
  background: var(--sh-ink); color: var(--sh-white);
  font-size: 16px; line-height: 1.4;
  transition: background-color var(--t-fast) ease, color var(--t-fast) ease;
}
.proj__btn--prev { justify-content: flex-start; }
.proj__btn--next { justify-content: flex-end; }
/* Was #18191A -> #000, a contrast change of 1.19 between the two states,
   i.e. invisible. Green fill, the same move the submit button makes. */
.proj__btn:hover { background: var(--sh-green); color: var(--sh-ink); }

/* Flush to the top and to the right edge; the only thing between the shots is
   a thin black band, which is the column's own background showing through. */
.proj__media { padding-top: 0; background: var(--sh-black); }
.proj__shot {
  display: block; width: 100%; height: auto;
  object-fit: cover; background: #000;
  margin-bottom: 0.4vw;                /* 4.7 at 1173 */
}
/* Never crop a film. The inline aspect-ratio on these came from the old
   site's layout slot rather than the file, so cover was cutting the top and
   bottom off any video whose real shape did not match. px.js replaces the
   ratio with the file's own as soon as metadata lands; contain makes sure
   that until it does, and if the script never runs, the whole frame is
   still visible. */
video.proj__shot { object-fit: contain; }
.proj__shot:last-child { margin-bottom: 0; }

/* ============================================================
   PHONE LAYOUT — measured, not guessed.
   Source: live-site screenshots taken on an iPhone 16 Pro Max,
   1320 x 2868 device px at dpr 3 → a 440 CSS px viewport.
   Every number below was read off those captures with a pixel
   probe (row/column colour steps for boxes, ink bounding boxes
   for type), then divided by 3. See HANDOFF "Mobile".

   Wix's mobile canvas is a 320px design grid scaled to the
   viewport, so the layout scales rather than sitting at fixed
   px. --u is one CSS pixel AT 440 WIDE: 1/440 = 0.2272727%.
   Writing calc(81 * var(--u)) therefore means "81px the way it
   measured on the 16 Pro Max, proportionally smaller on a
   narrower phone" — which is exactly what the live site does.

   Caveat worth keeping: only ONE mobile width was captured, so
   px-vs-scaling cannot be told apart from the evidence. Scaling
   is the choice because that is how Wix mobile works and how the
   desktop build already behaves. A probe at a second width would
   settle it.
   ============================================================ */
@media (max-width: 750px) {
  :root {
    /* 1px at 440 CSS px wide. Bounded, because this block is calibrated at 440
     but applies to 750: unbounded it reached 1.70px at 744 (a 144px header on
     an iPad mini) and fell to 0.73px at 320, taking eleven font sizes under
     10px. The floor and ceiling hold the design between ~343 and ~462 and
     degrade gently outside it. */
  --u: clamp(0.78px, 0.2272727vw, 1.05px);
    --sh-gutter: calc(27.5 * var(--u));
    --sh-edge:   calc(27.5 * var(--u));
    --sh-header-h: calc(85.3 * var(--u));
  }

  /* ---- Header: logo 23 tall, burger 32, gutters 27.5 ---- */
  /* 85.3, measured directly off the Contact capture where the header is in
     flow and the hero starts right under it. The 82.5 used before was
     inferred from logo centring on Home, where the header overlays the hero
     and its height is therefore invisible. */
  .sh-header { height: calc(85.3 * var(--u)); background: #000; }
  /* 31.4u would only match the old optical size; 34 is the "slightly
     bigger on mobile" that was asked for, about 8% above that. */
  .sh-logo svg { height: calc(34 * var(--u)); }
  /* the wordmark is only ~23 tall; the link needs a 44px target around it */
  .sh-logo { display: flex; align-items: center; min-height: 44px; }
  /* the logo beside it is already floored at 44; this measured 25x25 at 320 */
  .sh-burger { width: calc(32 * var(--u)); height: calc(32 * var(--u));
               min-width: 44px; min-height: 44px; }

  /* ---- Hero: black panel 453 tall, then a SQUARE full-bleed film.
       "Play" ink sat at y 453-545 of the page, the tagline at 578-589,
       and the film started at 705 — i.e. 60 (header) + 453.           */
  /* 705.3 not 453: the header OVERLAYS the hero (page-overlay), so the hero
     box starts at page y 0 and the film below it begins at 705.3. */
  .hero { height: calc(705.3 * var(--u)); }
  /* position must be restated: the <=1000px tablet block sets these two
     static, and a static box ignores top/left (but still honours transform,
     which is how the gif ended up 80px off the left edge). */
  /* The wordmark is an animating GIF, so its ink box changes frame to frame —
     comparing against the live capture only works on the matching frame.
     The live phone shot caught frame 5 (ink aspect 2.66), whose ink is
     1024/1750 = 58.51% of the GIF's width and starts 392/1167 down it.
     Live ink was 246 wide starting at y 452.7, which solves to a 420.4 box
     at top 358.5. Checked: that predicts a 92.5 ink height vs 92.4 measured. */
  .hero__gif {
    position: absolute;
    left: 50%; transform: translateX(-50%);
    top: calc(358.5 * var(--u));
    width: calc(420.4 * var(--u));
    margin: 0;
  }
  .hero__tagline {
    position: absolute;
    left: 0; right: 0; width: auto; margin: 0;
    top: calc(575.7 * var(--u));
  }

  /* Hero film KEEPS the parallax, resized for the box. Desktop pins a
     viewport-tall sheet; on a phone that sheet is 1.8x the 440 square and
     the film would read as a narrow vertical slice of itself. So the layer
     is 133% of its box — the same layer-to-box ratio desktop runs — and
     px.js drifts it on the same measured slope, clamped to the slack so an
     edge never shows. Same effect, framed properly.
     top is a percentage of the PARENT HEIGHT, which is what we want here;
     margin-top percentages resolve against parent WIDTH, so not those. */
  .px,
  .parallax-frame__media {
    position: absolute; left: 0; right: 0;
    top: -16.5%; height: 133%;
    min-height: 0; margin-top: 0;
  }
  /* the video is 125% of .px by default — compounding it with the 133%
     layer would crop twice, so inside the hero it just fills the layer. */

  /* ---- Statement (black): text left at 81, symmetric, 32/34 ---- */
  .statement {
    min-height: 0; text-align: left;
    padding: calc(215 * var(--u)) calc(81 * var(--u)) calc(150 * var(--u));
  }
  .statement h1, .statement .statement__h {
    font-size: calc(32 * var(--u));
    line-height: calc(34 * var(--u));      /* measured pitch: 34.0 */
    max-width: none; margin: 0;
  }
  .statement__foot {
    justify-content: flex-start; margin-top: calc(90 * var(--u));
    gap: calc(18 * var(--u));
  }
  /* Both taglines (hero and statement foot) are the same string and size.
     Floored at 10px: pure scaling puts 11.5u at 9.4px on a 360 phone. */
  /* Was max(10px, 11.5u) -- about 11px on a phone, which is caption size
     for the line that carries the studio's whole proposition. */
  .sh-tagline { font-size: max(13px, calc(14.5 * var(--u))); letter-spacing: -0.01em; }
  .sh-cta { font-size: calc(13 * var(--u)); gap: calc(14 * var(--u)); }
  .sh-cta svg { width: calc(22 * var(--u)); height: auto; }

  .black-gap { height: calc(300 * var(--u)); }

  /* ---- Flow: the head does NOT stick on mobile. It scrolls away before
       the first card arrives — confirmed across three captures. ---- */
  .flow { padding: calc(40 * var(--u)) 0 0; }
  /* STICKY on mobile too — same as desktop. An earlier pass read it as
     static, wrongly: the captures that showed it "scrolling away" were all
     taken before it reached the pin. The proof is two captures where the
     heading sits at the IDENTICAL screen y (595 dev) while card 1 sits at
     844 and 1062 — the head had stopped and the cards kept coming.
       pinned heading ink  = 128.3 below the content top  -> padding-top 120
       heading ink to card 1 in the document = 527        -> height 655
     (height - padding - the 8px ink offset inside the line box = 527.) */
  .flow__head {
    position: sticky; top: 0; z-index: 0;
    height: calc(655 * var(--u));
    padding: calc(120 * var(--u)) 0 0;
    margin: 0; box-sizing: border-box;
  }
  /* Tracking note, applies to every display item below: desktop runs
     -0.04/-0.05em, the live phone layout runs none. Keeping the negative
     tracking left each heading 10-16% narrower than measured while its
     height matched — the tell that it was tracking, not size. */
  .flow__head h2 { font-size: calc(31.5 * var(--u)); letter-spacing: 0; }
  .flow__head p { margin: calc(5 * var(--u)) 0 0; }

  /* Cards go portrait: 426 x 556.5 inside a 440 viewport, 7 side margin,
     16.6 between, 4 radius. Word top-left, icon + note bottom-left. */
  .band {
    /* Was 556.5u -- 544px on a large phone, over half the screen, and the
       band art is a 3.6:1 strip, so filling that height meant scaling the
       image up 1.9-2.5x. Shorter band, less upscaling: the two complaints
       were the same problem. */
    width: auto; height: calc(420 * var(--u));
    /* the tablet block sets aspect-ratio 1.9/1; left in place it would win
       over width:auto and compute the width FROM the height — 1057px wide. */
    aspect-ratio: auto;
    margin: 0 calc(7 * var(--u)) calc(16.6 * var(--u));
    border-radius: calc(4 * var(--u));
  }
  /* 125% of oversize was for px.js, which does not run on touch. The CSS
     parallax below travels far less, so the layer only needs 9% -- the rest
     was making the browser scale the art up for a box whose edges nobody
     sees. */
  .band__media { top: -4.5%; height: 109%; }
  /* Phone-only art. The desktop strips are 3.6:1 and the phone band is
     square, so they were cropped to a sliver and blown up. These two are
     framed for this shape: the baby is 5504x3072, so it scales DOWN into
     the band instead of up. The machine band keeps its own image. */
  .band--imagination .band__media {
    background-image: url('/assets/imagination-mobile.223177ae.webp') !important;
  }
  .band--story .band__media {
    background-image: url('/assets/story-mobile.06bf28ec.webp') !important;
  }
  /* The one that was 9KB at 2362x656 and looked it. The replacement is a
     2.2MB frame of the same shot: still upscaled 1.4x into this box, since
     it is 941 tall, but it is real detail rather than compression mush. */
  .band--machine .band__media {
    background-image: url('/assets/machine-mobile.2db784d7.webp') !important;
  }
  .band__inner {
    flex-direction: column; align-items: flex-start;
    justify-content: space-between; gap: 0;
    /* Bottom was 77u -- 75px holding the note well clear of the floor while
       the word sat 54px from the top, so the block read as floating in the
       middle rather than anchored. 40u brings it down to roughly match the
       word's distance from the top. */
    padding: calc(55 * var(--u)) calc(34 * var(--u)) calc(40 * var(--u));
  }
  .band__word { font-size: calc(34 * var(--u)); letter-spacing: 0; margin: 0; }
  .band__note {
    flex-direction: column; align-items: flex-start;
    gap: calc(10 * var(--u));
  }
  .band__note svg { height: calc(21 * var(--u)); width: auto; }
  .band__note .n1 { font-size: calc(14 * var(--u)); }
  .band__note .n2 { font-size: calc(30.5 * var(--u)); letter-spacing: 0; }
  .flow-tail { height: calc(22 * var(--u)); }

  /* ---- What We Do (white): left 54, label 14, body 32/34, note 14 ---- */
  .statement--light {
    min-height: 0;
    padding: calc(92 * var(--u)) 0 calc(114 * var(--u));
  }
  .statement--light .statement__inner {
    width: auto; align-items: flex-start; gap: 0;
    padding: 0 calc(54 * var(--u));
  }
  .statement--light .statement__eyebrow {
    font-size: calc(14 * var(--u)); margin: 0 0 calc(37 * var(--u));
  }
  .statement--light .statement__h { text-align: left; }
  .statement--light .statement__note {
    font-size: calc(13 * var(--u)); margin: calc(42 * var(--u)) 0 0;
  }

  /* ---- Section heads: an 88-tall black band, ink 26 down, 18 in from the
       left, 25px type. Same for the muted "Work, Then". Was 73: at that
       height the Film Predictor figures sat hard against the bottom edge,
       and all four bands move together so they stay a set. */
  .sh-sectionhead,
  .sh-sectionhead--muted {
    min-height: calc(88 * var(--u));
    /* 26 became 31 for the same reason as on desktop: the taller band
       needs the ink to move down with it, or it sits high and the gap
       all collects underneath. */
    padding: calc(31 * var(--u)) calc(18 * var(--u)) 0;
  }
  .sh-sectionhead h2 { font-size: calc(25.5 * var(--u)); letter-spacing: 0; }

  /* On a phone the figures sit on the heading's own line rather than
     under it. The row is left-packed, not spread: pushed to the right
     edge it would run under the fixed "Try Predictor" pill, which starts
     at 326 of a 390 screen. Packed left it ends around 300, clear of it.
     Padding-top is dropped here because the row is vertically centred. */
  /* All the extra height goes above, none below. The gap above cuts the
     band away from the editorial carousel it sits under; the tighter
     gap below keeps it attached to the predictor section it introduces.
     Centred inside the remaining box, so 72 above and 28 below. */
  .sh-sectionhead--steps {
    align-items: center; justify-content: flex-start;
    gap: calc(14 * var(--u));
    padding-top: 110px;
    min-height: calc(88 * var(--u) + 110px);
  }
  /* Sized up from 22/19/11/10: at that size the figures read as a
     footnote to the heading rather than as the claim. This is as large
     as they go -- the row has to end before the fixed pill at the right
     edge, and on a 360 screen the next size up leaves 4px, which is
     nothing. Here a 360 still clears it by 18. */
  .sh-steps { gap: calc(13 * var(--u)); }
  .sh-steps__i { gap: calc(7 * var(--u)); }
  .sh-steps__i b {
    width: calc(26 * var(--u)); height: calc(23 * var(--u));
    font-size: calc(13 * var(--u));
  }
  .sh-steps__i > span { font-size: calc(11.5 * var(--u)); }

  /* ---- Project films: full bleed, 440 x 172.5, 21.7 apart ---- */
  .filmstrip { height: auto; aspect-ratio: 440 / 172.5; }
  /* doubled class so it beats .filmstrip above rather than tying on
     specificity and losing to source order — the hero film is square. */
  .filmstrip.filmstrip--hero { height: auto; aspect-ratio: 1 / 1; }
  .film-gap { height: calc(21.7 * var(--u)); }

  /* ---- Editorial: a 3:4 slide 351 wide, 44 side margin so the
       neighbours peek, 5 gutter, 20px arrows centred vertically. ---- */
  .carousel { height: calc(468 * var(--u)); }
  .carousel__track {
    gap: calc(5 * var(--u)); padding: 0 calc(44 * var(--u));
    /* paged, not drifting (see index.html) — snap keeps a slide centred */
    scroll-behavior: smooth; scroll-snap-type: x mandatory;
  }
  .carousel__track img {
    flex: 0 0 calc(351 * var(--u)); height: 100%;
    scroll-snap-align: center;
  }
  .carousel__btn {
    width: 14px; height: 14px;
    min-width: 0; min-height: 0;
  }
  .carousel__btn svg { width: 6px; }
  /* 12px, not closer: the "Try Predictor" pill is fixed to the right
     edge and 50 wide, so the next arrow already passes beneath it as
     the page scrolls. Nearer the edge and it sits on the pill outright. */
  .carousel__btn--prev { left: 12px; }
  .carousel__btn--next { right: 12px; }

  /* ---- Work, Then: full-bleed SQUARE tiles, stacked, no gap.
       Label 20px bottom-left, then a full-width pill. ---- */
  .worktiles { grid-template-columns: 1fr; }
  .worktile { aspect-ratio: 1 / 1; }
  .worktile__body {
    /* 73.3 = the 49.3 the button block occupies + a 24 gap. Was 56, which
       left only 6.7 between the name and the pill. Left inset drops 14.7 ->
       12 so the name lines up with the button's left edge. */
    justify-content: flex-end;
    padding: 0 0 calc(73.3 * var(--u)) calc(12 * var(--u));
  }
  .worktile h3 { font-size: calc(21 * var(--u)); letter-spacing: 0; }
  .worktile__btn {
    left: calc(12 * var(--u)); right: calc(12 * var(--u));
    width: auto; bottom: calc(16.3 * var(--u));
    height: calc(33 * var(--u));
    font-size: calc(17 * var(--u));
    padding: 0 calc(13 * var(--u)) calc(1 * var(--u));
  }

  /* ---- Footer: nav column at 35.3, second column at 283.3 with
       "Singapore" on the Youtube baseline, then a centred CTA. ---- */
  .sh-footer {
    padding: calc(29 * var(--u)) calc(35.3 * var(--u)) calc(100 * var(--u));
  }
  .sh-footer__cols {
    grid-template-columns: calc(248 * var(--u)) 1fr;
    gap: 0; min-height: 0;
    font-size: calc(16 * var(--u));
  }
  /* Line-height alone is the live rhythm (28 / 25.5 at 440). It was floored at
     44px for tap targets, which stretched the list to half again its height and
     lost the original look. Back to the rhythm: ~25px still clears the 24px
     WCAG 2.2 AA minimum. */
  .sh-footer__cols a { display: flex; align-items: center; min-height: 24px; }
  /* Explicit placement: left to auto-flow, the socials nav lands in ROW 1
     COLUMN 2 (beside the page nav) and Singapore drops underneath it. Live
     stacks both navs in column 1 and puts Singapore alone in column 2, on
     the Youtube baseline. */
  .sh-footer__cols nav:first-of-type { grid-area: 1 / 1; }
  .sh-footer__cols nav:first-of-type a { line-height: calc(28 * var(--u)); }
  /* 147 came from a live capture where this nav sits in another grid row;
     stacked on a phone it was just a 147px hole of empty green */
  .sh-footer__cols nav + nav { grid-area: 2 / 1; margin-top: calc(118 * var(--u)); }
  .sh-footer__cols nav + nav a { line-height: calc(25.5 * var(--u)); }
  .sh-footer__place {
    grid-area: 2 / 2; align-self: end;
    line-height: calc(25.5 * var(--u));
  }
  .sh-footer__bottom {
    flex-direction: column; align-items: center; justify-content: center;
    padding-top: calc(86 * var(--u)); gap: calc(40 * var(--u));
  }
  .sh-footer__bottom h2 {
    font-size: calc(26 * var(--u)); text-align: center; justify-content: center;
  }
  .sh-footer__bottom h2 svg { display: none; }   /* not present on the live phone layout */
  .sh-footer__contact {
    font-size: calc(32 * var(--u)); gap: calc(23 * var(--u));
  }
  .sh-footer__contact svg { width: calc(25 * var(--u)); }

  /* ---------- ABOUT ----------
     Measured the same way, off four phone captures of the live page. Page-
     coordinate landmarks (page y, 440 wide):
       eyebrow ink      167.0      headline ink starts 217.7, pitch 36
       GET STARTED ink  562.7      frame      704.7 -> 1143.4  (1:1 SQUARE)
       body first ink  1203.7      body pitch 19.7, paragraph gap +19.5
       card 1 video    1776.4 -> 2020.0   (ar 1.806, same as desktop)
       badge row       2047.4      buttons 2161.4 and 2207.0
       next card video 2277.7      -> card pitch 501.3
     Note the two left margins differ and that is real: the intro block sits
     at 58, the body copy at 29.7.                                        */
  /* NOT symmetric: left 58, right 77. A symmetric 58 gives a 324 column,
     which fits one extra word and collapses the headline to 7 lines; live
     is 8, longest line 298.7. */
  .about-intro { padding: calc(77.2 * var(--u)) calc(77 * var(--u)) 0 calc(58 * var(--u)); }
  .about-intro__inner { width: auto; margin-left: 0; }
  .about-eyebrow {
    font-size: calc(14 * var(--u));
    margin: 0 0 calc(31 * var(--u));
  }
  .about-headline {
    font-size: calc(31.5 * var(--u));
    line-height: calc(36 * var(--u));
    margin: 0 0 calc(46.7 * var(--u));
  }
  .about-cta { font-size: calc(16 * var(--u)); height: auto; gap: calc(14 * var(--u)); }
  .about-cta svg { width: calc(22 * var(--u)); }
  .about-gap { height: calc(114.4 * var(--u)); }

  /* full bleed and SQUARE — 440 x 438.7 measured */
  .parallax-frame { height: auto; aspect-ratio: 1 / 1; }

  .about-body { padding: calc(54 * var(--u)) calc(29.7 * var(--u)) calc(66 * var(--u)); }
  .about-body__inner { width: auto; margin-left: 0; }
  .about-body p {
    font-size: calc(13 * var(--u));
    line-height: calc(19.7 * var(--u));
    margin: 0 0 calc(19.5 * var(--u));
  }

  /* Projects: video goes FULL BLEED (no page padding), the badge row sits at
     35.3, and the two links are centred pills 181.7 wide — not the
     full-width buttons the tablet block makes them. */
  /* zero bottom padding put the last venture's link flush against the green
     footer — the cards need room to end before the next band starts */
  /* the card is the page's left and right margin now, so the section
     itself only carries the gutter the card sits in */
  .about-projects { padding: 0 calc(24 * var(--u)) calc(76 * var(--u)); }
  .about-projects__row { grid-template-columns: 1fr; gap: calc(28 * var(--u)); }
  .apj { padding: calc(12 * var(--u)) calc(12 * var(--u)) calc(20 * var(--u)); }
  .apj__name { font-size: calc(30 * var(--u)); margin-top: calc(18 * var(--u)); }
  .apj__line { font-size: calc(14 * var(--u)); }
  .apj__links { margin-top: calc(18 * var(--u)); gap: calc(9 * var(--u)); }
  .apj__btn {
    width: auto; min-height: calc(38 * var(--u));
    font-size: calc(14 * var(--u));
    padding: calc(8 * var(--u)) calc(16 * var(--u));
  }

  /* ---------- CONTACT ----------
     Page-y landmarks at 440 (two live phone captures):
       hero         85.3 -> 584.7   (440 x 499.4, full bleed)
       "Hello" ink 665.0            labels 13px, inputs 37 tall
       inputs      754.0 / 849.0 / 944.0 / 1039.0   -> pitch 95.0
       textarea   1132.0 -> 1223.0  (91 tall)
       SUBMIT     1256.0 -> 1297.3  (41.3 tall)
       footer     1366.7
     Form column is symmetric: 30 each side, 380 wide.                   */
  .contact-hero { height: auto; aspect-ratio: 440 / 499.4; }
  .contact-body {
    grid-template-columns: 1fr; gap: 0; min-height: 0;
    padding: calc(70.9 * var(--u)) calc(30 * var(--u)) calc(69.4 * var(--u));
  }
  /* The live phone layout shows no "Get in touch." — the form's own "Hello"
     is the only heading. Hidden rather than removed so the page keeps its
     h1 for search and screen readers. */
  .contact-body h1 {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
  .contact-form { min-height: 0; }
  .contact-form h2 {
    font-size: calc(24 * var(--u)); line-height: 1.1;
    margin: 0 0 calc(38.7 * var(--u));
  }
  .field-row { grid-template-columns: 1fr; gap: 0; }
  .field { margin-bottom: calc(32.6 * var(--u)); }
  /* The message field butts straight onto SUBMIT, so it drops the field gap.
     NOT :last-of-type — first/last name are two divs inside .field-row, so
     that selector matches "Last name" and knocks 32.6 out of the middle of
     the form. :has(textarea) names the one field actually meant. */
  .contact-form .field:has(textarea) { margin-bottom: 0; }
  .field label {
    font-size: calc(13 * var(--u));
    margin-bottom: calc(8.5 * var(--u));
  }
  .field input, .field select, .field textarea {
    /* The base rule pads 14px/16px; with box-sizing:border-box an explicit
       height left a 0px content box at 320 and the select rendered blank.
       Let padding size the field, floor it at 44 for touch, and keep the type
       at 16px so iOS does not zoom the page when a field takes focus. */
    height: auto; min-height: 44px;
    padding: 11px 14px;
    font-size: 16px;
  }
  .field textarea {
    min-height: max(96px, calc(91 * var(--u)));
    padding-top: 12px;
    font-size: 16px;          /* under 16 and iOS zooms the page on focus */
  }
  .contact-submit {
    width: 100%; height: auto; min-height: 48px; padding: 12px 16px;
    margin-top: calc(26.2 * var(--u));
    font-size: 16px;
  }

  /* ---------- WORK LIST (Innovation & Design) ----------
     Measured off three phone captures. Mobile is NOT just the desktop grid
     in one column — the tile gets taller and the CTA changes shape:
       heading ink  135.0, x 24.0, 29px
       grid top     289.3
       tile         440 x 470.3, 4.4 apart   (desktop ar 1174/836 does NOT
                                              hold here; the crop is taller)
       band         bottom 157.7 of the tile = 33.53%
       title ink    band +21.3, x 23.3, 18px
       sub ink      band +46.3, x 22.3, 15px
       CTA          band +105.0, a DOTTED PILL x 20.3 -> 419.3, 25.6 tall,
                    text right-aligned — not the bare text desktop uses   */
  /* .worklist--ic and .wgrid--ic .wtile__band are desktop-only modifiers.
     The band one is TWO classes, so it outranks a bare .wtile__band here and
     would keep its desktop vw padding on a phone — both are restated. */
  .worklist,
  .worklist--ic { padding: calc(41.7 * var(--u)) 0 calc(125.1 * var(--u)) calc(24 * var(--u)); }
  .worklist__head { font-size: calc(29 * var(--u)); line-height: 1.284; }
  .wgrid { grid-template-columns: 1fr; gap: calc(4.4 * var(--u)); }
  .wtile { aspect-ratio: 440 / 470.3; }
  .wtile__band,
  .wgrid--ic .wtile__band {
    height: 33.53%;
    padding: calc(16.8 * var(--u)) 0 0 calc(23.3 * var(--u));
  }
  .wtile__title { font-size: calc(18 * var(--u)); }
  .wtile__sub { font-size: calc(15 * var(--u)); margin-top: calc(7.8 * var(--u)); }
  .wtile__cta,
  /* .wgrid--ic .wtile__cta is two classes, so without restating it here the
     desktop right: 1.5601vw wins and the pill sits 6.9 from the right edge
     against 20.3 on the left. Same trap as the width: 46.27% leak. */
  .wgrid--ic .wtile__cta {
    left: calc(20.3 * var(--u)); right: calc(20.7 * var(--u)); width: auto;
    top: calc(105 * var(--u));
    height: calc(33 * var(--u));                 /* home tile is 33 here */
    display: flex; align-items: center; justify-content: flex-end;
    padding: 0 calc(13 * var(--u)) calc(1 * var(--u));
    /* Was a dotted outline. Now the same solid pill the home-page tile uses
       at this width — fill rgba(24,25,26,.3), no border — so the two grids
       look like one system on a phone as well as on desktop. */
    border: 0;
    background: rgba(24, 25, 26, 0.3);
    border-radius: 100px;
    font-size: calc(17 * var(--u));
    color: #EEEDED;
    transition: background-color var(--t-fast) ease, color var(--t-fast) ease;
  }
  /* No hover on a phone, so the same green arrives on press instead. */
  .wtile:active .wtile__cta {
    background: var(--sh-green); color: var(--sh-ink);
  }
  .wtile:hover .wtile__media { transform: none; }

  /* ---------- PROJECT PAGE ----------
     Mobile is ONE column and the media moves BELOW the text — not the
     desktop grid narrowed. Measured (page y at 440):
       title ink    158.3, right-aligned to x 431, 36px
       rule         199.3   subtitle between   rule 284.7
       credits ink  347.3 / 376.0 / 403.3 (pitch 28), rule under each
       body         480.7, x 44, 28px / 31 line, paragraph gap +31
       prev / next  two pills 179.7 wide, 36.7 apart, 28 tall
       gallery      x 9.7 -> 430.3, 12 between, each shot its own height  */
  /* Grid padding moves onto the text column so the gallery can run edge to
     edge. Text x positions are unchanged — the 9.7 just lives elsewhere. */
  /* Prev/Next go LAST on a phone, under the gallery, the way they end up on
     desktop once the sticky column releases. They sit inside .proj__sticky in
     the markup, which desktop needs, so the two wrappers are dissolved with
     display:contents and the five real blocks become flex items that can be
     ordered. Their horizontal insets move onto the blocks themselves, since
     there is no .proj__text box left to carry padding. */
  .proj__grid {
    display: flex; flex-direction: column;
    gap: 0; padding: 0;
  }
  .proj__text, .proj__sticky { display: contents; }
  .proj__media { order: 1; }
  .proj__nav   { order: 2; }
  .proj__title {
    font-size: calc(36 * var(--u));
    margin: calc(73 * var(--u)) calc(9.7 * var(--u)) 0;
    padding-right: calc(0.7 * var(--u));
  }
  .proj__sub {
    font-size: calc(36 * var(--u));
    padding: calc(12 * var(--u)) calc(0.7 * var(--u)) calc(12 * var(--u)) 0;
    border-top: 1px solid var(--sh-grey-mid);
    border-bottom: 1px solid var(--sh-grey-mid);
    margin: calc(7.6 * var(--u)) calc(9.7 * var(--u)) 0;
  }
  .proj__credits { margin: calc(53 * var(--u)) calc(9.7 * var(--u)) 0; }
  .proj__credit { font-size: 16px; line-height: 19.8px; padding-bottom: 7px; }
  .proj__credit + .proj__credit { margin-top: 7px; }
  .proj__body {
    /* Even gutters either side, inset from the credit rules so the copy block
       reads as a column rather than running to the same edge as the rules. */
    margin: calc(48 * var(--u)) calc(23.7 * var(--u)) calc(56 * var(--u));
    max-width: none;
  }
  /* smaller than live (28/31) — asked for, same readability call as desktop */
  .proj__body p {
    font-size: calc(19.5 * var(--u));
    line-height: calc(25.5 * var(--u));
  }
  .proj__body p:not(:last-child) { margin-bottom: calc(22 * var(--u)); }
  .proj__nav {
    gap: calc(36.7 * var(--u));
    /* 21.3 either side = the old 9.7 text inset + 11.6 of its own */
    margin: calc(56 * var(--u)) calc(21.3 * var(--u)) calc(96 * var(--u));
    padding: 0;
  }
  .proj__btn {
    /* 28u is 22.9 on a 360 phone, under the 24 tap minimum; the floor
       never bites at 440, where 28u is 28 */
    height: calc(28 * var(--u)); min-height: 24px;
    padding: 0 calc(14 * var(--u));
    font-size: calc(15 * var(--u));
  }
  /* one column on a phone, so nothing pins */
  .proj__sticky { position: static; }
  /* Same as desktop: no top gap, black column, thin black band between shots. */
  .proj__media { padding-top: 0; background: var(--sh-black); }
  .proj__shot { margin-bottom: calc(4 * var(--u)); }
}

/* ============================================================
   TOUCH — kill sticky :hover
   iOS keeps the hover state on whatever was last tapped until the next
   tap elsewhere, so a tapped work tile stays lit up green, a submit
   button stays filled, and so on. Pointer-coarse devices get the
   resting appearance back; the states still work on anything with a
   real pointer.
   ============================================================ */
@media (hover: none) {
  .worktile:hover .worktile__btn {
    background: rgba(24, 25, 26, 0.3); color: #EEEDED;
  }
  .carousel__btn:hover {
    background: var(--sh-green); transform: translateY(-50%);
  }
  .contact-submit:hover { background: #000; color: var(--sh-white); }
  .about-cta:hover { color: var(--sh-white); }   /* touch reset */
  .about-cta:hover svg, .sh-cta:hover svg { transform: none; }
  /* :not(--pri) matters: the primary is green at rest, and an unscoped
     reset here strips it the moment a touch leaves a lingering :hover. */
  .apj__btn:not(.apj__btn--pri):hover {
    background: none; border-color: rgba(255,255,255,0.4); color: var(--sh-white);
  }
  /* A phone has no hover, so the card never lit up the way it does on a
     desktop. The press is the equivalent gesture: :active covers a touch
     anywhere on the card, :has() covers one that lands on a link, where
     Safari gives the press to the link and not its ancestors. No
     transition in -- a tap that navigates is gone before a fade lands. */
  .apj:hover { border-color: rgba(255,255,255,0.14); background: none; }
  .apj:active,
  .apj:has(.apj__btn:active) {
    border-color: var(--sh-green); background: rgba(158,255,0,0.05);
    transition: none;
  }
  .sh-footer__cols a:hover,
  .sh-footer__contact:hover { text-decoration: none; }
  /* the new ones */
  .wtile:hover .wtile__cta { background: rgba(24, 25, 26, 0.3); color: #EEEDED; }
  .wtile:hover .wtile__media { transform: none; }
  .proj__btn:hover { background: var(--sh-ink); color: var(--sh-white); }
  .sh-logo:hover { color: var(--sh-white); }
  .sh-logo:active { color: var(--sh-white); }
  .field input:hover,
  .field select:hover,
  .field textarea:hover { border-color: rgba(24, 25, 26, 0.6); }
}

/* ============================================================
   FOCUS — one ring for everything that didn't define its own.
   Only .sh-nav__link, .carousel__btn and the form fields had a
   designed focus style; the rest fell back to the browser's,
   which is clipped to a sliver on the full-bleed tiles because
   they run to the viewport edge. outline-offset is negative so
   the ring sits inside the element and stays whole.
   ============================================================ */
:is(.sh-logo, .sh-cta, .about-cta, .apj__btn, .worktile, .wtile,
    .proj__btn, .sh-burger, .contact-submit,
    .sh-mobile-menu a):focus-visible {
  outline: 2px solid var(--sh-green);
  outline-offset: -4px;
}
.sh-footer__cols a:focus-visible,
.sh-footer__contact:focus-visible {
  outline: 2px solid var(--sh-ink);
  outline-offset: 2px;
}

/* ============================================================
   SKIP LINK — four to five tab stops came before <main> on every
   page, and the <main> landmark only helps screen readers.
   ============================================================ */
.sh-skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
}
.sh-skip:focus {
  left: 16px; top: 16px; padding: 10px 18px;
  background: var(--sh-green); color: #000;
  border-radius: 100px; font-size: 15px; font-weight: 500;
  text-decoration: none;
}

/* ============================================================
   PHONE — pills rest in transparent black, green on tap.
   Green at rest was too loud against the artwork. The :hover
   selectors are restated at the same specificity so a stale
   hover on a touch device cannot light them up, and :active is
   declared last so the tap still reads.
   ============================================================ */
@media (max-width: 750px) {
  .wtile__cta,
  .wtile:hover .wtile__cta,
  .worktile__btn,
  .worktile:hover .worktile__btn {
    /* Same dark pill the desktop uses at rest. A solid green bar across a
       photograph shouts, and phone and desktop disagreeing on it was the
       bigger problem — one button, one look. */
    background: rgba(24, 25, 26, 0.3);
    border: 1px solid rgba(238, 237, 237, 0.38);
    color: #EEEDED;
  }
  /* Pressed: a solid darker green. Still opaque — no fade, no wash. */
  .wtile:active .wtile__cta,
  .worktile:active .worktile__btn,
  /* and on the pill itself, not only via the <a> around it. These two
     depended on the ANCESTOR taking :active, which a touch does not
     reliably give you -- the anchor is what gets tapped, but the pill is
     what has to change, and on a held touch the ancestor form never
     applied. Both forms are listed so it does not matter which one the
     browser decides to activate. */
  .wtile__cta:active,
  .worktile__btn:active,
  /* moved here from the earlier phone block: a tap on iOS fires :hover AND
     :active, and the sticky-hover reset above has the same specificity, so
     declared earlier it lost every time and the pill stayed dark. */
  .proj__btn:active {
    /* Was "pressed just deepens -- no colour flash", which left Previous and
       Next as the only buttons on the site that did not answer a tap in
       green. One press colour everywhere beats a per-button judgement:
       Sithum pointed at these alongside the work tiles and the venture
       cards as the same complaint. It only shows while the finger is down,
       so it cannot latch the way a hover does. */
    background: var(--sh-green);
    border-color: var(--sh-green);
    color: var(--sh-ink);
  }
  /* Press feedback for the buttons that have a hover on desktop. Declared
     here, after the sticky-hover reset, or the reset wins on every tap. */
  .sh-cta:active,
  /* The colour is right -- it just never arrives. A 0.25s fade means a tap
     that navigates in under 150ms only ever shows the pale middle of the
     transition, around rgb(189,255,81), and the press reads as "not quite
     our green". No transition on the way in: full strength immediately,
     and it still fades back out. */
  .about-cta:active, .sh-cta:active { color: var(--sh-green); transition: none; }
  .apj__btn:active,
  .contact-submit:active {
    background: var(--sh-green); border-color: var(--sh-green); color: var(--sh-ink);
  }
  /* already green, so the press needs a different shift to register */
  .apj__btn--pri:active { background: var(--sh-green-lit); border-color: var(--sh-green-lit); }
  .carousel__btn:active { background: var(--sh-green-lit); }
  .sh-burger:active { opacity: 0.55; }
}

/* ============================================================
   TAP HIGHLIGHT — iOS and Android paint a translucent grey box
   over whatever you touch, which dims the whole control and
   turned the green buttons olive. The property inherits, so one
   declaration on the root covers every element on every page,
   including any added later. Feedback comes from the :active
   states instead, which are the colours we chose.
   ============================================================ */
html { -webkit-tap-highlight-color: transparent; }

/* ============================================================
   SCROLL SMOOTHNESS
   Every layer the parallax moves is transformed on each frame.
   Without a compositing hint the browser repaints it instead of
   just moving an existing texture, which on a full-bleed film is
   a lot of pixels per frame and reads as stutter. will-change
   promotes them once, up front. `contain: paint` tells the
   browser nothing inside a frame can paint outside it, so it can
   skip work for the parts that are off screen.
   Kept to the handful of layers that actually move — promoting
   everything costs memory and makes things worse.
   ============================================================ */
.px,
.parallax-frame__media,
[data-px],
[data-px-pin],
[data-px-soft],
.wtile__media,
.worktile img {
  will-change: transform;
  backface-visibility: hidden;
}
.filmstrip,
.parallax-frame,
.wtile,
.worktile,
.proj__media {
  contain: paint;
}
/* A playing film inside a transformed layer is the most expensive thing on
   the page; keep it on its own compositor layer. */
.filmstrip video,
.filmstrip__video {
  transform: translateZ(0);
}

@media (prefers-reduced-motion: reduce) {
  .px, .parallax-frame__media, [data-px], [data-px-pin], [data-px-soft],
  .wtile__media, .worktile img { will-change: auto; }
}

/* ============================================================
   751-1000px had no rules of its own. The home tile is full
   width and ~1:1 there, so a centred body put the label 305-425
   above its own button; the heading fell to 14.7px, smaller than
   its own button text; and Contact Us hit 19.4px, its smallest
   anywhere on the site. The header also used a 5% gutter while
   everything below it used 24px.
   ============================================================ */
@media (min-width: 751px) and (max-width: 1000px) {
  :root { --sh-gutter: 24px; }
  /* 80 = the 55.9 the button block occupies + the same 24 gap the phone
     layout uses, so the label sits the same distance above its button. */
  .worktile__body { justify-content: flex-end; padding-bottom: 80px; }
  .worktile h3 { font-size: max(19px, 1.953vw); }
  .sh-footer__contact { font-size: max(24px, 2.578vw); }
}

/* Captions sit straight on photographs in two places. A scrim would change
   the artwork; a shadow only shows where the photo is light. */
.band__word, .band__note .n1, .band__note .n2 {
  text-shadow: 0 1px 14px rgba(0, 0, 0, 0.55);
}
/* #EEEDED on the 0.6 scrim bottoms out at 3.73:1 over a bright photo; white
   takes it to 4.62 and matches the title above it. */
.wtile__sub { color: var(--sh-white); }

/* line-height:1 on 36px display type made the two lines of a wrapped subtitle
   overlap by 10px wherever it ran long (7 of the 16 project pages). */
@media (max-width: 750px) {
  .proj__sub, .proj__title { line-height: 1.08; text-wrap: balance; }
}

/* unbounded vw: 33px at 1920 and 44px at 2560, beside credits fixed at 18px */
.proj__body p { font-size: clamp(17px, 1.72vw, 22px); }

/* ============================================================
   STUDIO — Pinterest-style feed of our own AI work.

   The masonry is CSS multi-column, not a JS layout: the browser
   fills the columns itself, so there is nothing to measure, and
   nothing re-positions on scroll or resize. Tiles must not be
   allowed to split across a column break, which is what
   break-inside does.
   ============================================================ */
.studio { background: var(--sh-black); color: var(--sh-white); }

.studio__top {
  padding: 7.5vw var(--sh-edge) 3.2vw;
  max-width: 54ch;
}
.studio__top h1 {
  font-size: max(34px, 4.8438vw); font-weight: 400; line-height: 1;
  letter-spacing: -0.03em; margin: 0;
}.studio__count {
  font-size: 13px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--sh-grey); margin: 1.6em 0 0;
}.studio__grid {
  padding: 0 var(--sh-edge) 8vw;
  column-count: 4;
  column-gap: 14px;
}
.studio__grid.is-ready { column-fill: balance; }
/* with JavaScript the grid becomes real columns that js/studio.js fills
   shortest-first, so the running order reads across the top instead of
   down column one. The column-count above stays as the no-JS fallback. */
.studio__grid.is-flex {
  column-count: auto;
  display: flex; align-items: flex-start; gap: 14px;
}
.studio__col { flex: 1 1 0; min-width: 0; }

/* ---- tile ---- */
/* The tile is a wrapper, not a button: the dots underneath are controls of
   their own and a button cannot contain a button. The picture is the
   button; the dots sit outside it. */
.stu {
  display: block; width: 100%;
  break-inside: avoid;              /* never split a tile across columns */
  margin: 0 0 14px;
}
/* The title is the tile's real control now -- the media is a scroll
   container, and a scroll container inside a <button> turns every drag
   into a press. A keyboard reaches the series through a control that
   says what it opens. */
.stu__open {
  display: block; width: 100%;
  margin: 0; padding: 0; border: 0; background: none; cursor: pointer;
  text-align: left; color: inherit; font: inherit;
  -webkit-appearance: none; appearance: none;
}
.stu__box {
  display: block; position: relative; overflow: hidden;
  border-radius: 10px; background: #121314;
  cursor: pointer;
  /* aspect-ratio is set per tile from the JSON, so the column height is
     reserved before the media arrives and nothing jumps while loading */
}
/* The series, as a strip the finger drags.
   Snapping, momentum and rubber-banding are the browser's here, which is
   why this feels like a phone's photo viewer and the old src-swap never
   could: that moved nothing until the finger had already left the glass.
   scroll-snap-stop keeps a hard fling to one frame, so a 15-shot series
   cannot be skipped past in one throw. */
.stu__track {
  display: flex; height: 100%;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.stu__track::-webkit-scrollbar { display: none; }
.stu__slide {
  flex: 0 0 100%; height: 100%;
  scroll-snap-align: center;
  scroll-snap-stop: always;
}
.stu__media {
  display: block; width: 100%; height: 100%;
  object-fit: cover;
  transition: transform var(--t-slow) cubic-bezier(0.22, 1, 0.36, 1);
}
.stu:hover .stu__media { transform: scale(1.035); }

.stu__badge {
  position: absolute; top: 10px; right: 10px;
  width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(0, 0, 0, 0.55); color: var(--sh-white);
  backdrop-filter: blur(4px);
}
/* ---- series dots ---- */
/* ---- one spacing scale for the tile, on both breakpoints ----
   These were set independently per breakpoint and had drifted apart:
   desktop ran 3px above the dots and 8px between them, the phone ran
   2px above and a gap of 0 patched with negative margins. Measured, the
   dot row was sitting flush against the bottom of the picture on both,
   while twelve dots spread so wide they read as a separate element
   rather than as this tile's row.

   Now: 9px of air under the picture, 5px between dots, 7px down to the
   title. Fixed pixels, not var(--u), so a phone and a desktop are
   spaced identically rather than nearly so -- which is the point. */
.stu__dots {
  display: flex; align-items: center; gap: 6px;
  /* no left inset: the phone's negative margin puts its first dot flush
     with the picture's left edge, so the desktop does the same rather
     than sitting 2px in. */
  padding: 9px 0 0;
  /* the row must never push a tile wider than its column; with 15 shots at
     the phone's 2-up width the dots wrap rather than overflow */
  flex-wrap: wrap;
}
/* A 4px dot was a 2x2 hit area on desktop and 6x8 on a phone -- effectively
   unhittable. The dot stays 4px; a transparent ::before carries the hit area.
   Vertically it takes the full 24px, which costs nothing because nothing sits
   above or below it. Horizontally it can only take the pitch (4px dot + the
   gap) before neighbouring dots start stealing each other's taps, so the gap
   goes up on a phone to buy room. Short of making the row wider than the tile
   it sits under, this is the limit -- and the dots are an enhancement: the
   tile itself opens the series, and the lightbox has full-size arrows. */
.stu__dot {
  position: relative;
  width: 4px; height: 4px; padding: 0; border: 0; border-radius: 50%;
  background-color: rgba(238, 237, 237, 0.22);
  background-clip: padding-box;
  cursor: pointer; -webkit-appearance: none; appearance: none;
  transition: background-color var(--t-fast) ease, transform var(--t-fast) ease;
}
.stu__dot::before {
  content: ""; position: absolute;
  top: -11px; bottom: -11px; left: -4px; right: -4px;
}
.stu__dot:hover { background-color: rgba(238, 237, 237, 0.65); }
.stu__dot.is-on { background-color: var(--sh-green); }
.stu__dot:focus-visible { outline: 2px solid var(--sh-green); outline-offset: 2px; }

.stu__cap { display: block; padding: 7px 2px 0; }
.stu__title {
  display: block; font-size: 15px; line-height: 1.25; letter-spacing: -0.02em;
  color: var(--sh-white);
}
.stu__tools,
.stu__n {
  display: block; margin-top: 3px;
  font-size: 12px; line-height: 1.3; color: var(--sh-grey);
}
.stu__n { color: rgba(238, 237, 237, 0.62); }   /* 0.45 measured 3.4:1 on black */
.stu:hover .stu__title { color: var(--sh-green); }
.stu__open:focus-visible { outline: 2px solid var(--sh-green); outline-offset: 3px; border-radius: 4px; }

/* ---- lightbox ---- */
/* overflow:hidden alone does not hold on iOS. If the page is still coasting
   from a flick when a piece is tapped, Safari keeps scrolling the grid
   underneath and the fixed panel lags behind it -- which is the previous
   tile showing above the picture on the first open, then settling. Pinning
   the body stops the scroll dead; studio.js restores the position on close. */
body.lb-open {
  position: fixed; left: 0; right: 0; width: 100%;
  overflow: hidden;
}
/* display:grid here beats the browser's own [hidden]{display:none}, which
   left the closed lightbox covering the page and swallowing every click on
   the grid behind it. The attribute selector has to win explicitly. */
/* 100dvh, not the implied 100vh of inset:0. On iOS a fixed element is laid
   out against the LARGE viewport -- the one with Safari's URL bar hidden --
   so with the bar showing, the top ~50px of this panel sits underneath it.
   That is the cut-off top of the film, and the Close pill sitting half
   behind the address field. dvh is the viewport that is actually visible. */
.lb {
  position: fixed; inset: 0; z-index: 300;
  height: 100vh; height: 100dvh;
  display: grid; place-items: center;
}
.lb[hidden] { display: none; }
/* Was 0.92. The eight per cent let the site header through, so About /
   Projects / Contact sat behind the Close button looking like live nav
   that could not be clicked -- the lightbox is above them, so every click
   landed on the scrim. Opaque: while a piece is open, it is the only
   thing on screen. */
.lb__scrim { position: absolute; inset: 0; background: #000; }
/* The same header both ways, so a desktop and a phone show the same
   piece with the same information: who made it, and what kind of work
   it is. The desktop keeps its two columns -- that is a use of the room
   it has, not a different set of facts. */
.lb__who {
  display: flex; align-items: center; gap: 10px; margin-bottom: 16px;
}
.lb__avatar {
  width: 34px; height: 34px; border-radius: 50%;
  flex: 0 0 auto; display: block; background: #000;
}
.lb__whotext { min-width: 0; }
.lb__whoname {
  display: block; font-size: 15px; font-weight: 500; line-height: 1.15;
  letter-spacing: -0.01em; color: var(--sh-white);
}
.lb__whocat {
  display: block; font-size: 12px; line-height: 1.15; margin-top: 1px;
  text-transform: uppercase; letter-spacing: 0.08em;
  color: rgba(255, 255, 255, 0.55);
}
/* the category lives in the header now, on both */
.lb__meta { display: none; }

/* an arrow with nowhere to go */
.lb__nav[disabled] { opacity: 0.25; cursor: default; pointer-events: none; }
.lb__panel {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: minmax(0, 1fr) 330px;
  gap: 28px; align-items: start;
  width: min(1180px, 92vw); max-height: 88vh;
  padding: 22px; box-sizing: border-box;
}
/* Every piece opens into the same 4:5 frame. Letting each image set its own
   size made the lightbox jump between a tall portrait and a short landscape
   on every arrow press, and the small ones looked lost. The frame is fixed,
   the picture is contained inside it, nothing is cropped. */
.lb__media {
  touch-action: pan-y;
  display: grid; place-items: center;
  width: 100%; max-height: 84vh;
  min-width: 0; overflow: hidden;
  border-radius: 8px;
  /* pure black, not the near-black that read as a warm grey box behind a
     letterboxed film */
  background: #000;
}
/* On a desktop the picture sets the size of the frame, not the other way
   round — a square piece in a forced 4:5 box just reads as a mistake. */
.lb__media img, .lb__media video {
  display: block; max-width: 100%; max-height: 84vh;
  width: auto; height: auto;
  object-fit: contain; background: transparent;
  /* on the picture itself, not only the frame — a letterboxed film would
     otherwise still square off inside a rounded frame */
  border-radius: 10px;
}
/* media and its dots share one grid cell, so the two-column panel stays
   two columns and the dots stay attached to the picture */
/* the frame hugs the picture instead of stretching to whatever height the
   write-up column happens to be, which left a tall empty box around a
   16:9 film */
.lb__col { position: relative; min-width: 0; display: grid;
           align-content: center; align-self: start; }
.lb__dots {
  display: flex; flex-wrap: wrap; gap: 6px; justify-content: center;
  margin: 14px 0 0;
}
.lb__dot {
  width: 5px; height: 5px; padding: 0; border: 0; border-radius: 50%;
  background-color: rgba(238, 237, 237, 0.25); cursor: pointer;
  -webkit-appearance: none; appearance: none;
  transition: background-color var(--t-fast) ease, transform var(--t-fast) ease;
}
.lb__dot:hover { background-color: rgba(238, 237, 237, 0.7); }
.lb__dot.is-on { background-color: var(--sh-green); }
.lb__dot:focus-visible { outline: 2px solid var(--sh-green); outline-offset: 3px; }
.lb__dots[hidden] { display: none; }

.lb__info { color: var(--sh-white); min-width: 0; }
.lb__count {
  font-size: 12px; letter-spacing: 0.1em; color: var(--sh-grey);
  margin: 0 0 14px; font-variant-numeric: tabular-nums;
}
.lb__title {
  font-size: 28px; font-weight: 400; line-height: 1.1; letter-spacing: -0.03em;
  margin: 0;
}
.lb__meta {
  font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--sh-grey); margin: 10px 0 0;
}
.lb__intro {
  font-size: 15px; line-height: 1.55; color: var(--sh-bone); margin: 16px 0 0;
}
.lb__tools { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 18px; }
.lb__tool {
  font-size: 12px; line-height: 1; padding: 7px 11px; border-radius: 100px;
  border: 1px solid rgba(255, 255, 255, 0.28); color: var(--sh-white);
}
.lb__plabel {
  font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--sh-grey); margin: 22px 0 6px;
}
.lb__prompt {
  font-family: var(--font-serif); font-style: italic;
  font-size: 15px; line-height: 1.5; color: var(--sh-bone); margin: 0;
  padding-left: 12px; border-left: 1px solid rgba(255, 255, 255, 0.25);
}
.lb__nav {
  position: absolute; z-index: 2;
  width: 44px; height: 44px; border-radius: 50%;
  display: grid; place-items: center;
  border: 1px solid rgba(255, 255, 255, 0.3);
  background: rgba(0, 0, 0, 0.5); color: var(--sh-white);
  cursor: pointer; transition: background-color var(--t-fast) ease,
                               color var(--t-fast) ease, border-color var(--t-fast) ease;
}
/* Inside the picture, top right — a green tab rather than a circle floating
   over the page behind it. */
.lb__close {
  position: absolute; z-index: 3; top: 12px; right: 12px;
  display: inline-flex; align-items: center;
  height: 30px; padding: 0 15px 1px;
  border: 0; border-radius: 100px;
  background: var(--sh-green); color: var(--sh-ink);
  font-size: 13px; font-weight: 500; letter-spacing: -0.01em; line-height: 1;
  cursor: pointer; -webkit-appearance: none; appearance: none;
  transition: background-color var(--t-fast) ease;
}
.lb__close:hover,
.lb__close:active { background: var(--sh-green-lit); }
.lb__nav--prev { left: -22px; top: 50%; transform: translateY(-50%); }
/* both arrows at the panel edges — 308px parked Next in the gap between
   the film and the text, where it read as unattached to either */
.lb__nav--next { right: -22px; top: 50%; transform: translateY(-50%); }
.lb__nav:hover {
  background: var(--sh-green); border-color: var(--sh-green); color: var(--sh-ink);
}
.lb__close:focus-visible, .lb__nav:focus-visible {
  outline: 2px solid var(--sh-white); outline-offset: 3px;
}

@media (max-width: 1100px) {
  .studio__grid { column-count: 3; }
  .lb__panel { grid-template-columns: minmax(0, 1fr); gap: 18px;
               max-height: 92dvh; overflow-y: auto; }
  .lb__media, .lb__media img, .lb__media video { max-height: 56vh; }
}

@media (max-width: 750px) {
  /* 62 tiles is a lot of layer for a phone to keep painted. Off-screen tiles
     skip paint and layout entirely; the reserved height keeps the scrollbar
     and the scroll position honest while they are skipped. */
  .stu { content-visibility: auto; contain-intrinsic-size: auto 340px; }
  .studio__top { padding: calc(64 * var(--u)) calc(14 * var(--u)) calc(26 * var(--u)); }
  .studio__top h1 { font-size: calc(36 * var(--u)); }.studio__count { font-size: 11px; }
  /* two up on a phone, as asked */
  .studio__grid {
    column-count: 2;
    column-gap: calc(10 * var(--u));
    padding: 0 calc(14 * var(--u)) calc(90 * var(--u));
  }
  .studio__grid.is-flex { gap: calc(10 * var(--u)); }
  .stu { margin-bottom: calc(10 * var(--u)); }
  .stu__box { border-radius: calc(8 * var(--u)); }
  .stu__title { font-size: max(13px, calc(13.5 * var(--u))); }
  .stu__tools, .stu__n { font-size: max(11px, calc(11.5 * var(--u))); }
  .stu__cap { padding-top: 4px; }   /* + the dot's 3px border = 7px seen */
  /* a finger needs a bigger target than a 6px dot, so each dot carries a
     transparent 11px pad; the dots themselves stay the same size */
  /* 6px + the dot's own 3px transparent border = the same 9px of visible
     air the desktop has. Matching the padding instead would read as 12. */
  .stu__dots { gap: 0; padding: 6px 0 0; margin: 0 -3px; }
  .stu__dot {
    /* 4px of dot on a 10px pitch — reads as one tight row. The tile itself
       and the swipe are the big targets; a dot is the fine adjustment. */
    width: 4px; height: 4px;
    border: 3px solid transparent; background-clip: padding-box;
    box-sizing: content-box;
  }
  /* 10px pitch here, so the hit area takes all of it horizontally and the
     same 26px vertically as everywhere else. */
  .stu__dot::before { top: -11px; bottom: -11px; left: -3px; right: -3px; }
  /* no hover on a phone; the press is the feedback */
  .stu:hover .stu__media { transform: none; }
  .stu:hover .stu__title { color: var(--sh-white); }
  /* .stu__open IS the title now, so the old descendant selector matched
     nothing and the press went dead. The picture is a tap target too, and
     it lights the title the same way. */
  .stu__open:active,
  .stu__box:active + .stu__dots + .stu__cap .stu__title,
  .stu__box:active + .stu__cap .stu__title { color: var(--sh-green); transition: none; }

  .lb__panel {
    width: 100vw; max-height: 100dvh;
    /* room at the top for the close button and the phone's status bar, so
       it stops sitting on top of the picture */
    /* 58 clears the Close pill, so the picture starts under it rather
       than behind it — a film that fills the frame was losing its top. */
    /* No side padding: the picture runs edge to edge, because the whole
       point of opening a piece is to see the whole of it. The writing
       takes the gutter back for itself below. */
    padding: calc(14px + env(safe-area-inset-top, 0px)) 0
             calc(28px + env(safe-area-inset-bottom, 0px));
    align-content: start;
    /* the panel is what scrolls when a piece runs past the screen, and it
       keeps the momentum a phone expects */
    overflow-y: auto; -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
  }
  /* was capped at 48vh, which is why every shot looked tiny. A floor as
     well: a 2:1 film was opening 181px tall on a 926px screen. */
  .lb__media { max-height: 66vh; }
  .lb__media img, .lb__media video {
    /* bounded by the frame on both axes — width:100% with a 66vh cap was
       letting a tall still overflow and get clipped by the frame */
    /* vh, not % — a percentage max-height against a grid area whose own
       height is still being resolved does nothing, so a tall still kept
       overflowing the frame and getting clipped. */
    max-width: 100%; max-height: 66vh;
    width: auto; height: auto; object-fit: contain;
    border-radius: 8px;
  }
  /* Stills open into a tall frame so they are not a sliver on a phone.
     A film keeps its own shape — a horizontal one is 16:9 and that is right. */
  .lb__media:not(.lb__media--video) { min-height: min(62vh, 112vw); }
  /* the picture starts under the Close pill instead of centring itself in
     the cell and sliding up behind it when the piece is a wide one */
  .lb__col { align-content: start; }
  /* Full bleed, nothing cropped, and a strip the finger drags.
     This used to fill any piece between 0.70 and 0.95 with object-fit:
     cover -- which is 3:4 and 4:5, the great majority of the library --
     so most pictures were being trimmed at the very moment someone had
     asked to look at one. Now the picture takes the full width, contain
     keeps every edge of it, and the frame is a scroll container so the
     gesture belongs to the browser.

     The frame's height is fixed rather than following each picture:
     133vw is exactly a 3:4 still at full width, which is most of the
     library, and a fixed height means the page does not jump as the
     strip moves between a portrait and a film. */
  .lb__col { margin: 0; }
  .lb__media {
    border-radius: 0; min-height: 0;
    height: min(78dvh, 133vw); max-height: 78dvh;
    display: block;
    /* The base rule pins this to pan-y, left from the hand-rolled swipe
       that used to read touchend deltas. It blocks horizontal panning
       outright, so the strip could not be dragged at all. */
    touch-action: pan-x pan-y;
    overflow-x: auto; overflow-y: hidden;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
  }
  .lb__media::-webkit-scrollbar { display: none; }
  .lb__strip { display: flex; height: 100%; }
  .lb__slide {
    flex: 0 0 100%; height: 100%;
    scroll-snap-align: center; scroll-snap-stop: always;
    display: grid; place-items: center;
  }
  .lb__slide img, .lb__slide video {
    display: block;
    width: 100%; height: 100%;
    max-width: none; max-height: none;
    object-fit: contain; border-radius: 0;
  }
  .lb__dots { padding: 0 14px; }
  /* Fixed, not absolute: an absolutely positioned top is measured from the
     panel's PADDING edge, so padding the panel down to clear the pill moved
     the pill down with it. Pinned to the viewport it stays put. */
  .lb__close {
    position: fixed;
    top: calc(env(safe-area-inset-top, 0px) + 12px);
    right: 12px; height: 32px; font-size: 13.5px; z-index: 4;
  }
  /* The arrows used to float at the bottom of the screen, which landed them
     on top of the write-up. They now sit in the flow, either side of the
     dots, under the picture — where a carousel's controls belong. */
  .lb__panel { grid-template-columns: 1fr 1fr; row-gap: 0; }
  /* One edge for every line of type, and the site's own gutter rather
     than the 14px the panel used to run -- that sat too close to the
     glass once the picture went full bleed. */
  .lb__who, .lb__title, .lb__intro, .lb__tools,
  .lb__plabel, .lb__prompt { margin-left: calc(24 * var(--u)); margin-right: calc(24 * var(--u)); }
  .lb__nav--prev { margin-left: calc(24 * var(--u)); }
  .lb__nav--next { margin-right: calc(24 * var(--u)); }

  /* Instagram's shape. The studio is the account, the piece is the post:
     who made it at the top, the picture, then the writing. It used to run
     picture-first with the name underneath, so you landed on a photograph
     with no idea whose it was and found its title exactly where Safari
     parks its toolbar. display:contents dissolves the info column so its
     parts become items of the panel grid and can be ordered around the
     picture. Close is position:fixed and floats clear of all of it. */
  .lb__who {
    display: flex; align-items: center; gap: 10px;
    grid-column: 1 / -1; order: 1; margin-bottom: 14px;
  }
  .lb__avatar {
    width: 34px; height: 34px; border-radius: 50%;
    flex: 0 0 auto; display: block; background: #000;
  }
  .lb__whotext { min-width: 0; }
  .lb__whoname {
    display: block; font-size: 15px; font-weight: 500; line-height: 1.15;
    letter-spacing: -0.01em; color: var(--sh-white);
  }
  .lb__whocat {
    display: block; font-size: 12px; line-height: 1.15; margin-top: 1px;
    text-transform: uppercase; letter-spacing: 0.08em;
    color: rgba(255, 255, 255, 0.55);
  }
  .lb__count { display: none; }   /* the dots already say which frame */
  .lb__meta  { display: none; }   /* the category moved into the header */

  .lb__info { display: contents; }
  .lb__title, .lb__intro, .lb__tools,
  .lb__plabel, .lb__prompt { grid-column: 1 / -1; }
  .lb__col    { grid-column: 1 / -1; order: 2; }
  /* margin-top/bottom only -- the shorthand would reset the gutter set
     above and drop the title back against the glass. */
  .lb__title  { order: 4; font-size: 19px;
                margin-top: 16px; margin-bottom: 4px; }
  .lb__intro  { order: 5; margin-top: 0; }
  .lb__tools  { order: 6; }
  .lb__plabel { order: 7; }
  .lb__prompt { order: 8; padding-bottom: 20px; }
  /* No arrows on a phone. The media already takes a swipe, and a swipe
     does exactly what they did -- step through every shot of every piece,
     carrying on into the next one past the last. Two circles taking up a
     row to repeat a gesture the thumb already knows. They stay on a
     desktop, where there is no swipe to fall back on. */
  .lb__nav { display: none; }
  .lb__nav--prev { grid-column: 1; justify-self: start; }
  .lb__nav--next { grid-column: 2; justify-self: end; }
  /* the dots belong to the picture, so they come up to meet it, and they
     match the ones under a tile rather than being their own size */
  .lb__dots { margin-top: 14px; gap: 8px; }
  .lb__dot { width: 4px; height: 4px; }
  .lb__title { font-size: calc(24 * var(--u)); }
}

@media (max-width: 750px) {
  /* will-change on this many elements costs more memory than the parallax
     it was buying — and there is no parallax on a phone. */
  .px, .parallax-frame__media, [data-px], [data-px-pin], [data-px-soft],
  .wtile__media, .worktile img { will-change: auto; }
}

@media (prefers-reduced-motion: reduce) {
  .stu__media { transition: none; }
  .stu:hover .stu__media { transform: none; }
}@media (max-width: 750px) {}

/* ============================================================
   LATENT — the studio's writing. Black, like Projects, so the
   section reads as part of the work rather than a company blog.
   Set as a numbered index: a ruled list scales to fifty pieces,
   a card grid does not.
   ============================================================ */
.lat { background: var(--sh-black); color: var(--sh-white); }

.lat-top {
  position: relative; overflow: hidden;
  padding: 8.6vw var(--sh-edge) 5vw;
}
/* the word sitting behind the headline, half-faded — the thing that is
   there but not yet visible, which is what latent means */
.lat-eyebrow {
  position: relative;
  display: flex; align-items: center; gap: 0.75em;
  font-size: max(11px, 0.84vw); letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--sh-grey); margin: 0 0 1.8vw;
}
/* Three dots rather than one, bobbing: the same mark the Read buttons use,
   so the section opens with the gesture it ends every piece on. */
.lat-dot { display: flex; gap: 4px; flex: none; }
.lat-dot i {
  display: block; width: 6px; height: 6px; border-radius: 50%;
  background: var(--sh-green);
  animation: sh-bob 1.25s ease-in-out infinite;
}
.lat-dot i:nth-child(2) { animation-delay: 0.16s; }
.lat-dot i:nth-child(3) { animation-delay: 0.32s; }
@media (prefers-reduced-motion: reduce) {
  .lat-dot i { animation: none; opacity: 1; }
}
.lat-head {
  position: relative;
  font-size: 4.844vw; font-weight: 400; line-height: 1;   /* house headline size */
  letter-spacing: -0.05em; margin: 0; max-width: 15ch;
}
.lat-head em {
  font-style: normal;
  font-weight: inherit;
  color: inherit;
}
.lat-lede {
  position: relative;
  font-size: clamp(15px, 1.22vw, 19px); line-height: 1.6;
  color: var(--sh-grey); margin: 2.4vw 0 0; max-width: 52ch;
}

.lat-list { padding: 0 var(--sh-edge) 9vw; }
.lat-item { border-top: 1px solid rgba(238, 237, 237, 0.16); }
.lat-item:last-child { border-bottom: 1px solid rgba(238, 237, 237, 0.16); }

.lat-link {
  display: grid; grid-template-columns: 7ch minmax(0, 1fr) auto;
  gap: 3vw; align-items: start;
  padding: 2.8vw 1.2vw;
  margin: 0 -1.2vw;
  color: inherit;
  transition: background-color var(--t-fast) ease;
}
.lat-num {
  font-size: max(12px, 0.9vw); letter-spacing: 0.12em;
  color: var(--sh-green); padding-top: 0.5em;
  font-variant-numeric: tabular-nums;
}
.lat-meta {
  display: flex; align-items: center; gap: 0.7em;
  font-size: max(11px, 0.8vw); letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--sh-grey); margin-bottom: 1.1vw;
}
.lat-sep { color: rgba(238, 237, 237, 0.3); }
.lat-title {
  font-size: clamp(23px, 2.6vw, 44px); font-weight: 400; line-height: 1.08;
  letter-spacing: -0.03em; margin: 0; max-width: 24ch;
  transition: color var(--t-fast) ease;
}
.lat-sum {
  font-size: max(14px, 1vw); line-height: 1.55;
  color: var(--sh-grey); margin: 1.1vw 0 0; max-width: 56ch;
}
/* The same button as the end of an article, not a circle: one shape doing
   this job across the section. */
.lat-go {
  display: inline-flex; align-items: center; gap: 0.5em;
  height: 31.9px; padding: 0 16px 1px; border-radius: 100px;
  background: var(--sh-green); color: var(--sh-ink);
  font-size: var(--fs-btn); line-height: 1; letter-spacing: -0.03em;
  justify-self: end; margin-top: 0.2em;
  transition: background-color var(--t-fast) ease, transform var(--t-fast) ease;
}
/* No arrow. The disc holds three dots and nothing else: at rest they sit
   still inside a hairline circle, on hover it fills green and they bob.
   Twenty green discs sitting lit down the page would shout; this way the
   colour marks the row you are actually on. */
.lat-go__dots { display: flex; gap: 4px; }
.lat-go__dots i {
  display: block; width: 5px; height: 5px; border-radius: 50%;
  background: currentColor;
  animation: sh-bob 1.25s ease-in-out infinite;
}
.lat-go__dots i:nth-child(2) { animation-delay: 0.16s; }
.lat-go__dots i:nth-child(3) { animation-delay: 0.32s; }
.lat-link:hover { background: rgba(238, 237, 237, 0.045); }
.lat-link:hover .lat-title { color: var(--sh-green); }
.lat-link:hover .lat-go { background: var(--sh-green-lit); transform: translateX(4px); }
@media (prefers-reduced-motion: reduce) {
  .lat-go__dots i { animation: none; opacity: 1; }
}
.lat-link:focus-visible { outline: 2px solid var(--sh-green); outline-offset: -4px; }

/* A piece that is not written yet is not a link. It keeps the row's layout but
   drops the affordances, so nothing invites a click that goes nowhere. */
.lat-link--soon { cursor: default; }
.lat-link--soon:hover { background: none; }
.lat-link--soon:hover .lat-title { color: var(--sh-white); }
.lat-link--soon:hover .lat-go { transform: none; background: var(--sh-green); }
/* An unwritten piece is not loading anything, so its dots stay still. */
.lat-link--soon .lat-go__dots i { animation: none; }
.lat-link--soon .lat-title,
.lat-link--soon .lat-go { opacity: .62; }

@media (max-width: 1100px) {
  .lat-link { grid-template-columns: 5ch minmax(0, 1fr) 44px; gap: 4vw; padding: 34px 0; margin: 0; }
  .lat-title { max-width: none; }
}
@media (max-width: 750px) {
  .lat-top { padding: calc(70 * var(--u)) calc(24 * var(--u)) calc(38 * var(--u)); }
  .lat-eyebrow { font-size: 11px; margin-bottom: calc(18 * var(--u)); }
  .lat-head { font-size: calc(31.5 * var(--u)); line-height: calc(36 * var(--u)); max-width: none; }
  .lat-lede { font-size: max(14px, calc(15 * var(--u))); margin-top: calc(22 * var(--u)); }
  .lat-list { padding: 0 calc(24 * var(--u)) calc(90 * var(--u)); }
  /* the arrow goes; the whole row is the tap target */
  .lat-link {
    grid-template-columns: calc(34 * var(--u)) minmax(0, 1fr);
    gap: 0; padding: calc(26 * var(--u)) 0;
  }
  .lat-go { display: none; }
  .lat-meta { font-size: 10.5px; margin-bottom: calc(10 * var(--u)); }
  .lat-title { font-size: calc(24 * var(--u)); }
  .lat-sum { font-size: max(13px, calc(14 * var(--u))); margin-top: calc(10 * var(--u)); }
  .lat-link:hover { background: none; }
  .lat-link:hover .lat-title { color: var(--sh-white); }
  .lat-link:active .lat-title { color: var(--sh-green); }
}/* Articles run dark like the Latent index. Without this the page inherits
   .page-light's white main, and the article's light body copy lands on it
   invisibly. The rule is on .sh-main because that is what .page-light
   paints — setting it on .art alone loses to the more specific selector. */
.sh-main.art {
  max-width: none;
  background: var(--sh-black);
  color: var(--sh-white);
}
.art > article { max-width: 1600px; margin: 0 auto; }
.art-head { padding: 5.2vw var(--sh-edge) 3.4vw; max-width: 72ch; }
.art-back {
  display: inline-flex; align-items: center; gap: 0.6em;
  font-size: max(11px, 0.82vw); letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--sh-grey); margin-bottom: 3.4vw;
  transition: color var(--t-fast) ease;
}
/* 14px tall on its own. Padding rather than height so the label does not
   move: the extra is split above and below the text. */
.art-back { min-height: 24px; padding: 5px 0; }
.art-back svg { width: 18px; }
.art-back:hover { color: var(--sh-green); }
.art-meta {
  display: flex; align-items: center; flex-wrap: wrap; gap: 0.7em;
  font-size: max(11px, 0.8vw); letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--sh-grey); margin-bottom: 1.6vw;
}
.art-title {
  font-size: clamp(30px, 4.2vw, 68px); font-weight: 400; line-height: 1.02;
  letter-spacing: -0.035em; margin: 0;
}
.art-stand {
  font-size: clamp(17px, 1.5vw, 25px); line-height: 1.45;
  color: var(--sh-grey); margin: 1.8vw 0 0;
}

.art-body {
  padding: 0 var(--sh-edge) 6vw;
  max-width: 68ch;
  font-size: clamp(16px, 1.18vw, 19px); line-height: 1.68;
  color: var(--sh-bone);
}
.art-body > * + * { margin-top: 1.5em; }
.art-first { color: var(--sh-white); font-size: 1.1em; line-height: 1.55; }
/* Section heads carry a faint numeral and a hairline, so a long piece reads
   as chapters a reader can place themselves in rather than one scroll. */
.art-body { counter-reset: art-sec; }
.art-body h2 {
  counter-increment: art-sec;
  display: grid; grid-template-columns: auto 1fr; align-items: end; gap: 0 0.6em;
  border-bottom: 1px solid rgba(238, 237, 237, 0.16); padding-bottom: 0.45em;
  font-size: clamp(21px, 1.9vw, 30px); font-weight: 400; line-height: 1.15;
  letter-spacing: -0.025em; color: var(--sh-white);
  margin-top: 2.9em;
}
.art-body h2::before {
  content: counter(art-sec, decimal-leading-zero);
  font-size: 1.6em; line-height: 0.78; letter-spacing: -0.05em;
  color: rgba(238, 237, 237, 0.26);
}
.art-body a {
  color: inherit; text-decoration: underline;
  text-decoration-color: var(--sh-green); text-underline-offset: 0.22em;
  text-decoration-thickness: 1px;
}
.art-body a:hover { color: var(--sh-green); }
.art-body ul { padding-left: 1.1em; }
.art-body li + li { margin-top: 0.45em; }

/* ---- FAQ questions on a service page ----
   h2 in an article body is a numbered section rule, which is right for an
   argument and wrong for a list of questions -- six of them would read as
   six more chapters. h3 instead, set as a question rather than a heading:
   no counter, no rule, tighter to the answer underneath it.

   build-service-page.py reads these back out of the rendered body to build
   the FAQPage structured data, so the markup and the schema cannot drift
   apart. The class is the contract: rename it and the rich results go. */
.art-body .svc-q {
  margin-top: 1.9em; margin-bottom: 0.45em;
  font-size: clamp(16px, 1.15vw, 19px); line-height: 1.3;
  letter-spacing: -0.01em; color: var(--sh-white);
}
.art-body .svc-q + p { margin-top: 0; }

@media (max-width: 750px) {
  .art-body .svc-q { font-size: calc(16 * var(--u)); margin-top: 1.6em; }
}
.art-quote {
  margin: 2.6em 0; padding-left: 1.1em;
  border-left: 2px solid var(--sh-green);
  font-size: clamp(19px, 1.75vw, 28px); line-height: 1.3;
  letter-spacing: -0.02em; color: var(--sh-white);
}

/* ---- things that break up the column ----
   A piece is twelve hundred words of even paragraphs. Without somewhere for
   the eye to land it reads as one block however good the sentences are. */

/* The figure a piece turns on, set at headline size. */
.art-fig { display: flex; align-items: baseline; gap: 0.6em; flex-wrap: wrap; margin: 2.4em 0; }
.art-fig__n {
  font-size: clamp(52px, 6vw, 104px); line-height: 0.86; letter-spacing: -0.06em;
  color: var(--sh-white);
}
.art-fig__n em { font-style: normal; color: var(--sh-green); }
.art-fig__c { flex: 1 1 14em; min-width: 12em; font-size: 0.94em; line-height: 1.5; color: var(--sh-grey-mid); }

/* ---- a contact sheet, for the one piece that is about the work itself ----
   Latent is otherwise all text, deliberately: an article that stops for a
   picture every other screen reads as a brochure. The studio piece is the
   exception, because a claim about craft that shows no craft is just a
   claim. Once, at the point the argument needs it.

   Nine squares, not three tall ones. The first version used 4:5, which
   is the library's common shape -- but the pieces are NOT all that
   shape. The cinematic stills are 700x297, so a 4:5 box cropped away
   three fifths of a widescreen frame and cut through the subject. A
   square is the most forgiving box for a set this mixed, and nine of
   them read as a contact sheet, which is what a page about sixty-two
   pieces should look like.

   object-position sits at 30% rather than centre because in almost all
   of these the subject's head is above the middle of the frame; a
   centred square crop decapitates the full-length fashion shots.

   No caption per tile and no link per tile: nine links to the same
   page is nine duplicate links for a screen reader and for Google, and
   nine captions is the clutter this page is meant to avoid. One line
   underneath does both jobs. */
.art-shots {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: calc(6 * var(--u)); margin: 2.6em 0 0;
  /* Without this the images are grid items with the default
     align-self:stretch, so every one is pulled to the height of the
     tallest in its row and aspect-ratio never gets to apply -- which
     is the stretching this grid was meant to fix. Measured: tiles came
     out 217x297 instead of 217x217. */
  align-items: start;
}
.art-shots img {
  display: block; width: 100%; height: auto; aspect-ratio: 1 / 1;
  object-fit: cover; object-position: center 30%;
  background: #111;
}
.art-shots__c {
  margin: 0.9em 0 2.6em;
  font-size: 0.85em; line-height: 1.5; color: var(--sh-grey-mid);
}
.art-shots__c a { color: var(--sh-white); text-underline-offset: 3px; }
.art-shots__c a:hover, .art-shots__c a:focus-visible { color: var(--sh-green); }

@media (max-width: 750px) {
  /* Still three across. At 430px each square is about 130px, which is
     plenty for a contact sheet and keeps the 3x3 shape -- going two-up
     would leave a widowed ninth tile. */
  .art-shots { gap: calc(5 * var(--u)); margin-top: 2em; }
  .art-shots__c { margin-bottom: 2em; }
}

/* Three or four hard numbers side by side, near the top of a piece. */
.art-strip {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(8em, 1fr));
  border-top: 1px solid rgba(238, 237, 237, 0.16);
  border-bottom: 1px solid rgba(238, 237, 237, 0.16);
  margin: 2.4em 0;
}
.art-strip > div { padding: 1.1em 1em 1.2em; border-left: 1px solid rgba(238, 237, 237, 0.16); }
.art-strip > div:first-child { border-left: 0; padding-left: 0; }
.art-strip dt {
  font-size: max(10px, 0.72em); letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--sh-grey); margin-bottom: 0.9em;
}
.art-strip dd {
  margin: 0; font-size: clamp(21px, 2vw, 30px); line-height: 1;
  letter-spacing: -0.04em; color: var(--sh-white);
}

/* Where we could not stand a claim up, said so in the open rather than
   buried mid-paragraph. */
.art-call {
  border: 1px solid rgba(238, 237, 237, 0.38); border-radius: 16px;
  padding: 1.3em 1.4em 1.4em; margin: 2.4em 0;
}
.art-call__k {
  display: flex; align-items: center; gap: 0.6em;
  font-size: max(10px, 0.72em); letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--sh-green); margin-bottom: 0.8em;
}
.art-call__k::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--sh-green); }
.art-call p { margin: 0; color: var(--sh-grey-mid); }/* Past 1200 the text column leaves most of the window black. The pull quote
   moves out into that margin and the paragraphs run past it, which is what
   the space is for. Float rather than grid: the body is a flat run of
   paragraphs, so a grid would put the quote on a row of its own and leave a
   hole in the column beside it. */
@media (min-width: 1200px) {
  .art-quote {
    float: right; width: 21em; margin: 0.3em -24em 1.4em 3em;
    font-size: clamp(21px, 1.9vw, 30px);
  }
  .art-fig { margin-right: -10em; }
}

/* Who made this, and the way out. Quiet on purpose: the Read-next block
   below already carries the one green button on the page, and two of them
   stacked would read as a pair of ads rather than an ending. */
.art-sig {
  max-width: 68ch; padding: 2.4vw var(--sh-edge) 0;
  font-size: clamp(14px, 1.05vw, 16px); line-height: 1.6; color: var(--sh-grey);
}
.art-sig p { margin: 0; }
.art-sig__go { margin-top: 0.7em; display: flex; align-items: center; gap: 0.8em; flex-wrap: wrap; }
.art-sig__go a {
  display: inline-flex; align-items: center; min-height: 24px;
  color: var(--sh-white); text-decoration: underline;
  text-decoration-color: var(--sh-green); text-underline-offset: 0.22em;
  text-decoration-thickness: 1px;
  transition: color var(--t-fast) ease;
}
.art-sig__go a:hover { color: var(--sh-green); }
.art-sig__sep { color: rgba(238, 237, 237, 0.3); }

/* Mirrors .art-body's box exactly (same max-width, same edge padding, and
   border-box above), so the rule above the block lands on the text column
   rather than running the full width of the viewport. */
.art-foot { max-width: 68ch; padding: 0 var(--sh-edge) 7vw; }
.art-next {
  display: block; padding: 2.6vw 0 0; color: inherit;
  border-top: 1px solid rgba(238, 237, 237, 0.16);
}
.art-next__label {
  display: block; font-size: max(11px, 0.8vw); letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--sh-grey); margin-bottom: 0.7em;
}
.art-next__title {
  display: block; font-size: clamp(22px, 2.4vw, 40px); line-height: 1.1;
  letter-spacing: -0.03em;
  transition: color var(--t-fast) ease;
}
/* The one green-at-rest button on the site. Everywhere else green is the
   hover state; at the end of a piece there is nothing competing with it, so
   it can carry the weight. Hover lifts to the carousel's lighter green. */
.art-next__cta {
  display: inline-flex; align-items: center; gap: 0.5em;
  margin-top: 1.3em; height: 31.9px; padding: 0 16px 1px;
  border-radius: 100px;
  background: var(--sh-green); color: var(--sh-ink);
  font-size: var(--fs-btn); line-height: 1; letter-spacing: -0.03em;
  transition: background-color var(--t-fast) ease;
}
/* The title stays white. With a green button sitting under it, turning the
   headline green too made the whole block one colour on hover. */
.art-next:hover .art-next__title { color: var(--sh-white); }
.art-next:hover .art-next__cta,
.art-next:focus-visible .art-next__cta { background: var(--sh-green-lit); }

/* No arrow, and the dots run whether or not anyone is pointing at them.
   The button reads as something already working rather than something
   waiting to be touched. */
.art-next__dots { display: flex; gap: 3px; flex: 0 0 auto; }
.art-next__dots i {
  display: block; width: 4px; height: 4px; border-radius: 50%;
  background: currentColor;
  animation: sh-bob 1.25s ease-in-out infinite;
}
.art-next__dots i:nth-child(2) { animation-delay: 0.16s; }
.art-next__dots i:nth-child(3) { animation-delay: 0.32s; }
@keyframes sh-bob {
  0%, 60%, 100% { transform: translateY(0); opacity: 0.45; }
  30%           { transform: translateY(-4px); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .art-next__dots i { animation: none; opacity: 1; }
}

@media (max-width: 750px) {
  .art-head { padding: calc(40 * var(--u)) calc(24 * var(--u)) calc(26 * var(--u)); }
  .art-back { margin-bottom: calc(28 * var(--u)); font-size: 11px; }
  .art-meta { font-size: 10.5px; margin-bottom: calc(16 * var(--u)); }
  .art-title { font-size: calc(32 * var(--u)); }
  .art-stand { font-size: calc(16 * var(--u)); margin-top: calc(18 * var(--u)); }
  /* Was 17u/1.66. In a 380px column that set about nine words to the line,
     which is wide for a phone and reads as a wall. Smaller type and tighter
     leading put it near the twelve-word line a column this narrow wants.
     The 15px floor is the smallest that stays comfortable at arm's length. */
  .art-body {
    padding: 0 calc(24 * var(--u)) calc(60 * var(--u));
    font-size: max(15px, calc(15.5 * var(--u))); line-height: 1.62;
  }
  /* The numeral sits above the heading on a phone: beside it, a heading that
     wraps to three lines leaves the number stranded against the last one. */
  .art-body h2 {
    font-size: calc(20 * var(--u));
    grid-template-columns: 1fr; gap: 0.35em 0; align-items: start;
  }
  .art-body h2::before { font-size: 1.3em; line-height: 1; }
  .art-quote { font-size: calc(19 * var(--u)); margin: 2em 0; }
  /* The figure blocks: the strip's columns do not fit a phone, so it stacks
     and the hairline moves from the left of each cell to the top. */
  .art-fig { margin: 2em 0; gap: 0.5em; }
  .art-fig__n { font-size: calc(46 * var(--u)); }
  .art-strip { grid-template-columns: 1fr; margin: 2em 0; }
  .art-strip > div {
    padding: 0.9em 0; border-left: 0;
    border-top: 1px solid rgba(238, 237, 237, 0.16);
  }
  .art-strip > div:first-child { border-top: 0; }
  .art-strip dt { margin-bottom: 0.5em; }
  .art-strip dd { font-size: calc(22 * var(--u)); }
  .art-call { padding: 1.1em 1.2em 1.2em; margin: 2em 0; }
  .art-sig { padding: calc(26 * var(--u)) calc(24 * var(--u)) 0; }
  .art-foot { padding: 0 calc(24 * var(--u)) calc(56 * var(--u)); }
  .art-next { padding: calc(26 * var(--u)) 0 0; }
  .art-next__title { font-size: calc(24 * var(--u)); }
}


/* ===== About — the studio statement =================================
   Short, large, and set against itself: Space Grotesk carries the
   structure, EB Garamond italic carries the emphasis. The one line worth
   remembering gets the whole width. */
/* ONE SIZE for the whole block, the same 4.844vw the headline above the
   image uses. Four different sizes in one short column read as a ranking
   the copy does not have -- the lead was 2.6vw, the note 1.3vw, the
   sign-off 1.7vw, and only the shout matched the page. */
.about-body .ab-sign,
.ab-shout {
  font-size: 4.844vw;
  font-weight: 400;
  line-height: 1;
  letter-spacing: -0.05em;
}.ab-shout {
  font-family: var(--font-display);
  color: var(--sh-white);
  margin: 0;
  border: 0;
  padding: 0;
  quotes: none;
}
.ab-shout em {
  font-style: normal;
  font-weight: inherit;
  color: inherit;
}.about-body .ab-sign {
  font-family: var(--font-display);
  color: var(--sh-white);
  /* Not a separate statement -- the sign-off is the last line of the
     same one, so it sits on the block's own line rhythm with neither a
     rule nor a gap between them. */
  margin: 0;
}
.about-body .ab-sign em {
  font-style: normal;
  font-weight: inherit;
  color: inherit;
}

/* ---- the pointer from the claim to the piece that evidences it ----
   Deliberately not another .about-cta: this page already has one arrow
   button ("Get started") and a second one turns a statement into a
   sales column. This borrows the Latent row instead -- small label,
   title, one line of summary, the three dots -- so it reads as a
   citation under the shout rather than a second ask.

   Kept small on purpose. The shout is the statement and this must not
   compete with it -- so the type sits near body size rather than near
   the headline, and the block is narrow enough to read as a footnote
   under the sign-off instead of a second section.

   The pill and its bobbing dots are .lat-go's, not a new invention:
   it is the same "there is a piece behind this" signal the Latent
   index uses, so it needs no learning. sh-bob is the site's existing
   keyframe and is switched off under reduced motion like the others. */
.ab-read {
  display: block; text-decoration: none; margin-top: 2.2em;
  padding-top: 1.2em; border-top: 1px solid rgba(238, 237, 237, 0.14);
  max-width: 26em;
}
.ab-read__label {
  display: block; font-size: 10px; letter-spacing: 0.15em;
  text-transform: uppercase; color: var(--sh-green);
}
.ab-read__title {
  display: block; margin-top: 0.45em;
  font-size: clamp(15px, 1.05vw, 17px); line-height: 1.3; letter-spacing: -0.01em;
  color: var(--sh-white); transition: color var(--t-fast) ease;
}
.ab-read__sum {
  display: block; margin-top: 0.3em;
  font-size: 12.5px; line-height: 1.45; color: var(--sh-grey-mid);
}
/* = .lat-go, a touch shorter */
.ab-read__go {
  display: inline-flex; align-items: center; gap: 0.5em; margin-top: 0.9em;
  height: 28px; padding: 0 14px 1px; border-radius: 100px;
  background: var(--sh-green); color: var(--sh-ink);
  font-size: 12px; line-height: 1; letter-spacing: -0.03em;
  transition: background-color var(--t-fast) ease, transform var(--t-fast) ease;
}
.ab-read__dots { display: flex; gap: 4px; }
.ab-read__dots i {
  display: block; width: 5px; height: 5px; border-radius: 50%;
  background: currentColor; animation: sh-bob 1.25s ease-in-out infinite;
}
.ab-read__dots i:nth-child(2) { animation-delay: 0.16s; }
.ab-read__dots i:nth-child(3) { animation-delay: 0.32s; }
.ab-read:hover .ab-read__title, .ab-read:focus-visible .ab-read__title { color: var(--sh-green); }
.ab-read:hover .ab-read__go, .ab-read:focus-visible .ab-read__go {
  background: var(--sh-green-lit); transform: translateX(4px);
}
.ab-read:focus-visible { outline: 2px solid var(--sh-green); outline-offset: 4px; }

@media (prefers-reduced-motion: reduce) {
  .ab-read__dots i { animation: none; opacity: 1; }
  .ab-read:hover .ab-read__go, .ab-read:focus-visible .ab-read__go { transform: none; }
}

@media (max-width: 750px) {
  .ab-read { margin-top: 1.8em; padding-top: 1em; max-width: none; }
  .ab-read__title { font-size: calc(15 * var(--u)); }
  .ab-read__sum { font-size: calc(12 * var(--u)); }
  .ab-read__go { height: calc(28 * var(--u)); font-size: calc(12 * var(--u));
                 padding: 0 calc(14 * var(--u)) 1px; }
  .ab-read__dots i { width: calc(5 * var(--u)); height: calc(5 * var(--u)); }
  /* No hover on a phone, so the press carries the same lit green. */
  .ab-read:active .ab-read__go { background: var(--sh-green-lit); }
  .ab-read:active .ab-read__title { color: var(--sh-green); }
}

@media (max-width: 750px) {
  /* = .about-headline on a phone */
  .about-body .ab-sign,
.ab-shout {
    font-size: calc(31.5 * var(--u));
    line-height: calc(36 * var(--u));
    max-width: none;
  }
}


/* ---- Menu close ----------------------------------------------------
   Open, the burger stops being two lines crossing and becomes a pill
   that says Close. Same shape language as the lightbox close; quiet
   fill so it does not shout off a black panel. */
body.sh-menu-open .sh-burger {
  width: auto;
  min-width: 0;
  height: 32px;
  padding: 0 14px 1px;
  border: 1px solid rgba(238, 237, 237, 0.42);
  border-radius: 100px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: var(--sh-white);
  font-family: var(--font-display);
  font-size: 13.5px;
  font-weight: 400;
  letter-spacing: 0.01em;
}
body.sh-menu-open .sh-burger span { display: none; }
body.sh-menu-open .sh-burger::after {
  content: "Close";
  line-height: 1;
}
body.sh-menu-open .sh-burger:active {
  opacity: 1;
  background: rgba(238, 237, 237, 0.12);
}


/* ============================================================
   THANK YOU  (/thanks)
   Where the contact form lands. Without an action the form went to
   Netlify's own white confirmation page, which drops the visitor out
   of the site at the one moment they have just committed to it.
   ============================================================ */
.page-thanks .contact-body { min-height: 52vh; }
.thanks { max-width: 46ch; }
/* The contact page is page-light, so this lands on white: ink, not white,
   and a grey dark enough to read on it rather than --sh-grey-mid, which is
   tuned for black. */
.thanks__lead {
  font-size: clamp(21px, 2.6vw, 34px); line-height: 1.15;
  letter-spacing: -0.035em; color: #000; margin: 0;
}
.thanks__note {
  margin: 1.1em 0 0; font-size: clamp(15px, 1.18vw, 18px);
  line-height: 1.6; color: #55534F;
}
.thanks__go { display: flex; align-items: center; gap: 1.6em; flex-wrap: wrap; margin-top: 2.4em; }
.thanks__btn {
  display: inline-flex; align-items: center; gap: 0.5em;
  height: 31.9px; padding: 0 16px 1px; border-radius: 100px;
  background: var(--sh-green); color: var(--sh-ink);
  font-size: var(--fs-btn); line-height: 1; letter-spacing: -0.03em;
  transition: background-color var(--t-fast) ease;
}
.thanks__btn:hover { background: var(--sh-green-lit); }
.thanks__alt {
  display: inline-flex; align-items: center; min-height: 24px;
  color: #55534F; transition: color var(--t-fast) ease;
}
.thanks__alt:hover { color: #000; }
@media (max-width: 750px) {
  .thanks__lead { font-size: calc(24 * var(--u)); }
  .thanks__go { gap: 1.2em; margin-top: 2em; }
}


/* ============================================================
   HOMEPAGE -> LATENT
   The homepage carried one footer link to twenty articles. Three rows
   and a way through, in the index's own language so the section is
   recognisable when a reader gets there.
   ============================================================ */
.hlat { padding: 7vw var(--sh-edge) 8vw; background: var(--sh-black); }
.hlat__eyebrow {
  display: flex; align-items: center; gap: 0.75em;
  font-size: max(11px, 0.84vw); letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--sh-grey); margin: 0 0 1.4vw;
}
.hlat__head {
  font-size: 4.844vw; font-weight: 400; line-height: 1;   /* house headline size */
  letter-spacing: -0.05em; color: var(--sh-white); margin: 0 0 3.4vw;
}
.hlat__item { border-top: 1px solid rgba(238, 237, 237, 0.16); }
.hlat__item:last-child { border-bottom: 1px solid rgba(238, 237, 237, 0.16); }
.hlat__link {
  display: grid; grid-template-columns: 9ch minmax(0, 1fr); gap: 2vw;
  align-items: baseline; padding: 2.2vw 1.2vw; margin: 0 -1.2vw;
  color: inherit; transition: background-color var(--t-fast) ease;
}
.hlat__link:hover { background: rgba(238, 237, 237, 0.045); }
.hlat__meta {
  font-size: max(11px, 0.8vw); letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--sh-grey);
}
.hlat__title {
  margin: 0; font-size: clamp(20px, 2.1vw, 34px); font-weight: 400;
  line-height: 1.12; letter-spacing: -0.03em; color: var(--sh-white);
  transition: color var(--t-fast) ease;
}
.hlat__link:hover .hlat__title { color: var(--sh-green); }
.hlat__all {
  display: inline-flex; align-items: center; gap: 0.5em;
  margin-top: 3vw; height: 31.9px; padding: 0 16px 1px; border-radius: 100px;
  background: var(--sh-green); color: var(--sh-ink);
  font-size: var(--fs-btn); line-height: 1; letter-spacing: -0.03em;
  transition: background-color var(--t-fast) ease;
}
.hlat__all:hover { background: var(--sh-green-lit); }
@media (max-width: 750px) {
  .hlat { padding: calc(70 * var(--u)) calc(24 * var(--u)) calc(80 * var(--u)); }
  .hlat__head { font-size: calc(31.5 * var(--u)); line-height: calc(36 * var(--u)); margin-bottom: calc(30 * var(--u)); }
  .hlat__link { grid-template-columns: 1fr; gap: calc(8 * var(--u)); padding: calc(26 * var(--u)) 0; margin: 0; }
  .hlat__title { font-size: calc(22 * var(--u)); }
  .hlat__all { margin-top: calc(30 * var(--u)); }
}


/* ============================================================
   PARALLAX ON TOUCH, WITHOUT JAVASCRIPT
   js/px.js bows out on touch: iOS stops firing scroll events during
   momentum scrolling and delivers them in a burst when the finger
   lifts, so a JS-driven layer always lagged the page -- the "stubborn"
   stutter. Disabling it fixed the stutter and cost the movement.

   A scroll-driven animation gets the movement back without the stutter,
   because the browser runs it on the compositor off the main thread:
   there is no scroll handler to fall behind. Guarded by @supports, so a
   browser without it simply keeps today's still frame rather than
   breaking.

   The travel matches what px.js does on desktop -- a quarter of the
   viewport either side of centre -- held to 20vh here because .px is a
   100vh layer inside a shorter window and must not show its own edge.
   ============================================================ */
@media (hover: none) and (pointer: coarse) {
  @supports (animation-timeline: view()) {
    /* The timeline is named on the SECTION, not taken from the layer.
       .px sits inside a clipped, painted-contained box, which is itself a
       scroll container, so view() resolved against that box and the
       animation sat frozen at its midpoint. The section's own scroll
       container is the document, which is the thing actually moving. */
    .filmstrip, .parallax-frame, .band { view-timeline-name: --sh-strip; view-timeline-axis: block; }
    /* About's frame holds .parallax-frame__media, not a .px layer, so the
       selector below never matched it and the page scrolled dead. It is
       already 133% tall on a phone, so there is room for the same travel
       the bands use. */
    .parallax-frame__media {
      animation: px-band linear both;
      animation-timeline: --sh-strip;
      animation-range: cover 0% cover 100%;
      will-change: transform;
    }
    /* The bands move too. They carry data-px-soft rather than a .px layer,
       so they were left out of the first pass and sat dead on a phone while
       the hero drifted. Their overhang is 9%, so the travel is scaled to
       match -- 4% either side keeps the layer's edge out of the frame. */
    .band__media {
      animation: px-band linear both;
      animation-timeline: --sh-strip;
      animation-range: cover 0% cover 100%;
      will-change: transform;
    }
    @keyframes px-band {
      from { transform: translate3d(0, -4%, 0); }
      to   { transform: translate3d(0,  4%, 0); }
    }
    .filmstrip .px,
    .parallax-frame .px {
      /* The layer must overhang by exactly what it travels, or it shows
         its own edge at the ends of the run. 100vh centred on a short
         section left only ~66px of slack against 124px of travel. */
      top: -20vh; bottom: -20vh; height: auto; margin-top: 0;
      animation: px-drift linear both;
      animation-timeline: --sh-strip;
      animation-range: cover 0% cover 100%;
      will-change: transform;
    }
    @keyframes px-drift {
      from { transform: translate3d(0, -20vh, 0); }
      to   { transform: translate3d(0,  20vh, 0); }
    }
  }
  @media (prefers-reduced-motion: reduce) {
    .filmstrip .px, .parallax-frame .px, .band__media,
    .parallax-frame__media { animation: none; }
  }
}


/* ============================================================
   "AI-powered. Human-led."
   The hyphen in each is a break opportunity, so the line could split
   as "Human-" / "led." -- the worst place in the sentence to break,
   because the hyphen reads as the end of a word rather than the middle
   of one. nowrap closes that everywhere.

   On a phone the pair then gets a line each, so the two halves of the
   claim sit one above the other instead of wrapping against each other.
   ============================================================ */
.nb { white-space: nowrap; }
@media (max-width: 750px) {
  .statement h1 .nb { display: block; }
}


/* ============================================================
   BAND ICONS — a slow pulse
   One dip per cycle rather than a strobe: the mark sits at full
   strength for most of the loop and dims briefly, which reads as a
   signal breathing rather than something flashing for attention.
   The three are offset so they are not in lockstep when two bands
   are on screen together.
   ============================================================ */
@keyframes sh-pulse {
  0%, 62%, 100% { opacity: 1;   transform: scale(1); }
  78%           { opacity: .38; transform: scale(.9); }
}
.band__note svg {
  transform-origin: center;
  animation: sh-pulse 3.4s ease-in-out infinite;
}
.band--machine .band__note svg { animation-delay: 1.1s; }
.band--story   .band__note svg { animation-delay: 2.2s; }
@media (prefers-reduced-motion: reduce) {
  .band__note svg { animation: none; }
}


/* Introduces the two project cards. Without it they arrive on an About page
   with nothing saying whether they are clients, case studies or the studio's
   own -- the line that used to do that job was cut from the block above. */
.apj-eyebrow { text-transform: uppercase; margin: 0 0 clamp(22px, 2.4vw, 40px); }
@media (max-width: 750px) {
  .apj-eyebrow { margin-bottom: calc(26 * var(--u)); }
}

/* ============================================================
   PRESS LANDS AT FULL STRENGTH
   ------------------------------------------------------------
   A press that fades in never arrives. These buttons carry a
   0.2-0.25s background transition for their hover, which is right
   on a desktop where the pointer rests. On a tap that navigates in
   under 150ms, the only thing ever rendered is the middle of that
   fade -- a pale, washed-out green around rgb(189,255,81) -- so the
   press reads as transparent rather than as our colour.

   .sh-cta, .about-cta and the venture buttons were fixed for this
   one at a time. This is the same fix for the rest of them, in one
   place, so a new button does not have to rediscover it: no
   transition on the way IN. It still fades back out on release,
   because that direction has time to be seen.

   Not scoped to a breakpoint -- a fast click on a desktop shows the
   same pale middle, it is just rarer because a mouse tends to dwell.
   ============================================================ */
/* The section headings belong here too, and badly: now that a whole
   band is the link, the heading greens on every tap anywhere in it,
   and the pale middle of its 0.18s colour fade -- rgb(211,255,139)
   at about 55ms -- is most of what anyone sees before the page
   changes. Measured, not guessed. */
.sh-sectionhead h2 a:active,
.sh-sectionhead--tap:active h2 a,
.carousel__btn:active,
.contact-submit:active,
.lat-link:active,
.lb__close:active,
.proj__btn:active,
.worktile__btn:active,
.wtile__cta:active,
.worktile:active .worktile__btn,
.wtile:active .wtile__cta,
/* These two were missed on the first pass and measuring caught them.
   .apj__btn--pri is already green at rest and shifts to green-lit on
   press, so a fade shows rgb(160,255,4) -- a colour that is in neither
   state and looks like a dimmed version of the button. And .lat-link
   greens its TITLE rather than its own background, so killing the
   transition on the row did nothing; the child is what has to stop
   fading. */
.apj__btn:active,
.apj__btn--pri:active,
.lat-link:active .lat-title {
  transition: none;
}

/* ============================================================
   FINGERS, NOT WIDTHS
   Every 44px hit-target floor on this site used to live inside
   @media (max-width: 750px). That is the wrong axis. An iPad mini in
   portrait is 768px, a Galaxy Tab S9 is 800px, an iPad Pro 13" is
   1024px -- all above the phone breakpoint, all driven by a finger.
   Measured on an iPad mini: burger 32x32, carousel arrows 30x30,
   logo 30 tall, the social icons ~17 square. The burger IS the
   navigation on a tablet in portrait.

   pointer: coarse asks the question that actually matters, so this
   covers iPads, Android tablets and touchscreen laptops, and leaves
   a mouse desktop exactly as it was.
   ============================================================ */
@media (pointer: coarse) {
  .sh-burger { min-width: 44px; min-height: 44px; }
  .sh-logo   { display: flex; align-items: center; min-height: 44px; }
  /* The carousel arrows are the exception to the min-size rule above.
     Growing the button itself to 44 put a 44px disc of green around a
     6px arrow, twice, over the editorial pictures -- on a phone that
     read as clutter, and it quietly overrode the 18px the phone rule
     asks for, because min-width beats width whatever the order.

     So the ink stays small and only the hit area grows: a transparent
     44x44 box centred on the dot, which satisfies the same tap target
     without putting any more green on the page. inset:auto resets the
     shorthand so top/left win. */
  .carousel__btn::before {
    content: ""; position: absolute; inset: auto;
    top: 50%; left: 50%; width: 44px; height: 44px;
    transform: translate(-50%, -50%);
  }
  .about-cta { min-height: 44px; }
  .statement__foot a { display: inline-flex; align-items: center;
                       justify-content: center;
                       min-width: 44px; min-height: 44px; }
  /* the dot stays 4px of ink; only its invisible hit box grows, from
     12x26 to 24x44, so neighbouring dots still don't overlap at gap 6 */
  .stu__dot::before { top: -20px; bottom: -20px; left: -10px; right: -10px; }
  /* The tile title is a <button> that opens the same lightbox the picture
     does, and it measured 16px tall -- under the 24px WCAG 2.2 AA floor.
     The picture above it is the real target, so this is a second way in
     rather than the only one, and the caption spacing is tuned to the
     pixel. Padding grows the hit box, the matching negative margin gives
     the space straight back, so the box reaches 28px and nothing moves.
     6px of padding stays inside .stu__cap's 7px top gap, so the hit area
     never reaches up over the picture and steals its click. */
  .stu__title { padding: 6px 0; margin: -6px 0; }
}

/* ============================================================
   BELOW 320: THE FLIP COVER SCREEN
   Every real phone is 320 CSS px or wider -- that is the floor even
   on a Galaxy with the display-size slider maxed out. The one thing
   narrower is the cover screen on a folding Flip, around 260.

   There the footer is the only thing that breaks: its two columns
   want 193 + 24 + 61 = 278px of a 217px content box, so the page
   gains 22px of sideways scroll. Nothing else leaks -- the hero film
   and the carousels sit inside containers that already clip, and
   scrollWidth measured 260 exactly once this grid was stacked.

   max-width: 319px means no phone layout can reach this rule.
   ============================================================ */
@media (max-width: 319px) {
  /* one column, and the explicit row/column placements the phone
     layout sets must be handed back or they fight the single track */
  .sh-footer__cols { grid-template-columns: 1fr; }
  .sh-footer__cols nav:first-of-type,
  .sh-footer__cols nav + nav { grid-area: auto; }
  .sh-footer__place {
    grid-area: auto; align-self: start;
    /* the social nav above it carries no bottom spacing of its own */
    margin-top: calc(25 * var(--u));
  }
}


/* ================= FILM PREDICTOR (homepage) =================
   The predictor's own hero, rebuilt here rather than reinterpreted: the
   word top left, the word bottom right, the line between them. This is
   the one block on the site that is not in the site's typeface, because
   it is not the site -- it is the product. */
@font-face{font-family:'Uncut Sans';font-style:normal;font-weight:300;font-display:swap;
  src:url('../assets/fonts/uncut-sans-latin-300-normal.287ffb1f.woff2') format('woff2')}
@font-face{font-family:'Uncut Sans';font-style:normal;font-weight:500;font-display:swap;
  src:url('../assets/fonts/uncut-sans-latin-500-normal.5b7839b1.woff2') format('woff2')}
@font-face{font-family:'Uncut Sans';font-style:normal;font-weight:600;font-display:swap;
  src:url('../assets/fonts/uncut-sans-latin-600-normal.749282d7.woff2') format('woff2')}

.hpred {
  --hp-gut: clamp(18px, 3.1vw, 44px);
  position: relative;
  overflow: hidden;
  background: #000;
  /* the same band the filmstrips use, so it sits in the page's rhythm
     rather than hijacking the screen */
  height: 39.06vw;
  min-height: 420px;
  font-family: 'Uncut Sans', var(--font-display), sans-serif;
}

/* ---- the opener ----
   The same flight the predictor page runs: six frames pass through a
   small window, the seventh stays, and the window opens out to become
   the band. It is held at the first frame until the section is scrolled
   to -- .is-in comes from the same observer the rest of the page uses --
   so a visitor arrives to the animation rather than missing it. */
.hpred { background: #fff;
  /* Not 40%: a percentage inside clip-path resolves against width for the
     sides and height for the top and bottom, so the hole came out shorter
     than the window and the frames behind it showed above and below. The
     band is full-bleed, so vw is the same number without the two meanings. */
  --hp-sq: min(300px, 40vw); }
/* The page behind this band is black, so fading a white band up from
   nothing reads as grey for most of a second. The band keeps .reveal --
   that is what the observer watches to start the opener -- but not the
   fade: it is white from the moment it is on screen, and its contents
   do the arriving. */
.hpred.reveal { opacity: 1; }
.hpred.reveal.is-in { animation: none; }
/* The six that pass through do it at one size, in one place: each fades
   in and stays, so the next simply covers it. No flight, no perspective,
   nothing scales. */
.hpred__win {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  overflow: hidden;
  width: var(--hp-sq); aspect-ratio: 1 / 1;
}
.hpred__deck { position: absolute; inset: 0; }
.hpred__deck i {
  position: absolute; inset: 0; display: block; opacity: 0;
  /* each frame overrides this inline with its place in the queue. Declared
     here so the property exists in the stylesheet: without it the build's
     custom-property check fails the deploy, and rightly -- a var() with no
     declaration anywhere is invalid at computed-value time. */
  --d: 0;
}
.hpred.is-in .hpred__deck i {
  animation: hp-cut 200ms linear calc(var(--d) * 120ms) forwards;
}
@keyframes hp-cut { 0% { opacity: 0 } 26% { opacity: 1 } 100% { opacity: 1 } }
.hpred__deck img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The seventh picture is full bleed from the start, behind a square hole
   the size of the frames. The hole opens outward to uncover it: the
   picture itself never moves or changes size. */
.hpred__full {
  position: absolute; inset: 0; opacity: 0;
  clip-path: inset(calc(50% - var(--hp-sq)/2) calc(50% - var(--hp-sq)/2));
}
.hpred.is-in .hpred__full {
  animation: hp-open 1.2s cubic-bezier(.76,0,.24,1) 1.62s forwards;
}
@keyframes hp-open {
  0%   { opacity: 0; clip-path: inset(calc(50% - var(--hp-sq)/2) calc(50% - var(--hp-sq)/2)); }
  14%  { opacity: 1; clip-path: inset(calc(50% - var(--hp-sq)/2) calc(50% - var(--hp-sq)/2)); }
  30%  { opacity: 1; clip-path: inset(calc(50% - var(--hp-sq)/2) calc(50% - var(--hp-sq)/2)); }
  100% { opacity: 1; clip-path: inset(0 0 0 0); }
}
/* the frames clear once the picture has the band */
.hpred.is-in .hpred__win { animation: hp-gone 1ms linear 2.1s forwards; }
@keyframes hp-gone { to { opacity: 0 } }
.hpred__bg { width: 100%; height: 100%; object-fit: cover; object-position: 58% 28%; }

/* the words and the line wait for the window to open */
.hpred__name, .hpred__ask { opacity: 0; }
.hpred.is-in .hpred__name--a { animation: hp-sink 2.0s cubic-bezier(.16,.84,.26,1) 2.70s forwards; }
.hpred.is-in .hpred__name--b { animation: hp-lift 2.0s cubic-bezier(.16,.84,.26,1) 3.00s forwards; }
.hpred.is-in .hpred__ask     { animation: hp-in .7s ease 2.95s forwards; }
@keyframes hp-sink { from { transform: translateY(-.30em); opacity: 0; }
                     to   { transform: translateY(.30em);  opacity: 1; } }
@keyframes hp-lift { from { transform: translateY(.30em);  opacity: 0; }
                     to   { transform: translateY(-.30em); opacity: 1; } }
@keyframes hp-in   { to { opacity: 1; } }

/* Less motion means the finished band, no flight. */
@media (prefers-reduced-motion: reduce) {
  .hpred__win { display: none; }
  .hpred__full { opacity: 1; clip-path: none; animation: none !important; }
  .hpred__name, .hpred__ask { opacity: 1; animation: none !important; }
}
.hpred__name {
  position: absolute;
  margin: 0;
  font-weight: 500;
  font-size: clamp(44px, 11vw, 190px);
  line-height: .82;
  letter-spacing: -.035em;
  color: var(--sh-white);
}
.hpred__name--a { top: var(--hp-gut); left: var(--hp-gut); }
.hpred__name--b { bottom: calc(var(--hp-gut) * 1.2); right: var(--hp-gut); }

.hpred__ask {
  position: absolute;
  z-index: 2;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: min(620px, 72vw);
}
.hpred__row {
  display: flex;
  align-items: center;
  gap: 12px;
  border-bottom: 1px solid rgba(255,255,255,.5);
  padding-bottom: 12px;
}
.hpred__row:focus-within { border-bottom-color: var(--sh-green); }
.hpred__q {
  flex: 1 1 auto;
  min-width: 0;
  background: none;
  border: 0;
  outline: none;
  color: var(--sh-white);
  font-family: inherit;
  font-weight: 300;
  font-size: clamp(17px, 1.7vw, 25px);
  letter-spacing: -.01em;
  padding: 2px;
}
.hpred__q::placeholder { color: #FFFFFF; font-weight: 600; opacity: 1; }
.hpred__go {
  flex: none;
  border: 0;
  border-radius: 40px;
  background: var(--sh-green);
  color: var(--sh-black);
  font-family: inherit;
  font-weight: 500;
  font-size: 15px;
  line-height: 1;
  letter-spacing: -.01em;
  padding: 13px 22px;
  min-height: 44px;
  white-space: nowrap;
  cursor: pointer;
  transition: background .18s ease, transform .18s ease;
}
.hpred__go:hover { background: var(--sh-green-lit); transform: translateY(-1px); }
.hpred__go:focus-visible { outline: 2px solid var(--sh-green); outline-offset: 3px; }

@media (max-width: 700px) {
  /* 4:5 on a phone */
  .hpred { height: auto; aspect-ratio: 4 / 5; min-height: 0; }
  .hpred__name { font-size: clamp(50px, 15.4vw, 88px); }
  .hpred__ask { width: calc(100% - var(--hp-gut) * 2); }
  .hpred__row { gap: 9px; }
  /* 16px floor: Safari zooms the page when a focused field is smaller */
  .hpred__q { font-size: 16px; }
  .hpred__go { font-size: clamp(10px, 3.05vw, 13px); padding: 10px 15px; min-height: 36px; }
}
@media (max-width: 359px) {
  .hpred__long { display: none; }
}
