﻿/* ============================================================
   RAPIDSITE STUDIO â€” editorial poster aesthetic
   cream / black / one orange
   ------------------------------------------------------------
   Single source of truth lives in :root. Every colour, type
   size, radius, shadow and spacing value below pulls from a
   token â€” no section should introduce a one-off value.
   ============================================================ */

/* ---------- the two families, served from this origin ----------
   They used to come from fonts.googleapis.com, which cost the page a
   render-blocking stylesheet on a third-party origin before a single word
   could paint - and, because first layout waited on it, every lazy image
   below the fold was judged to be in the viewport and fetched at once.
   Hosting them here removes both: the faces are declared in this sheet, which
   the page is already waiting for, and the three files the first screen needs
   are preloaded from the document. Only latin and latin-ext are carried; the
   Cyrillic, Devanagari and Vietnamese cuts Google also serves have nothing to
   set on this site. unicode-range is kept exactly as Google wrote it, so a
   browser still downloads only the subsets a page actually needs.
   swap, not block: the fallback stack is a system sans, so a line of copy is
   readable from the first paint and re-sets when the real face lands. */

@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('../assets/fonts/poppins-300-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('../assets/fonts/poppins-300-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/poppins-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/poppins-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../assets/fonts/poppins-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../assets/fonts/poppins-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../assets/fonts/poppins-600-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../assets/fonts/poppins-600-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../assets/fonts/poppins-700-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../assets/fonts/poppins-700-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/ibm-plex-mono-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/ibm-plex-mono-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../assets/fonts/ibm-plex-mono-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../assets/fonts/ibm-plex-mono-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* ---------- brand palette (single source of truth) ---------- */
  --black:  #111111;
  --cream:  #f2ede4;
  --orange: #f5641e;

  /* tonal supports (warm, derived from the three brand colours) */
  --ink:         var(--black);  /* legacy alias used across the sheet */
  --charcoal:    #1b1916;       /* raised surface on black */
  --cream-deep:  #e8e1d3;       /* deeper cream â€” connect section */
  --cream-panel: #faf6ee;       /* raised card/pill on cream */
  --cream-side:  #f1e9de;       /* icon strips + cta-banner field */
  --cream-plate: #ebe2d4;       /* sampled from cream-back1.webp - the field ground */
  --cream-mute:  #d8d1c7;       /* cream stepped back - secondary copy on the dark plate */
  /* Cream held 15% off full, mixed toward the ground rather than to an
     alpha: this is the running copy that sits over a photograph, and an
     alpha would let every line pick up whatever happens to be behind it -
     warmer where the frame is lit, colder where it is not. Mixing to the
     black holds one flat tone the whole way down a block. Resolves to
     #d0ccc4. */
  --cream-soft:  color-mix(in srgb, var(--cream) 85%, var(--black));
  --orange-dark: #dd5316;

  /* text greys â€” checked for WCAG AA */
  --muted:       #5c584f;       /* muted body on cream  (6.1:1) */
  --muted-light: #a8a294;       /* muted body on black  (7.4:1) */

  /* rgba channels â€” lets shadows/glows pull from one source */
  --black-rgb:  17, 17, 17;
  --orange-rgb: 245, 100, 30;
  --cream-rgb:  242, 237, 228;
  --white-rgb:  255, 255, 255;  /* the top-edge highlight on raised cream panels */

  /* ---------- type ----------
     Two families now:
       Poppins        -> the headline, every section heading, all running
                         copy, the nav and every button
       IBM Plex Mono  -> section labels, pills, meta, micro-UI
     Display and body land on one face again. Nothing is left to tell the two
     roles apart but scale, weight and tracking, which is what the steps in
     the scale below are for - a heading is a heading because it is large and
     tracked tight, not because it is a different typeface.

     Poppins is NOT variable: Google serves it as discrete cuts, so every
     weight a rule asks for has to be in the request in index.html or the
     browser will synthesise it. The four in use are 300/400/500/700, and 600
     is requested too so a rule can reach for it without a second round trip.
     The mono is untouched - it is a monospace role, and Poppins cannot fill
     it. */
  --font-hero:    "Poppins", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-display: "Poppins", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-body:    "Poppins", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-mono:    "IBM Plex Mono", ui-monospace, SFMono-Regular, monospace;

  /* Weights are tokens so the two roles can differ without hunting rules.
     Both roles land on 700. Headline and section headings share a face again,
     so scale and tracking are what tell them apart - the hero is far larger and
     tracked open, the headings tracked tight. */
  --fw-hero:    700;
  --fw-display: 700;

  /* ---------- responsive type scale ----------
     Fluid steps. Each one interpolates between its size at a 360px viewport
     and its size at 1440px. The middle term is `rem + vw`, never bare vw,
     so the scale still answers to browser zoom and to a user's default font
     size (WCAG 1.4.4). The px range each step spans is in the comment.

       slope     = (max - min) / (1440 - 360)
       preferred = (min - slope * 360)/16 rem + slope * 100 vw

     Display steps climb at a ~1.24 ratio. Their clamp minimum sits a step
     under the 360px value on purpose, so a headline keeps shrinking on a
     320px phone instead of overflowing it. */
  --fs-hero: clamp(2rem,      1.2083rem + 3.5185vw, 4.375rem); /* 32 -> 70 */
  --fs-mega: clamp(1.75rem,   1.333rem + 2.963vw, 4rem);      /* 32 -> 64 */
  --fs-h1:   clamp(1.625rem,  1.417rem + 2.037vw, 3.25rem);   /* 30 -> 52 */
  --fs-h2:   clamp(1.5rem,    1.292rem + 1.481vw, 2.625rem);  /* 26 -> 42 */
  --fs-h3:   clamp(1.25rem,   1.125rem + 1.111vw, 2.125rem);  /* 22 -> 34 */
  --fs-h4:   clamp(1.0625rem, 0.938rem + 0.833vw, 1.6875rem); /* 18 -> 27 */
  --fs-h5:   clamp(0.9375rem, 0.875rem + 0.556vw, 1.375rem);  /* 16 -> 22 */

  /* Body and UI steps floor at their 360px value: running copy should never
     shrink below its small-screen size, it should only rewrap. */
  --fs-lead:    clamp(1.0625rem, 0.979rem + 0.370vw, 1.3125rem); /* 17 -> 21 */
  /* Running copy is flat 17px at every width: it used to floor at 16 on a
     phone, which is where the text is read most and least forgiving. The
     secondary step keeps a size between them so cards and lists still sit
     under page copy, and it no longer drops below 16 anywhere. */
  --fs-body:    1.0625rem;                                       /* 17 flat  */
  --fs-body-sm: 1rem;                                            /* 16 flat  */
  --fs-label:   clamp(0.75rem,   0.729rem + 0.093vw, 0.8125rem); /* 12 -> 13   mono section labels */
  --fs-ui:      clamp(0.75rem,   0.733rem + 0.074vw, 0.8rem);    /* 12 -> 12.8 buttons / pills */
  --fs-ui-sm:   clamp(0.6875rem, 0.677rem + 0.046vw, 0.719rem);  /* 11 -> 11.5 */
  --fs-meta:    clamp(0.8125rem, 0.771rem + 0.185vw, 0.9375rem); /* 13 -> 15   row meta / captions */
  --fs-micro:   clamp(0.5625rem, 0.542rem + 0.231vw, 0.75rem);   /* 9.5 -> 12  gallery pills / micro labels */

  /* rhythm — four roles, tight at the top of the scale and opening as the type
     gets smaller, which is the whole of the editorial rule: the larger the type,
     the less leading it needs.

       hero     the headline is sentence case, so it carries descenders and the
                rows cannot close below the em box the way an all-caps stack can.
                1.0 is the floor that clears a "y" over the row beneath while
                still binding the three rows into one block. It opens once more
                at 720px, where a row can be a fragment rather than a whole line.
       display  every section heading. Set in caps, so there are no descenders to
                clear and 1.1 sits at the tight end of the editorial band without
                letting the rows drift apart.
       heading  card and step titles, two or three words at h5/h3 — near enough
                to reading size that they want real leading, not display leading.
       lead     decks and pull quotes at h4 and up. Large running copy needs less
                than small running copy; 1.5 is where a 27px line stops feeling
                double-spaced.
       body     everything at reading size. Poppins has a tall x-height and
                near-circular bowls that want air, and 1.7 is the middle of
                the 1.6-1.8 band the page is set to. */
  --lh-hero:    1.0;
  --lh-display: 1.1;
  --lh-heading: 1.25;
  --lh-lead:    1.5;
  --lh-body:    1.7;

  /* Poppins is geometric and sets wide on its own. At display size the default
     fit reads loose, so the headline is tracked in harder than any other role -
     tight enough to bind each row into a single mass, stopping short of the
     point where the round lowercase (e, a, o) starts to close up. */
  --ls-hero:    -0.04em;
  --ls-display: -0.02em;
  /* card and step titles sit between display and reading size: enough of a
     negative to stay punchy, short of the point where 16-22px starts to clot */
  --ls-heading: -0.015em;
  /* Small uppercase is the one place tracking has to go the other way — caps
     set at label size read as a solid block without it. The mono eyebrows take
     the widest fit; uppercase headings at h5 and the UI chrome take less. */
  --ls-label:   0.18em;
  --ls-caps:    0.03em;
  --ls-ui:      0.1em;

  /* ---------- layout ---------- */
  /* The reading measure. In ch so it tracks the face and size actually set
     rather than a fixed pixel column — but the figure is calibrated, not
     copied: the ch unit is the width of "0", and Poppins' zero is wider than
     its average lowercase letter, so a literal 65ch cap renders well past 65
     real characters. 55ch is the value that measures ~65 of them in this
     face, which is the middle of the 55-75 band. Re-measured in the browser
     against Poppins, not carried over. Every block of running copy is capped by this or by a tighter,
     deliberately chosen figure; nothing runs the full container width. */
  --measure:   55ch;
  --container: 84rem;
  --gutter:    clamp(1.25rem, 4vw, 4rem);
  --section-y: clamp(5rem, 12vh, 9rem);

  /* radii */
  --radius-pill:  999px;
  --radius-card:  16px;
  --radius-panel: 1.5rem;

  /* elevation */
  --shadow-card:       0 18px 40px -24px rgba(var(--black-rgb), 0.45);
  --shadow-card-hover: 0 30px 55px -28px rgba(var(--black-rgb), 0.5);
  /* the same drop, warmed: a second orange throw under the panel plus a
     hairline of light on its top edge, so a lit card still sits in the
     page's shadow language instead of glowing free of it */
  --shadow-lit:       0 18px 40px -24px rgba(var(--black-rgb), 0.45),
                      0 14px 34px -22px rgba(var(--orange-rgb), 0.28),
                      inset 0 1px 0 rgba(var(--white-rgb), 0.8);
  --shadow-lit-hover: 0 28px 56px -26px rgba(var(--black-rgb), 0.5),
                      0 20px 44px -20px rgba(var(--orange-rgb), 0.45),
                      inset 0 1px 0 rgba(var(--white-rgb), 0.92);

  /* soft-UI, held at about a tenth of the usual strength: one light source at
     the upper left and its answering shadow at the lower right. Enough to make
     a dark surface read as moulded out of the page; not enough to turn the
     section into plastic. Used by the work gallery only. */
  --soft-light: rgba(var(--cream-rgb), 0.05);
  --soft-dark:  rgba(0, 0, 0, 0.55);
  --shadow-soft-raised: -5px -5px 14px var(--soft-light),
                         6px 9px 20px var(--soft-dark);
  --shadow-soft-pressed: inset 3px 3px 8px var(--soft-dark),
                         inset -2px -2px 6px var(--soft-light);

  /* motion */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: 400;
  background: var(--cream);
  color: var(--ink);
  line-height: var(--lh-body);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

/* Nothing may push a column past the viewport on a 320px screen: display type
   is the usual culprit, since a single long word cannot rewrap out of trouble. */
h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }

img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
ul, ol { list-style: none; }

::selection { background: var(--orange); color: var(--cream); }

/* ---------- reveal system ---------- */
[data-reveal] {
  opacity: 0;
  transform: translateY(36px);
  transition: opacity 0.9s var(--ease-out), transform 0.9s var(--ease-out);
  transition-delay: var(--d, 0s);
}
[data-reveal].in-view {
  opacity: 1;
  transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  [data-reveal] { opacity: 1; transform: none; transition: none; }
  html { scroll-behavior: auto; }
  .marquee-track { animation: none !important; }
  .orbit-ring { animation: none !important; }
  .hero-object__art { animation: none !important; }
}

/* ---------- keyboard: the skip link and the default focus ring ----------
   The link is in the flow rather than display:none, because a hidden element
   cannot take focus; it is simply lifted off the top of the screen until it
   does, and then drops into the corner over the header.
   The ring underneath it is a fallback, not an override: the components that
   draw their own focus state - the service rows, the buttons, the FAQ heads -
   already match :focus-visible with a more specific selector and keep winning.
   This one only catches what is left, which is mostly plain links in the nav,
   the footer and the running copy, where the UA's own ring is nearly invisible
   against this much black. */
.skip-link {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 200;
  transform: translateY(-140%);
  margin: 0.6rem;
  padding: 0.85rem 1.35rem;
  background: var(--orange);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: var(--fs-ui);
  text-transform: uppercase;
  letter-spacing: var(--ls-ui);
  border-radius: var(--radius-pill);
  text-decoration: none;
  transition: transform 0.2s var(--ease-out);
}
/* :focus as well as :focus-visible. The link is only reachable by keyboard in
   the first place, and a skip link that stays hidden when something focuses it
   programmatically - a fragment jump, a browser restoring focus - is a skip
   link that fails exactly when it is needed. */
.skip-link:focus,
.skip-link:focus-visible { transform: translateY(0); }

:where(a, button, [tabindex]):focus-visible {
  outline: 2px solid var(--orange);
  outline-offset: 3px;
  border-radius: 2px;
}

/* ---------- shared: section label ---------- */
.section-label {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: clamp(2rem, 5vw, 4rem);
}
.section-label-light { color: var(--muted-light); }
.label-bar {
  width: 2.5rem;
  height: 0.55rem;
  background: var(--orange);
  display: inline-block;
  flex-shrink: 0;
}

/* ---------- shared: the section corner mark ----------
   The studio mark stamped into the top-right corner of every banded section.

   Three things make it read as a mark rather than as a stain. It is small and
   crisp instead of large and faint - the glyph is a blocky monogram, and at
   poster size a low alpha only ever resolves into a grey rectangle, because
   what you see is its bounding area rather than its shape. It sits in the
   section's top padding, clear of the content, so it never lands behind a
   heading or a button in one section and on bare ground in the next. And the
   orange disc keeps several times the alpha of the letterform, so the one
   piece of the logo that is unmistakably ours stays legible when the rest of
   it has sunk into the surface.

   The right offset resolves to the page container's edge on a full-bleed
   section and to the gutter on a section that is itself the container, which
   is what puts every mark on the same vertical line down the page however its
   own section is built. Centred sections narrower than the viewport fall out
   of the same expression without a special case. */
.section-mark {
  /* sized once, because the vertical placement below is derived from it */
  --mark-size: clamp(2.75rem, 4.6vw, 4.5rem);
  --mark-pt: var(--section-y);          /* the host section's top padding */
  --mark-gap: clamp(0.6rem, 1.6vh, 1.1rem);

  position: absolute;
  z-index: -1;
  /* The mark hangs from the *bottom* of the section's top padding rather than
     from the section's edge: it clears the first line of content by --mark-gap
     at every viewport height, instead of sitting at a fixed inset that a short
     viewport would push straight into a heading or, in .connect, behind the
     solid form panel. The max() is the floor for viewports too short to give
     the mark a band of its own. */
  top: max(clamp(0.75rem, 2vh, 1.25rem),
           calc(var(--mark-pt) - var(--mark-size) * 0.99 - var(--mark-gap)));
  right: max(var(--gutter), (100% - var(--container)) / 2);
  width: var(--mark-size);
  aspect-ratio: 975 / 965;
  height: auto;
  pointer-events: none;
  overflow: visible;
}
/* the light ground wants slightly less of the letterform than the dark one:
   ink on cream carries further than cream on ink at the same alpha */
.section-mark--light {
  --mark-ink: var(--cream);
  --mark-ink-o: 0.085;
  --mark-dot-o: 0.42;
}
.section-mark:not(.section-mark--light) {
  --mark-ink: var(--ink);
  --mark-ink-o: 0.08;
  --mark-dot-o: 0.3;
}
/* The sprite is referenced, never rendered. It may not be display:none - a
   <use> pointing into a display:none tree renders nothing in some engines -
   so it is collapsed to zero instead. */
.mark-sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/* ---------- shared: button system ----------
   One geometry for every text button. Roles:
     .btn-solid  â†’ primary   (solid black, blooms orange)
     .btn-ghost  â†’ secondary (outline, fills on hover)
     .btn-accent â†’ accent    (solid orange â€” for dark surfaces)
*/
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  /* 700, same as the nav - see .main-nav on why the weight is up here */
  font-family: var(--font-body);
  font-size: var(--fs-ui);
  font-weight: 700;
  letter-spacing: var(--ls-ui);
  text-transform: uppercase;
  padding: 0.9em 1.8em;
  border-radius: var(--radius-pill);
  border: 1.5px solid transparent;
  cursor: pointer;
  transition: transform 0.3s var(--ease-out), background 0.3s var(--ease-out),
              color 0.3s var(--ease-out), border-color 0.3s var(--ease-out);
}
.btn svg { width: 1.05rem; transition: transform 0.3s var(--ease-out); }
.btn:hover svg { transform: translateX(4px); }

.btn-solid { background: var(--ink); color: var(--cream); border-color: var(--ink); }
.btn-solid:hover { background: var(--orange); color: var(--ink); border-color: var(--orange); transform: translateY(-2px); }

.btn-ghost { background: transparent; color: var(--ink); border-color: var(--ink); }
.btn-ghost:hover { background: var(--ink); color: var(--cream); transform: translateY(-2px); }

.btn-accent { background: var(--orange); color: var(--ink); border-color: var(--orange); }
.btn-accent:hover { background: var(--cream); color: var(--ink); border-color: var(--cream); transform: translateY(-2px); }

u {
  text-decoration-thickness: 2px;
  text-underline-offset: 0.18em;
}

/* ============================================================
   HEADER
   ============================================================ */
.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  background: color-mix(in srgb, var(--cream) 88%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 6%, transparent);
  transition: transform 0.5s var(--ease-out),
              background 0.45s var(--ease-out),
              border-color 0.45s var(--ease-out),
              color 0.45s var(--ease-out);
}
.site-header.is-hidden { transform: translateY(-100%); }

/* ---------- header over the hero ----------
   The bar is fixed, so the hero's frame already runs up to the top edge of
   the viewport behind it - all this state does is stop painting cream over
   it. What goes down instead is a scrim that starts at 26% black under the
   row of controls and falls away to nothing by the foot of the bar. It is a
   gradient rather than a flat tint and there is no bottom rule, and that is
   the whole trick: either one would end on a hard horizontal line across the
   picture. This ends on nothing. It sits at a quarter rather than the half it
   used to, because with the bar this much shorter the type sits closer to the
   top of the frame, where the vignette is already dark - the scrim only has
   to lift the controls off the plate, not black it out.
   Everything in the bar inverts to cream to sit on it. The class comes off
   again once the hero has scrolled past, because below it the page is cream
   and the bar has to go back to reading dark-on-light. */
.site-header.over-hero {
  background: linear-gradient(
    180deg,
    rgba(17, 17, 17, 0.26) 0%,
    rgba(17, 17, 17, 0.20) 45%,
    rgba(17, 17, 17, 0.08) 78%,
    rgba(17, 17, 17, 0) 100%
  );
  backdrop-filter: none;
  border-bottom-color: transparent;
  color: var(--cream);
}
/* The blur cannot go on the bar itself: a uniform backdrop-filter stops dead
   at the foot of the box and draws exactly the horizontal seam across the
   picture that the gradient above is written to avoid. So it goes on a layer
   of its own, behind the scrim, and that layer is masked with the same
   falloff - 6px of blur under the controls, nothing at all by the bottom
   edge. The photograph runs on through unbroken.

   It carries a second weight of black as well, and only on the right. Both
   plates the bar goes transparent over are lit from the right - the banner's
   object shot most of all, where the glass burns hardest at about the point
   the CTA sits - so a scrim that falls only downwards leaves the one control
   on the bar that is itself a solid colour sitting on the brightest thing
   behind it: orange on orange, with nothing between them. This runs the other
   way, nothing at all across the left half where the wall is already dark and
   the nav reads clean, then down to the button. It rides on this layer rather
   than in the bar's own gradient because a sideways scrim written there would
   end on a hard horizontal line at the foot of the bar, which is the one thing
   the whole arrangement exists to avoid: here it inherits the same mask, so it
   fades out downwards exactly as everything else does. */
.site-header.over-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    90deg,
    rgba(17, 17, 17, 0) 40%,
    rgba(17, 17, 17, 0.16) 68%,
    rgba(17, 17, 17, 0.30) 100%
  );
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 55%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 0%, #000 55%, transparent 100%);
  pointer-events: none;
}
.site-header.over-hero .logo-mark { color: var(--cream); }
.site-header.over-hero .main-nav a { color: var(--cream); }
.site-header.over-hero .main-nav a:hover { background: var(--cream); color: var(--ink); }
/* the page's solid button is ink-on-cream, which sinks into the frame - same
   inversion the hero's own buttons take */
.site-header.over-hero .header-cta {
  /* The accent, same as the hero's own primary directly below it — the bar's
     button and "See our work" are the same offer, so they now read as one
     thing said twice rather than two different-coloured pills. It sits a few
     points off full strength for the reason the cream did: flat colour at
     full strength outweighed the logo and the nav put together and read as
     the subject of the bar rather than one of three things in it. */
  background: color-mix(in srgb, var(--orange) 94%, transparent);
  color: var(--ink);
  border-color: color-mix(in srgb, var(--orange) 94%, transparent);
}
.site-header.over-hero .header-cta:hover {
  background: var(--cream);
  color: var(--ink);
  border-color: var(--cream);
}
.site-header.over-hero .nav-toggle span { background: var(--cream); }

.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* ~74px of bar. The tallest thing in the row is the CTA, so the block
     padding is measured off that, and it is not symmetrical: the scrim behind
     it is already fading out across the bottom third, so an even value left a
     visible run of empty tinted frame under the controls. The foot gives up
     the difference. */
  padding: 1rem var(--gutter) 0.85rem;
  max-width: var(--container);
  margin: 0 auto;
}

.logo {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-size: 1.35rem;
  letter-spacing: var(--ls-display);
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
}
.logo sup { color: var(--orange); font-size: 0.6em; }
.logo-mark {
  width: 2.1rem;
  height: 2.1rem;
  flex-shrink: 0;
  color: var(--ink);
  transition: transform 0.45s var(--ease-out);
}
.logo:hover .logo-mark { transform: rotate(-6deg) scale(1.08); }

.main-nav {
  display: flex;
  align-items: center;
  /* No container. The links sit straight on the bar: no capsule, no fill and
     no hairline round them, so the only thing drawing the middle of the header
     is the type. The gap opens from 0.2rem because without an enclosing shape
     the links need daylight of their own to read as five separate items
     rather than one run of caps. */
  gap: 0.35rem;
  /* Matching the buttons: the interactive chrome speaks in one voice. The
     page is one face now, so what separates this from a heading is that it is
     small, uppercase and tracked wide - not the typeface.
     700 because uppercase text this small and tracked this wide needs the
     weight to hold its colour. Poppins does ship a 500, unlike the Lato this
     replaced, so this is a live choice rather than the only cut available. */
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--fs-ui);
  text-transform: uppercase;
  letter-spacing: var(--ls-ui);
}
.main-nav a {
  position: relative;
  padding: 0.62em 1.15em;
  border-radius: var(--radius-pill);
  color: var(--ink);
  transition: background 0.28s var(--ease-out), color 0.28s var(--ease-out);
}
.main-nav a:hover { background: var(--ink); color: var(--cream); }

/* The bar's button is the same button as everywhere else on the page, but it
   is the only one that has to share a line with the logo and the nav. At the
   page's full 0.9em/1.8em it sat taller than the pill and dominated the row,
   so it takes a trim here and only here. */
.header-cta { padding: 0.78em 1.5em; }

.nav-toggle { display: none; }

/* ============================================================
   HERO
   ============================================================ */
.hero {
  position: relative;
  isolation: isolate;
  /* The photographic ground. One frame, one flat scrim, nothing else on top of
     it: the plate is already dark and softly lit, so all that is left to do is
     seat the type on it. 15% of brand black is the whole treatment - it holds
     cream copy well clear of AA over the lit middle, and it is light enough to
     leave the grain its micro-contrast. Past about 20% this frame stops
     reading as a lit surface and starts reading as a soft black rectangle,
     which is the flatness the scrim is meant to avoid. */
  background-color: var(--black);
  background-image:
    linear-gradient(rgba(17, 17, 17, 0.15), rgba(17, 17, 17, 0.15)),
    url("../assets/img/hero-back2.webp");
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
  /* the section is dark now, so the whole block inverts off one declaration */
  color: var(--cream);
  padding: clamp(7rem, 16vh, 11rem) var(--gutter) clamp(5rem, 10vh, 8rem);
  min-height: 86svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  overflow: hidden;
}

/* What is left of the crop at desk widths comes off the top and the bottom.
   It is split 80/20 rather than centred: four fifths off the top, which is
   empty vignette, and a fifth off the foot, which is little enough that the
   orange arcs still land whole in the corner. Below this width the box turns
   portrait, the crop comes in from the sides instead and the vertical anchor
   stops mattering — so it is only set here. */
@media (min-width: 901px) {
  /* The box runs wider than the 3:2 frame at these widths, so `cover` crops it
     top and bottom and the taller the box, the more of the picture survives.
     88% of the viewport is the balance struck: enough of the frame to keep the
     vignette breathing around the light, and short enough that the section
     below shows at the foot of the first screen rather than the hero sealing
     it off. The crop that is left comes off the top and the bottom evenly, so
     the vignette stays symmetrical. Set here rather than in the base rule
     because below this width the box is already portrait, and there a taller
     box means a tighter crop, not a looser one. */
  .hero { min-height: 88svh; }
}

.hero-inner {
  /* The hero is a split now: everything that is read sits in the left half,
     the object holds the right. It runs to the page container rather than the
     old narrower measure - a centred column wanted a short measure, a split
     wants the full width, and the copy's own measure is set on the column
     instead. The two halves are even, and the object is centred on the height
     of the copy beside it rather than stacked above or below anything. */
  max-width: var(--container);
  margin: 0 auto;
  width: 100%;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  column-gap: clamp(2rem, 5vw, 5rem);
  position: relative;
  z-index: 1;
}

/* the reading column. Left-ranged, top to bottom: headline, deck, buttons. */
.hero-copy {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  min-width: 0;
}

/* ---------- the object ----------
   One copy, in the right half of the split, centred on the copy beside it.
   Nothing is layered through the type any more: the object holds its own
   column, which is what the composition asks for now. It is sized off the
   column rather than the viewport, with a ceiling so it stops growing before
   it starts to read as wallpaper on a wide screen, and it is decorative, so
   it never takes a pointer or a caret. */
.hero-object {
  position: relative;
  width: 100%;
  max-width: 34rem;
  justify-self: center;
  pointer-events: none;
  -webkit-user-select: none;
  user-select: none;
  /* the stage the tilt is read against. Far enough back that leaning the solid
     toward the cursor reads as depth rather than as the picture being sheared. */
  perspective: 1100px;
  /* the plate behind it is dark and near flat, so without a shadow the object
     floats with nothing under it - this is a seat, not a glow. It sits on the
     wrapper, which never turns, so the shadow keeps falling straight down
     while the solid rolls above it. */
  filter: drop-shadow(0 30px 70px rgba(var(--black-rgb), 0.75));
}

/* ---------- the turn ----------
   The cube is rendered corner-on, standing on one of its body diagonals, and
   a solid seen down that axis repeats every third of a turn - so turning the
   picture in its own plane reads as the cube rolling on that diagonal rather
   than as a flat image being spun. Only the light gives it away, and it is
   diffuse enough not to.

   One turn every 48 seconds: slow enough that nothing about it asks to be
   watched, fast enough that the object is visibly alive if you rest on it for
   a second. Linear, because a solid turning under its own weight has no
   easing in it - any ease here would read as a loop restarting.

   The origin is the cube's own centre, not the frame's: the render sits a
   little right of centre and a little high in its box, and turning about the
   box centre would swing the whole solid in a small orbit instead of letting
   it turn on the spot. */
.hero-object__art {
  width: 100%;
  height: auto;
  transform-origin: 52% 48%;
  animation: hero-object-turn 48s linear infinite;
  /* the wrapper's drop-shadow has to be re-drawn as this moves, so hand the
     turn to its own layer and keep that work off the main thread */
  will-change: transform;
}

@keyframes hero-object-turn {
  to { transform: rotate(360deg); }
}

/* once the hero has scrolled off, there is nothing to keep turning for, and a
   full-size filtered image re-rastering every frame for an empty screen is the
   one part of this that is not free. Driven from js/main.js. */
.hero-object.is-turn-paused .hero-object__art { animation-play-state: paused; }

/* ---------- the same turn, handed to the cursor ----------
   With a fine pointer on the page the turn stops being a keyframe and becomes
   a number js/main.js integrates every frame, so the cursor can push on it:
   circle the solid and it follows your hand, flick it and it keeps going the
   way you threw it, leave it alone and it settles back to the same slow drift
   it had before — in whichever direction it was last sent. The keyframe has to
   come off for that, because an animation outranks the inline transform the
   loop writes. Only ever set from script, so a page without JS keeps the
   automatic turn exactly as it was. */
.hero-object.is-steerable { pointer-events: auto; cursor: grab; }
.hero-object.is-steerable.is-grabbed { cursor: grabbing; }
.hero-object.is-steerable .hero-object__art { animation: none; }

/* The nudge. It rides just above the cursor rather than sitting in a fixed
   spot on the object: the joke is that the solid is asking to be handled, and
   a label pinned to the corner of the frame reads as a caption instead. It
   never takes the pointer itself — the whole area under it has to stay
   grabbable — and it is drawn on the cream so it holds against the dark plate
   and the lit part of the frame alike. */
.hero-object__nudge {
  position: absolute;
  left: var(--nudge-x, 50%);
  top: var(--nudge-y, 50%);
  transform: translate(-50%, calc(-100% - 0.9rem)) scale(0.94);
  transform-origin: 50% 120%;
  z-index: 2;
  pointer-events: none;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.02em;
  color: var(--black);
  background: var(--cream);
  padding: 0.45em 0.85em;
  border-radius: 999px;
  box-shadow: 0 8px 24px rgba(var(--black-rgb), 0.35);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0s linear 0.18s;
}
.hero-object.is-nudging .hero-object__nudge {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, calc(-100% - 0.9rem)) scale(1);
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0s;
}

/* Poppins at 700, sentence case. The section headings are the same face and
   weight but set in caps, so case and scale are what separate the headline from
   them. The running copy underneath is that same face again, so the drop in
   size and weight is all that sets it apart. */
.hero-title {
  font-family: var(--font-hero);
  font-weight: var(--fw-hero);
  font-size: var(--fs-hero);
  line-height: var(--lh-hero);
  letter-spacing: var(--ls-hero);
  text-align: left;
}

.hero-sub {
  /* Deliberately under the page measure: supporting copy stays clearly
     narrower than the headline column above it. The deck is set at 700, so
     it runs a touch under --measure to land on the same ~65-character line. */
  max-width: min(54ch, 100%);
  /* Ranged left, ragged right. The deck was justified when it sat centred
     under a centred headline and had to read as a rectangle; in a left-ranged
     column the ragged edge is the point, and justifying a measure this narrow
     only opens rivers. */
  text-align: left;
  hyphens: none;
  text-wrap: pretty;
  /* 36 -> 48px. The tightened headline leading gives back roughly a
     descender's worth of space under the last row, so the gap to the sub is
     set here rather than inherited from the line box — and it scales with the
     headline, so a smaller hero takes a proportionally smaller gap. */
  margin-top: clamp(2.25rem, 4vh, 3rem);
  /* Held at a flat 18px on purpose rather than pulled from a fluid step:
     the hero sub is the one line meant to stay quiet under the headline
     at every width, so it does not grow with the viewport. Stepped down from
     19px now the deck is set at a regular weight: a lighter cut at the old
     size read thin and wide rather than refined. */
  font-size: 1.125rem;
  /* A notch above --lh-body (1.7). Light type wants more air between the rows
     or the block greys into a slab; the extra leading is what makes a regular
     weight read as editorial rather than merely small. */
  line-height: 1.8;
  /* The ground under this paragraph is a photograph, so the deck is set a step
     off full cream rather than at it: it reads as the secondary voice under the
     headline and still clears AA over the frame - measured against the pixels
     actually behind it, 6.1:1 at the single brightest point of the lit middle
     and 8.6:1 across all but the top 5% of the block, so most of the paragraph
     is clearing AAA. See --cream-soft for why this is a flat tone rather than
     cream at an alpha. The shadow is a seat, not a halo: one short, dark,
     near-invisible drop that stops the letterforms grinding against the grain,
     with nothing wide enough to read as a plate. */
  color: var(--cream-soft);
  text-shadow: 0 1px 3px rgba(var(--black-rgb), 0.68);
  /* 300 - Poppins Light, the same cut the about panel's copy takes, so the two
     blocks of running copy on the page's two photographic panels now read at one
     weight. The deck came down from 700 to 400 to stop the weight doing the work
     contrast should do; this is the last step on that ramp. Poppins is served in
     discrete cuts (300/400/500/600/700), so 300 is genuinely the lightest thing
     available - a requested 450 would resolve up to 500, heavier, not lighter.
     The seat below is deepened again with the drop, on the same reasoning it was
     deepened at 400: colour contrast is unchanged by weight, but thinner strokes
     have less area to hold it against a lit, grainy ground. */
  font-weight: 300;
  /* Regular weight at this size closes the counters slightly against the
     texture behind it. A hair of tracking opens the line back up. */
  letter-spacing: 0.008em;
}

.hero-actions {
  display: flex;
  gap: 0.9rem;
  margin-top: clamp(1.8rem, 4vh, 2.8rem);
  flex-wrap: wrap;
  justify-content: flex-start;
}
/* The page's two buttons are ink-on-cream, which is the wrong way round on a
   dark ground - a black fill sinks into the plate and the ghost's black rule
   disappears into it. Same two shapes and the same spacing, inverted: the
   solid takes the cream, the ghost outlines in it, and both still land on the
   orange or the cream on hover, so the interaction reads as it does elsewhere
   on the page. */
/* The primary leads with the accent. It used to rest in cream and only reach
   the orange on hover, which spent the brand colour on a state most visitors
   never trigger and left the two hero buttons telling apart by fill alone. The
   two states are simply swapped: orange at rest, cream on hover. Ink text on
   both — ink on the orange is 5.3:1 and ink on the cream is far past it, where
   cream text on the orange would not have cleared AA at this size. */
.hero .btn-solid {
  background: var(--orange);
  color: var(--ink);
  border-color: var(--orange);
}
.hero .btn-solid:hover {
  background: var(--cream);
  color: var(--ink);
  border-color: var(--cream);
}
.hero .btn-ghost {
  color: var(--cream);
  border-color: color-mix(in srgb, var(--cream) 55%, transparent);
}
.hero .btn-ghost:hover {
  background: var(--cream);
  color: var(--ink);
  border-color: var(--cream);
}
/* one row each at full width; once a row has to wrap on a phone, balance keeps
   the two halves even instead of leaving a single word stranded.
   The two inline media chips that used to ride between these words are gone -
   the headline is plain text end to end now, so nothing here has to make room
   for artwork overhanging the cap height, and the frame has stopped moving. */
.hero-line { display: block; text-wrap: balance; }

.hero-scrollhint {
  position: absolute;
  bottom: 1.5rem;
  left: var(--gutter);
  font-family: var(--font-mono);
  font-size: var(--fs-ui-sm);
  text-transform: uppercase;
  letter-spacing: 0.2em;
  /* the black-panel muted grey, not the cream-panel one: this label now sits
     on the dark frame like the ones in the intro and footer do */
  color: var(--muted-light);
  display: flex;
  align-items: center;
  gap: 0.8rem;
}
.hint-bar {
  width: 3rem; height: 1px;
  background: color-mix(in srgb, var(--cream) 30%, transparent);
  position: relative;
  overflow: hidden;
}
.hint-bar::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--orange);
  animation: hint-slide 2s var(--ease-out) infinite;
}
@keyframes hint-slide {
  from { transform: translateX(-100%); }
  to { transform: translateX(100%); }
}

/* ============================================================
   MARQUEE
   ============================================================ */
.marquee {
  background: var(--orange);
  color: var(--ink);
  overflow: hidden;
  padding: 0.9rem 0;
  border-block: 3px solid var(--ink);
  margin-block: clamp(2rem, 5vh, 4rem) 0;
  position: relative;
  z-index: 5;
}
/* The gap above is cream breathing space between two cream-ground sections.
   Under the hero there is no cream to breathe: the strip reads as a seam
   between the dark frame and the orange bar, so the bar is butted straight up
   against the foot of the photograph instead. */
.hero + .marquee { margin-top: 0; }
/* Same case, one page over: on /services the printed field ends where the bar
   begins, and the gap above the bar is there to separate two cream sections,
   which these are not. Closing it lets the plate butt straight against the
   bar's own black rule — a seam between two materials, which is what the
   field wants at an end it does not have to fade into. */
.cream-field + .marquee { margin-top: 0; }
.marquee-track {
  display: flex;
  align-items: center;
  gap: 2.5rem;
  width: max-content;
  animation: marquee 22s linear infinite;
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-size: var(--fs-h4);
  text-transform: uppercase;
  letter-spacing: var(--ls-display);
  white-space: nowrap;
}
.marquee-track i { font-style: normal; font-size: 0.8em; }
@keyframes marquee {
  to { transform: translateX(calc(-100% / 3)); }
}

/* ============================================================
   INTRO â€” full-bleed black panel
   ============================================================ */
.intro {
  /* the studio photograph carries the panel, with two scrims stacked over it.
     Scrim one is the brand ramp: heaviest across the empty left wall, thinnest
     over the orange arch on the right, so the single warm accent in the frame
     keeps its colour instead of being flattened into the black.
     Scrim two runs vertically, and it is here because this panel centres its
     type rather than setting it left - the horizontal ramp alone leaves the
     right shoulder of the headline sitting on the lit arch. It stays thin
     through the middle so the arch and the cream stone survive, and closes to
     solid at the foot so the panel still meets the cream field below on the
     same clean black edge it always had. */
  --about-scrim-top:  0.30;
  --about-scrim-mid:  0.22;
  --about-scrim-foot: 0.50;
  --about-scrim-base: 0.86;

  background-color: var(--ink);
  background-image:
    linear-gradient(180deg,
      rgba(17, 17, 17, var(--about-scrim-top))  0%,
      rgba(17, 17, 17, var(--about-scrim-mid))  20%,
      rgba(17, 17, 17, var(--about-scrim-mid))  58%,
      rgba(17, 17, 17, var(--about-scrim-foot)) 82%,
      rgba(17, 17, 17, var(--about-scrim-base)) 100%),
    linear-gradient(90deg,
      rgba(17, 17, 17, 0.88) 0%,
      rgba(17, 17, 17, 0.72) 35%,
      rgba(17, 17, 17, 0.38) 65%,
      rgba(17, 17, 17, 0.12) 100%),
    url("../assets/img/about-section-background.webp");
  background-repeat: no-repeat;
  background-size: cover;
  /* held a little above centre: when the panel is much wider than it is tall
     the frame crops top and bottom, and the arch lives in the upper half */
  background-position: 50% 45%;
  color: var(--cream);
  padding: clamp(6rem, 14vh, 10rem) var(--gutter);
  padding-top: clamp(8rem, 18vh, 13rem);
  margin-top: calc(-1 * clamp(2rem, 5vh, 4rem));
  position: relative;
  z-index: 1;
  overflow: hidden;
}

/* Below the desk widths the panel turns portrait and the frame crops in from
   the sides instead, so the window walks right to keep the arch in it. The
   scrim thickens a step at the same time: the measure now runs nearly edge to
   edge, so more of the type lands on lit ground than it does on desktop. */
@media (max-width: 900px) {
  .intro {
    --about-scrim-top:  0.40;
    --about-scrim-mid:  0.36;
    --about-scrim-foot: 0.60;
    --about-scrim-base: 0.90;

    background-position: 65% 45%;
  }
}
@media (max-width: 560px) {
  /* phone: only about a third of the frame survives the crop, so the window
     centres on the arch itself rather than on the room */
  .intro { background-position: 72% 45%; }
}

.intro-inner {
  /* One rhythm for the whole panel: heading, both paragraphs and the sign-off
     are separated by the same step, and the button takes a clean multiple of
     it. Nothing in here sets its own ad-hoc margin. */
  --intro-gap: clamp(1.15rem, 2.6vh, 1.6rem);

  /* narrower than it was (58rem). Centred copy needs a shorter column than
     ranged copy to keep both rags readable, and the panel now carries four
     short blocks rather than two long ones. */
  max-width: 46rem;
  margin: 0 auto;
  text-align: center;
}
.intro-title {
  /* bold and unchanged: the heading is the one heavy thing in the panel, and
     the lighter copy below is what makes it read as heavy */
  text-wrap: balance;
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-size: var(--fs-h2);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
  margin-bottom: var(--intro-gap);
}
.intro-title em { font-style: normal; color: var(--orange); }

.intro-copy {
  /* a deck at --fs-lead, a step down from the h4 it used to take. The copy is
     shorter now, so it does not need the size to hold the panel — and the
     smaller step is what lets the line open up without the block growing. */
  max-width: 42ch;
  margin-inline: auto;
  font-size: var(--fs-lead);
  /* above the page's --lh-lead (1.5). Light type wants more room between the
     rows than book weight does, and this is where the block stops reading as
     a paragraph and starts reading as a statement. */
  line-height: 1.65;
  /* Poppins Light. The brief asked for 400-450, but the face is not variable:
     the cuts loaded are 300/400/500/600/700, and CSS resolves a requested 450
     upward to 500 — heavier, not lighter. 400 is what this already was. So 300
     is the only step on the ramp that actually reads lighter, and at this size
     it carries cleanly on the scrim. */
  font-weight: 300;
  /* the negative tracking came off with the weight: tightening light type is
     what makes it look thin rather than refined */
  letter-spacing: 0;
  /* The same step off full cream the hero deck takes, and for the same reason:
     this panel is a photograph too, with the arch lit behind the right shoulder
     of the measure. Held at 5.3:1 against the single brightest pixel behind the
     block and 10.8:1 across all but the top 5% of it. The sign-off line below
     stays further back again, so the panel still reads heading / copy /
     sign-off rather than two blocks at one tone. */
  color: var(--cream-soft);
  /* short centred statement, so even rows beat a ragged last one. This is also
     what keeps the breaks deliberate without a single <br> in the markup, so
     nothing snaps awkwardly on a narrow screen. */
  text-wrap: balance;
}
.intro-copy + .intro-copy { margin-top: var(--intro-gap); }

/* single closing line under the paragraph — a step down from the body so the
   panel reads headline / support / sign-off rather than three equal blocks */
.intro-close {
  max-width: 38ch;
  margin-inline: auto;
  margin-top: var(--intro-gap);
  font-size: var(--fs-body);
  line-height: 1.65;
  /* held at book weight, not the 300 above: this line is smaller and already
     stepped back to 72% of the cream, and light type at both of those at once
     stops holding its own on a photographic ground */
  font-weight: 400;
  color: color-mix(in srgb, var(--cream) 72%, transparent);
  text-wrap: balance;
}
.intro-copy u {
  text-decoration-color: color-mix(in srgb, var(--cream) 45%, transparent);
  transition: text-decoration-color 0.4s, color 0.4s;
}
.intro-copy u:hover { text-decoration-color: var(--orange); color: var(--orange); }

/* orbit button — the corner mark on the about panel.
   Drawn to the Figma reference (node 148:5384), which is a 150px frame; the
   ratios below are that frame's numbers carried straight over, so the mark
   holds its proportions at every size rather than only at one. */
.orbit-btn {
  /* tops out at the reference's own 150px */
  --size: clamp(6.5rem, 10vw, 9.375rem);
  /* Lifted out of the flow and pinned to the panel's bottom-right corner. It
     takes the same --gutter every other edge element on the page takes, so it
     lines up with the section's own margin rather than floating at a figure of
     its own. .intro is position:relative and clips, so the corner it lands in
     is the corner of the black plate. */
  position: absolute;
  right: var(--gutter);
  bottom: clamp(2.5rem, 6vh, 4.5rem);
  width: var(--size);
  height: var(--size);
  display: grid;
  place-items: center;
  /* over the panel's scrims, which sit on the section background */
  z-index: 2;
}
.orbit-ring {
  position: absolute;
  inset: 0;
  animation: spin 14s linear infinite;
}
/* The two hairlines. Stroke width is in the viewBox's user units, not px, so
   it scales down with the mark instead of holding a fixed weight as --size
   shrinks — a rule that stayed 1px while the circle halved would read heavy. */
.orbit-rule {
  fill: none;
  /* the reference strikes these in white on flat dark; here they land on a
     photograph, so the cream steps back to keep them a rule rather than a
     border cut around the mark */
  stroke: color-mix(in srgb, var(--cream) 55%, transparent);
  stroke-width: 0.93;
}
.orbit-ring text {
  font-family: var(--font-mono);
  /* 11 user units on a 150 frame, tracked to close the loop: the label runs
     twice around a 100-unit circle, and at this size and tracking the second
     pass meets the first instead of leaving a gap under the spin. */
  font-size: 11px;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  fill: var(--cream);
}
@keyframes spin { to { transform: rotate(360deg); } }
.orbit-core {
  /* 37.26 / 150 from the reference. The disc is deliberately small against the
     ring — that gap is what gives the two hairlines and the label room to read
     as three separate rings rather than one thick band. */
  width: 24.8%;
  height: 24.8%;
  background: var(--orange);
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: var(--ink);
  transition: transform 0.45s var(--ease-out), background 0.3s;
}
/* 14.26 / 37.26 — the arrow's share of the disc in the reference */
.orbit-core svg { width: 38%; }
.orbit-btn:hover .orbit-core { transform: scale(1.15) rotate(45deg); background: var(--cream); }

/* The footer's copy of the mark. It is pinned to the corner of the footer's
   top block rather than sitting in the flow, so it costs the footer no height
   of its own and lands on the same hard-right edge the directory column above
   it already ends on. A size below the about panel's, because this one closes
   the page rather than opening the argument, and it is read next to link lists
   rather than a headline. */
.orbit-btn.orbit-btn--footer {
  --size: clamp(6.05rem, 8.8vw, 8.25rem);
  /* Lifted out of the flow and pinned to the corner of the footer's top block,
     the same move the about panel's copy makes. In the flow it pushed the
     footer taller by its own height; out here it costs nothing, and it lands
     in the space the directory already leaves — the services column is the
     long one, so the corner under the connect list is empty at every width the
     two-column footer holds. Half a gutter past the container edge is as far
     out as it can go and still sit inside the footer's padding: the section
     clips, and a mark with its edge cut off is a mistake, not a bleed. */
  position: absolute;
  right: calc(var(--gutter) * -0.5);
  /* half a line under the block's foot: the band between the last link and
     the top of the wordmark is a little taller than the mark, and this centres
     it in that band rather than leaving all of the slack on one side */
  bottom: -0.5rem;
  margin: 0;
}
/* the footer's own black, not a photograph: the hairlines can hold their
   nominal weight of cream here without cutting a hard border round the mark */
.orbit-btn--footer .orbit-rule { stroke: color-mix(in srgb, var(--cream) 45%, transparent); }

/* Under this width the panel is no longer wide enough for the mark to clear
   the copy column beside it — the column is clamped at 46rem and centred, so
   the space either side of it runs out before the gutter does. It comes back
   into the flow underneath, centred, which is where it sat before the corner. */
@media (max-width: 1080px) {
  .orbit-btn {
    position: static;
    margin: clamp(2.75rem, 6vh, 4rem) auto 0;
  }
}

/* ============================================================
   CREAM FIELD — the printed ground under the services list
   The plate is an element rather than a background on the section because
   it has to run the full width of the viewport while the section stays
   clamped to the grid, and because a background on a section paints under
   every negative-index child in it (the corner mark included) instead of
   behind the section as a whole.

   The field is bounded by black on both sides — .intro above, .portfolio
   below — so the plate butts against them with no fade at either end. A fade
   here is what read as the abrupt band under the intro: it let the page cream
   show through in a strip between the black edge and the plate.

   The plate is tiled at the image's own size rather than scaled to cover.
   cream-back1 is a grain, not a picture: it carries no drawing, and it reads
   the same at every edge (its four borders and its centre are within one
   value of each other), so it repeats with nothing to give the joins away.
   Tiling is what keeps the grain at the density it was authored at - the
   field runs two to three times the image's height, so cover would blow the
   texture up by that factor and soften it into a plain wash, which is the one
   thing this surface is here to avoid.

   Nothing in the image asks for a particular anchor, so the tile starts at
   the top-left of the field and runs from there; that also keeps the phase of
   the grain fixed to the top black edge instead of shifting as the field
   grows. The colour underneath is sampled from the image, so a slow-loading
   or unsupported texture leaves the same ground rather than a different
   material.
   ============================================================ */
.cream-field {
  position: relative;
  isolation: isolate;
}
/* The plate is a class rather than an element of .cream-field because a second
   field takes the same ground: .why and .process share one below, on their own
   wrapper, so the surface can be laid with the same rule wherever it is
   wanted rather than being tied to this element. Any section
   that wants the printed ground needs three things — position, isolation, and
   this element as its first child, so it paints under the corner mark rather
   than over it. */
.cream-plate {
  position: absolute;
  inset: 0;
  left: 50%;
  z-index: -1;
  width: 100vw;
  transform: translateX(-50%);
  pointer-events: none;
  background:
    url("../assets/img/cream-back1.webp") top left / auto repeat,
    var(--cream-plate);
}

/* The field butts against a black band at both ends wherever it can, which is
   why neither plate carries a fade. The lower field no longer can: the section
   under it is cream too, so the grain and the plate's slightly warmer tone
   simply stopped, and the join read as a printed edge across the page.
   This fades the plate out into exactly the colour that follows it rather than
   into nothing - fading to transparent would reveal the body's lighter cream
   and put a pale strip where the seam had been. Painted after the plate at the
   same depth, so it lies over the texture and under everything readable. */
.cream-field--fade-out::after {
  content: "";
  position: absolute;
  z-index: -1;
  left: 50%;
  bottom: 0;
  width: 100vw;
  height: clamp(4rem, 9vh, 7rem);
  transform: translateX(-50%);
  pointer-events: none;
  background: linear-gradient(to bottom, transparent, var(--cream-deep));
}

/* ============================================================
   SERVICES — hover-preview rows
   The section keeps the page's cream ground: nothing here paints
   a surface. Every colour is pulled from the three brand tokens,
   and the hover state reuses the orange-band language the studio
   already speaks in the marquee and the label bars.
   ============================================================ */
.services {
  padding: var(--section-y) 0 clamp(2.5rem, 5vh, 4rem);
  width: 85%;
  margin: 0 auto;
  overflow: hidden; /* keeps a cursor-tracked preview from widening the page */
}

/* ---------- intro: label + heading on the left, work link on the right ---------- */
.services-head {
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 var(--gutter);
}
.showcase__intro {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem 2rem;
  align-items: flex-end;
  justify-content: space-between;
}
/* the heading column shrinks first so the work link keeps its seat on the
   same line; below ~18rem of room it drops and the link follows underneath */
.showcase__intro > div { flex: 1 1 18rem; min-width: 0; }
.services-head .section-label { margin-bottom: clamp(0.9rem, 2vh, 1.35rem); }

.showcase__heading {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  /* every section heading on the page sits on one step, --fs-h2, so the hero
     is the only type that outranks a section. Was --fs-h1. */
  font-size: var(--fs-h2);
  text-transform: uppercase;
  letter-spacing: var(--ls-display);
  line-height: var(--lh-display);
  color: var(--ink);
}
.showcase__heading em { font-style: normal; color: var(--orange); }
/* the manual break only earns its keep once the line has room to matter */
.showcase__break { display: none; }
@media (min-width: 620px) { .showcase__break { display: inline; } }

.showcase__all {
  display: inline-flex;
  align-items: center;
  gap: 0.85rem;
  flex-shrink: 0;
  padding: 0.4rem 0.4rem 0.4rem 1.4rem;
  font-family: var(--font-mono);
  font-size: var(--fs-ui);
  font-weight: 500;
  letter-spacing: var(--ls-ui);
  text-transform: uppercase;
  color: var(--ink);
  border: 1.5px solid color-mix(in srgb, var(--ink) 22%, transparent);
  border-radius: var(--radius-pill);
  transition: background 0.35s var(--ease-out), border-color 0.35s var(--ease-out),
              color 0.35s var(--ease-out), transform 0.35s var(--ease-out);
}
.showcase__all svg {
  width: 2.3rem;
  height: 2.3rem;
  padding: 0.6rem;
  flex-shrink: 0;
  border-radius: var(--radius-pill);
  background: var(--orange);
  color: var(--black);
  transition: transform 0.45s var(--ease-out);
}
.showcase__all:hover,
.showcase__all:focus-visible {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--cream);
  transform: translateY(-2px);
}
/* the corner arrow swings a quarter turn and becomes a plain "go right" */
.showcase__all:hover svg,
.showcase__all:focus-visible svg { transform: rotate(45deg); }

/* ---------- the rows ---------- */
.showcase__list {
  max-width: var(--container);
  margin: clamp(2.25rem, 5.5vh, 3.5rem) auto 0;
  padding: 0 var(--gutter);
}

.project-row {
  position: relative;
  z-index: 0;                  /* own stacking context, so hovered rows can lift */
  display: flex;
  align-items: center;
  gap: clamp(0.85rem, 2.5vw, 2.25rem);
  padding: clamp(1.15rem, 2.8vh, 1.9rem) clamp(0.4rem, 1.2vw, 1rem);
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 16%, transparent);
  color: var(--ink);
}
/* the opening rule rides the row, not the list, so every hairline in the
   stack starts and stops on the same two verticals as the heading above */
.project-row:first-child { border-top: 1px solid color-mix(in srgb, var(--ink) 16%, transparent); }
.project-row:hover,
.project-row:focus-visible {
  z-index: 3;                          /* lift the preview over its neighbours */
}
.project-row:focus-visible { outline: 2px solid var(--ink); outline-offset: -6px; }

.project-row__number {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 0.15em;
  color: var(--orange);
  transition: color 0.35s var(--ease-out), transform 0.5s var(--ease-out);
}
.project-row:hover .project-row__number,
.project-row:focus-visible .project-row__number { color: var(--ink); transform: translateX(3px); }

.project-row__title {
  position: relative;
  z-index: 0;                  /* own stacking context: the marker sits under the word */
  margin-right: auto;
  font-family: var(--font-display);
  /* the display cut, like every other heading on the page. It was a one-off
     300 before - still Poppins, but Light against a sheet set in Bold reads as
     a second typeface rather than a lighter one, which is the one thing the
     move to a single family was meant to stop. These titles are the same role
     as the FAQ questions and the process steps, so they take the same token. */
  font-weight: var(--fw-display);
  font-size: var(--fs-h3);
  text-transform: uppercase;
  /* large caps still want a little air between the letters, and the title can
     now run to three words and wrap, so it needs real leading under it */
  letter-spacing: var(--ls-caps);
  line-height: var(--lh-heading);
}
/* orange marker, hugging the words rather than washing the whole row —
   it wipes in from the left edge of the title on hover */
.project-row__title::before {
  content: "";
  position: absolute;
  inset: 0.09em -0.22em 0.07em -0.16em;
  z-index: -1;
  background: var(--orange);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.45s var(--ease-out);
}
.project-row:hover .project-row__title::before,
.project-row:focus-visible .project-row__title::before { transform: scaleX(1); }

/* The meta is a sentence now, not a two-word tag — so it drops the mono caps
   and the nowrap it used to get away with. Uppercase is for labels; a line of
   caps this long is read letter by letter. Capped well under the page measure
   so it stays a caption beside the title rather than a second paragraph. */
.project-row__meta {
  flex: 0 1 auto;
  max-width: 34ch;
  font-size: var(--fs-meta);
  line-height: var(--lh-lead);
  color: var(--muted);
  /* it is a right-hand caption column: the title pushes it over with an auto
     margin, so it has to set to the right edge or a wrapped second line leaves
     a hole between the caption and the arrow it sits against */
  text-align: right;
  text-wrap: pretty;
  transition: color 0.35s var(--ease-out);
}
.project-row:hover .project-row__meta,
.project-row:focus-visible .project-row__meta { color: var(--ink); }

.project-row__button {
  width: clamp(2.5rem, 3.6vw, 3.1rem);
  aspect-ratio: 1;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  border-radius: var(--radius-pill);
  border: 1.5px solid color-mix(in srgb, var(--ink) 20%, transparent);
  color: var(--ink);
  transition: background 0.35s var(--ease-out), border-color 0.35s var(--ease-out),
              color 0.35s var(--ease-out), transform 0.5s var(--ease-out);
}
.project-row__button svg { width: 46%; height: 46%; }
.project-row:hover .project-row__button,
.project-row:focus-visible .project-row__button {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--orange);
  transform: rotate(45deg);
}

/* cursor-tracked preview. --px / --rot are driven by js/main.js; the reveal
   itself is pure CSS, so the object still works with the script disabled.
   These are cut-out 3D marks, not photographs — so there is no card around
   them: they float over the orange wash and the drop-shadow does the seating. */
.project-row__preview {
  position: absolute;
  top: 50%;
  left: 0;
  z-index: 2;
  width: clamp(7.5rem, 12vw, 11.5rem);
  aspect-ratio: 1;
  margin: 0;
  pointer-events: none;
  opacity: 0;
  /* a slight lean, so the mark reads as tumbling along the row rather than
     parked on it — then --rot adds the lag from the pointer on top */
  transform: translate3d(var(--px, 0px), -50%, 0) rotate(-3deg) rotate(var(--rot, 0deg));
  transition: opacity 0.3s var(--ease-out);
  will-change: transform, opacity;
}
.project-row__preview img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* the shadow is cast by the cut-out itself, so it follows the silhouette
     instead of boxing it — doubled up for a tight contact + a soft throw */
  filter: drop-shadow(0 2px 4px rgba(var(--black-rgb), 0.42))
          drop-shadow(0 18px 22px rgba(var(--black-rgb), 0.22));
  transform: scale(0.72) rotate(-14deg);
  transition: transform 0.75s var(--ease-out);
}
.project-row:hover .project-row__preview,
.project-row:focus-visible .project-row__preview { opacity: 1; }
.project-row:hover .project-row__preview img,
.project-row:focus-visible .project-row__preview img { transform: scale(1) rotate(0deg); }

/* touch: there is no hover to give, so the row drops the preview altogether.
   It used to be promoted to a permanent thumbnail here, and that is what the
   marks cannot carry: they are cut-outs, each with its own silhouette and its
   own margin inside its frame, so one fixed box renders them at visibly
   different sizes down the list — the cube fills its square, the flatter marks
   sit small inside theirs, and the column reads as a mistake rather than as a
   set. Sized to match, they would need the art re-cropped, not the CSS
   adjusted. The tap still gets the wash and the title's rule.
   The pointer preview above is untouched: this branch never applies to it. */
@media (hover: none) {
  .project-row__preview { display: none; }
  .project-row:active .project-row__title::before { transform: scaleX(1); }
}

@media (max-width: 900px) {
  .project-row__meta { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .project-row__title::before { transition: none; }
  .project-row__preview img { transform: none; transition: none; }
  .project-row:hover .project-row__number,
  .project-row:hover .project-row__button,
  .project-row:focus-visible .project-row__number,
  .project-row:focus-visible .project-row__button { transform: none; }
}

/* ---------- foot ---------- */
.services-foot {
  max-width: var(--container);
  margin: clamp(1.5rem, 3.5vh, 2.5rem) auto 0;
  padding: 0 var(--gutter);
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  align-items: flex-start;
}
@media (min-width: 768px) {
  .services-foot { flex-direction: row; justify-content: space-between; align-items: center; }
}
/* Three sentences, so it is set as running copy rather than as a mono label:
   at this length the old uppercase treatment was a wall. One step under page
   body, on a short measure, so it still reads as an aside to the list above. */
.services-foot p {
  max-width: 54ch;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body);
  text-wrap: pretty;
}
.services-cta {
  height: 3.4rem; width: 3.4rem; flex-shrink: 0;
  border-radius: var(--radius-pill);
  border: 1px solid color-mix(in srgb, var(--ink) 20%, transparent);
  display: grid; place-items: center;
  color: var(--ink);
  transition: color 0.25s var(--ease-out), border-color 0.25s var(--ease-out), background 0.25s var(--ease-out), transform 0.35s var(--ease-out);
}
.services-cta svg { width: 1.3rem; transition: transform 0.3s var(--ease-out); }
.services-cta:hover {
  background: var(--orange);
  border-color: var(--orange);
  color: var(--ink);
  transform: translateY(-2px);
}
.services-cta:hover svg { transform: translateX(3px); }

/* ============================================================
   WORK / PORTFOLIO â€” black, three-column scroll gallery
   ============================================================ */
.portfolio {
  position: relative;
  isolation: isolate;
  background: var(--ink);
  color: var(--cream);
  padding: var(--section-y) var(--gutter);
  /* clip, not hidden. `hidden` would make this section a scroll container, and
     a sticky element resolves against the nearest one - which would leave the
     gallery's stage stuck to a box that never scrolls, i.e. not stuck at all.
     `clip` cuts the same bleed without creating a scrollport. */
  overflow: clip;
}
.portfolio-head {
  position: relative;
  z-index: 1;
  max-width: var(--container);
  margin: 0 auto clamp(2.5rem, 6vh, 4rem);
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem 2rem;
  align-items: flex-end;
  justify-content: space-between;
}
.portfolio-title {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  /* one step for every section heading — see .showcase__heading. Was --fs-h1. */
  font-size: var(--fs-h2);
  text-transform: uppercase;
  letter-spacing: var(--ls-display);
  line-height: var(--lh-display);
  margin-top: 0.5rem;
}
.portfolio-title span { color: var(--orange); }

/* stays a mono caption — two short clauses, which is the length uppercase can
   carry. Held to a caption measure so it never runs the width of the head. */
.portfolio-note {
  max-width: 34ch;
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: var(--fs-ui);
  text-transform: uppercase;
  letter-spacing: var(--ls-ui);
  line-height: var(--lh-body);
  color: var(--muted-light);
  text-align: right;
}
/* One sentence of the note is mode-specific. The wide one is the default, so
   nothing about the desktop note changes; the narrow one is only ever shown
   inside the reel's own query, which means reduced motion at that width gets
   neither instruction - and a static column of cards needs no instruction. */
.portfolio-note-swipe { display: none; }
@media (max-width: 900px) {
  .portfolio-note-scroll { display: none; }
}
@media (max-width: 900px) and (prefers-reduced-motion: no-preference) {
  .portfolio-note-swipe { display: inline; }
}

/* ---------- the work gallery ----------
   Three layouts, and the stylesheet owns all of them. What is written here is
   the static one: three columns of cards in ordinary flow, no stage, no
   travel, which is what reduced motion is left with. The wide scroll stage and
   the narrow reel are both switched on by media queries further down rather
   than by script, so the height the section occupies is settled in the first
   layout pass and nothing below it moves when main.js starts measuring. */
.pgal {
  position: relative;
  z-index: 1;
  max-width: var(--container);
  margin: clamp(2rem, 5vh, 3.5rem) auto 0;
}
.pgal-stage {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1.4rem, 2.2vw, 2.25rem);
  align-items: start;
}
/* The columns run past both ends of the stage, so they are dissolved into the
   black rather than cut on a line - and the top fade doubles as the ground the
   fixed header sits on while the section is on screen.
   These are painted caps rather than a mask on the stage. What they show is
   identical: masking to transparent here would reveal this section's flat ink,
   and flat ink is exactly what the gradients paint. What they cost is not - a
   mask makes the whole stuck screen a masked layer for the compositor to redo
   every frame of the scrub, where two gradient strips are painted once and
   then only composited. Shown only where there is a stage; the static
   fallbacks have no edge to soften. */
.pgal-stage::before,
.pgal-stage::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  z-index: 3;
  display: none;
  pointer-events: none;
}
.pgal-stage::before {
  top: 0;
  height: 9%;
  background: linear-gradient(180deg, var(--ink), transparent);
}
.pgal-stage::after {
  bottom: 0;
  height: 10%;
  background: linear-gradient(0deg, var(--ink), transparent);
}
.pgal-track {
  display: flex;
  flex-direction: column;
  gap: clamp(1.4rem, 2.2vw, 2.25rem);
}

/* the card keeps the old showreel's plate, reflowed into a column: the same top-lit
   surface and the same soft-UI pair moulding it out of the black ground */
.pgal-card {
  position: relative;
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--cream) 7%, var(--charcoal)) 0%,
    var(--charcoal) 42%
  );
  border-radius: var(--radius-card);
  overflow: hidden;
  box-shadow:
    inset 0 1px 0 rgba(var(--cream-rgb), 0.09),
    inset 0 -1px 0 rgba(0, 0, 0, 0.5),
    var(--shadow-soft-raised),
    0 2px 6px -2px rgba(0, 0, 0, 0.7),
    0 26px 44px -26px rgba(0, 0, 0, 0.95);
  transition: box-shadow 0.55s var(--ease-out);
}
/* The card does not lift on hover the way the old reel card did. Here it is moving
   under the cursor for the whole of the scrub, and a translate on hover would
   fire and unfire every time a column carried a card past the pointer. What is
   left is everything that reads without moving the box: the image pushes in,
   the scrim lifts, the chip fills. */
.pgal-card:hover {
  box-shadow:
    inset 0 1px 0 rgba(var(--cream-rgb), 0.14),
    inset 0 -1px 0 rgba(0, 0, 0, 0.5),
    -7px -7px 20px rgba(var(--cream-rgb), 0.07),
    0 4px 10px -4px rgba(0, 0, 0, 0.75),
    0 34px 56px -30px rgba(0, 0, 0, 1),
    0 22px 54px -30px rgba(var(--orange-rgb), 0.5);
}

/* Every frame is cut to its own image's ratio, passed in as --ar on the card,
   so `cover` crops nothing and the columns take their rhythm from the work
   rather than from one imposed box - a 3:2 screenshot sits next to a square
   product shot and neither is letterboxed or trimmed to fit the other. */
.pgal-media {
  position: relative;
  aspect-ratio: var(--ar, 3 / 2);
  background: var(--black);
  overflow: hidden;
}
.pgal-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  -webkit-user-drag: none;
  transition: transform 0.8s var(--ease-out);
}
.pgal-card:hover .pgal-media img { transform: scale(1.04); }

/* the scrim carries the well too: an inset shadow drawn over the image, which
   is the only way to recess a replaced element - it paints above its own
   background, so an inset on .pgal-media would sit behind the picture */
.pgal-media::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(180deg, transparent 58%, rgba(0, 0, 0, 0.42) 100%);
  box-shadow: var(--shadow-soft-pressed);
  opacity: 0.78;
  transition: opacity 0.5s var(--ease-out);
  pointer-events: none;
}
.pgal-card:hover .pgal-media::after { opacity: 0.5; }

/* the Qwick card is one app on two phones. Each render is 3:4 and half of a
   3:2 box is exactly 3:4, so the pair tiles the frame edge to edge with no
   crop and no letterbox; both plates are shot on black, so the seam between
   them and the frame's own black is invisible. */
.pgal-media--duo img {
  position: absolute;
  top: 0;
  width: 50%;
  height: 100%;
}
.pgal-media--duo img:first-of-type { left: 0; }
.pgal-media--duo img:last-of-type { left: 50%; }

/* label over name, the same order the page uses everywhere else: a mono
   eyebrow and then the thing it names. Stacked rather than run on one line so
   a two-word project never has to be truncated in a narrow column. */
.pgal-foot {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.95rem 1.05rem 1.1rem;
}
.pgal-pill {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-ui);
  color: var(--orange);
  border: 1px solid color-mix(in srgb, var(--orange) 45%, transparent);
  border-radius: var(--radius-pill);
  padding: 0.4em 0.85em;
  /* the chip is moulded from the card face, same light as the plate */
  background: linear-gradient(180deg,
              color-mix(in srgb, var(--cream) 5%, var(--charcoal)),
              var(--charcoal));
  box-shadow:
    -2px -2px 5px var(--soft-light),
    2px 3px 7px var(--soft-dark),
    inset 0 1px 0 rgba(var(--cream-rgb), 0.07);
  transition: background 0.35s var(--ease-out), color 0.35s var(--ease-out),
              border-color 0.35s var(--ease-out);
}
.pgal-card:hover .pgal-pill {
  background: var(--orange);
  border-color: var(--orange);
  color: var(--ink);
  box-shadow: inset 0 1px 0 rgba(var(--white-rgb), 0.25);
}
.pgal-name {
  font-size: var(--fs-body-sm);
  font-weight: 500;
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-heading);
  color: var(--cream);
}

/* ---------- the scroll stage, wide ----------
   One screen, stuck to the top of the viewport, with --pgal-scroll of page
   height running under it. The three tracks are scrubbed through that height
   by js/main.js: the outer two start wound to their foot and unwind downward,
   so they travel against the page, and the middle one runs the ordinary way.
   The query is the same string main.js passes to matchMedia(), so the sheet
   and the script can never disagree about which mode the section is in. */
@media (min-width: 901px) and (prefers-reduced-motion: no-preference) {
  .pgal {
    /* The travel, reserved as page height here and nowhere else.
       It is set to one number: the distance the TALLEST column has to cover.
       Do that and the arithmetic falls out - that column moves its own height
       over exactly that much page, which is 1:1 with the scroll, so the middle
       column reads as ordinary page scroll at every screen size rather than
       only at the one this was tuned on. The two outer columns are shorter, so
       they come back down at 0.8 and 0.9 of it, which is the opposing move.
       A plain vh figure cannot do this. Column height follows the viewport's
       WIDTH (the cards are a fraction of it) while a vh figure follows its
       height, so the two come apart the moment the window is not the shape it
       was tuned on - the same sheet ran at 0.2x on a 2560x1440 screen and 1.5x
       on a 1440x700 one before this.
       So the column is modelled instead. Its height is linear in viewport
       width - 114vw + 346px fits the twelve cards authored below to within a
       few px from 1024 up - until .pgal reaches its 84rem cap, past which the
       columns stop growing and the height plateaus at 2010px. Subtract the
       screen the stage occupies and that is the travel.
       These three numbers describe THIS set of cards. Add or remove one, or
       change the aspect of one, and they want re-measuring: the console will
       tell you, since the number wanted is just the tallest track's height
       less the stage's. */
    --pgal-scroll: max(0px, min(114vw + 346px, 2010px) - 100svh);

    height: calc(100svh + var(--pgal-scroll));
  }
  .pgal-stage {
    position: sticky;
    top: 0;
    height: 100svh;
    overflow: hidden;  }
  .pgal-stage::before,
  .pgal-stage::after { display: block; }
  /* While a column is moving its cards do not take the pointer. A card sliding
     under a still cursor would otherwise pick up and drop :hover over and over,
     and the hover state animates a large blurred shadow - the one thing on the
     card that costs a real repaint. Profiling a scrub with the cursor resting
     on a column showed twice as many long frames as one with it off to the
     side. The flag comes off the moment the columns settle, so hovering a card
     you have actually stopped on is unaffected. */
  .pgal-stage.is-moving .pgal-card { pointer-events: none; }
  /* the browser is told once that these move, rather than working it out from
     the first transform of every scrub */
  .pgal-track { will-change: transform; }
}

/* Out of the page in every mode but the reel's — see the rule inside the reel
   query, which is the only place it is shown. display:none rather than
   visibility so it is out of the tab order too: a control for a row that is not
   moving is worse than no control. */
.pgal-play { display: none; }

/* ---------- the reel, narrow ----------
   No stage at all below 901px. Two columns of small cards travelling against
   each other is a great deal of movement inside a phone's width, and a card
   two fifths of the screen wide is a thumbnail — twelve of them read as a
   texture rather than as twelve pieces of work. So the columns are unwrapped
   with display:contents, which drops all twelve cards into .pgal-stage as one
   flex row in document order, and that row drifts leftward with a finger able
   to throw it along.
   One card at a time, near enough the width of the screen, and the section
   costs its own height instead of a screen plus two and a half of travel.
   Everything below the query is the mobile view only: the wide stage above is
   untouched by any of it. */
@media (max-width: 900px) and (prefers-reduced-motion: no-preference) {
  .pgal {
    /* full width rather than inside the section's gutter: the row is meant to
       run off both edges of the screen, so the screen is what it is measured
       in. It is also the clip — the row itself is what moves, and a moving box
       cannot be its own window. */
    max-width: none;
    margin-inline: calc(-1 * var(--gutter));
    overflow: hidden;
    /* the card plates carry a shadow that falls below them; without this the
       clip would cut it off square against the section ground */
    padding-block: 0 clamp(0.9rem, 3vw, 1.5rem);
    /* Across is the reel's, down is still the page's. Declaring it means a
       vertical swipe scrolls the page with no wait for the handler to decide,
       and a horizontal one comes through as pointer events the reel can read —
       so the reel never has to cancel a gesture to claim it. */
    touch-action: pan-y;
    cursor: grab;
  }
  .pgal.is-dragging { cursor: grabbing; }
  .pgal-stage {
    /* the row, and the thing that moves. width:max-content so it is as long as
       its cards rather than as wide as the screen it overflows. */
    display: flex;
    align-items: stretch;
    width: max-content;
    gap: clamp(0.7rem, 2.4vw, 1.1rem);
    will-change: transform;
    /* One height for every card's picture, and the width left to follow each
       image's own ratio — which is the same bargain the wide columns strike
       the other way up. Nothing is cropped: a screenshot comes out wide, a
       square mockup square, a portrait narrow, and the row keeps one unbroken
       line along its top and bottom instead of a ragged one. */
    --reel-h: clamp(9.5rem, 44vw, 16rem);
  }
  /* the columns stop being boxes: their cards become the row's own children */
  .pgal-col,
  .pgal-track { display: contents; }
  .pgal-card {
    /* the card is as wide as its picture is at that height. --ar is a pair of
       numbers, so it multiplies straight into the calc. */
    flex: 0 0 auto;
    width: calc(var(--reel-h) * var(--ar));
  }
  /* the top and bottom fades belong to the wide stage; across here they are on
     .pgal, which is the part that holds still */
  .pgal-stage::before,
  .pgal-stage::after { display: none; }
  /* The row runs past both ends of the screen, so it is dissolved into the
     section's ink rather than cut on a line. Painted strips rather than a mask
     on .pgal for the same reason the wide stage paints its own: what they show
     is identical over a flat ground, and a mask would make the whole clipping
     box a masked layer for the compositor to redo every frame the row moves. */
  .pgal::before,
  .pgal::after {
    content: "";
    position: absolute;
    inset-block: 0;
    z-index: 3;
    width: clamp(1.25rem, 5vw, 2.5rem);
    pointer-events: none;
  }
  .pgal::before {
    left: 0;
    background: linear-gradient(90deg, var(--ink), transparent);
  }
  .pgal::after {
    right: 0;
    background: linear-gradient(270deg, var(--ink), transparent);
  }
  /* the caption is set to a card around two thirds the desktop width: the name
     drops to the caption step so a two-word project still sets on one or two
     lines rather than four, and the padding comes in to match */
  .pgal-foot { gap: 0.45rem; padding: 0.7rem 0.75rem 0.8rem; }
  .pgal-name { font-size: var(--fs-meta); }
  /* a card the finger is throwing is not a card the finger is choosing: the
     hover states are for a mouse, and a phone would otherwise stick them on
     for as long as the touch lasts */
  .pgal-card { pointer-events: none; }
  /* The stop. Keyed to is-reel, which the script only sets while the row is
     actually drifting, and it sits outside .pgal so the reel's own drag handler
     never sees the tap. Set as a quiet mono chip under the right end of the
     row: it is a control, not a call, so it takes the muted ink and lights to
     the accent only on the press. */
  .pgal.is-reel + .pgal-play {
    /* a flex box rather than a block so the chip can be given a real target
       height - the mono micro type would otherwise set a control 26px tall,
       which is a poor thing to hand someone who needs it */
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 2.75rem;
    width: fit-content;
    margin: clamp(0.75rem, 2.5vw, 1.1rem) 0 0 auto;
    font-family: var(--font-mono);
    font-size: var(--fs-micro);
    text-transform: uppercase;
    letter-spacing: var(--ls-ui);
    color: var(--muted-light);
    background: transparent;
    border: 1px solid rgba(var(--cream-rgb), 0.22);
    border-radius: var(--radius-pill);
    padding: 0 1.5em;
    cursor: pointer;
    transition: color 0.3s var(--ease-out), border-color 0.3s var(--ease-out);
  }
  .pgal.is-reel + .pgal-play:hover,
  .pgal.is-reel + .pgal-play:focus-visible {
    color: var(--orange);
    border-color: color-mix(in srgb, var(--orange) 55%, transparent);
  }
}

/* ---------- reduced motion, narrow: one column ----------
   No reel either — a row that drifts on its own is exactly what this
   preference is asking not to be given. The columns are unwrapped with
   display:contents and all twelve cards fall into a single file in document
   order, which is already interleaved across the three columns rather than
   three runs of one kind of work. Capped to a reading width so a square mockup
   does not become the largest thing on the page. (Reduced motion on a wide
   screen keeps the plain three-column grid the base rules already describe.) */
@media (max-width: 900px) and (prefers-reduced-motion: reduce) {
  .pgal-stage {
    grid-template-columns: minmax(0, 1fr);
    max-width: 34rem;
    margin-inline: auto;
  }
  .pgal-col,
  .pgal-track { display: contents; }
}

.portfolio-foot {
  position: relative;
  z-index: 1;
  max-width: var(--container);
  margin: clamp(2rem, 5vh, 3.5rem) auto 0;
  display: flex;
  justify-content: center;
}
.portfolio-cta {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: var(--fs-ui);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--ls-ui);
  color: var(--orange);
  border: 1.5px solid color-mix(in srgb, var(--orange) 50%, transparent);
  border-radius: var(--radius-pill);
  padding: 0.9em 1.8em;
  background: linear-gradient(180deg,
              color-mix(in srgb, var(--cream) 5%, var(--charcoal)),
              var(--charcoal));
  box-shadow: var(--shadow-soft-raised), inset 0 1px 0 rgba(var(--cream-rgb), 0.07);
  transition: background 0.3s var(--ease-out), color 0.3s var(--ease-out),
              box-shadow 0.35s var(--ease-out), transform 0.35s var(--ease-out);
}
.portfolio-cta svg { width: 1.05rem; transition: transform 0.3s var(--ease-out); }
.portfolio-cta:hover {
  background: var(--orange);
  color: var(--ink);
  border-color: var(--orange);
  transform: translateY(-2px);
  box-shadow: var(--shadow-soft-raised),
              0 14px 34px -18px rgba(var(--orange-rgb), 0.75);
}
.portfolio-cta:hover svg { transform: translateX(4px); }
/* the button takes the press: raised state swaps for the inset pair */
.portfolio-cta:active { transform: translateY(0); box-shadow: var(--shadow-soft-pressed); }

@media (max-width: 560px) {
  .portfolio-note { text-align: left; margin-left: 0; }
}

/* ============================================================
   WHY CHOOSE US — cream, image cluster left / the argument right

   The band that answers "why you" before .process answers "how". It shares
   one .cream-field with .process rather than carrying a plate of its own, so
   the printed ground runs unbroken across both and the grain never restarts
   mid-field. Only position and isolation are needed here: they hold the
   corner mark above the wrapper's plate, since both sit at z-index -1 and
   the isolation is what stops this section's mark falling behind it.

   The composition is the Figma's, translated into the page's own language.
   Every overlap is a percentage of the stage rather than a fixed offset, so
   the cluster keeps its proportions from 1440 down to the point where there
   is no longer room to overlap in at all — at which point the two loose
   pieces stop floating and stack under the plate instead.
   ============================================================ */
.why {
  position: relative;
  isolation: isolate;
  padding: var(--section-y) var(--gutter) clamp(3rem, 7vh, 5rem);
  max-width: var(--container);
  margin: 0 auto;
}
/* the label introduces the whole band rather than a heading directly beneath
   it, so it keeps the wider shared gap rather than the tight head gap */
.why > .section-label { margin-bottom: clamp(2rem, 5vw, 3.25rem); }

.why-grid {
  display: grid;
  /* the stage is given slightly less than the copy: a photo carries at a
     smaller size than a paragraph does, and the copy column has to hold a
     heading, a deck, a plate and a footer row without any of them cramping */
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1fr);
  gap: clamp(2.5rem, 6vw, 5.5rem);
  align-items: center;
}

/* ---------- the image cluster ---------- */
.why-stage {
  position: relative;
  /* the room the framed inset hangs into below the main plate */
  padding-bottom: clamp(2rem, 6%, 3.5rem);
}
.why-shot {
  position: relative;
  margin: 0;
  overflow: hidden;
  border-radius: var(--radius-card);
}
.why-shot img { width: 100%; height: 100%; object-fit: cover; }

/* The main plate is indented from the left so the stat card has an edge to
   break without hanging into the page gutter — the Figma hangs it outside the
   column, which a fluid grid cannot do without risking overflow at the narrow
   end. Indenting the photo instead buys the same overlap and stays on grid. */
.why-shot--main {
  margin-left: 14%;
  aspect-ratio: 460 / 514;
  box-shadow: var(--shadow-card);
}

/* the second photo, framed in the accent and dropped below the plate's
   bottom-right corner so the group has a diagonal to read along */
.why-shot--inset {
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: 1;
  width: 42%;
  aspect-ratio: 203 / 257;
  border: 4px solid var(--orange);
  box-shadow: var(--shadow-card-hover);
}

/* Until the 560px block below, this wrapper generates no box at all: the
   inset and the stat card resolve their absolute positions against .why-stage
   exactly as if they were its own children. It exists only so both can be
   pulled out of the composition together at the narrow end. */
.why-cluster { display: contents; }

/* the one lit surface in the section — orange plate, ink type, which is the
   page's accent convention (see .btn-accent) rather than the white-on-orange
   the reference used */
.why-stat {
  position: absolute;
  left: 0;
  top: 14%;
  z-index: 2;
  width: min(58%, 15.5rem);
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 0.85rem;
  padding: 1.05rem 1.2rem;
  background: var(--orange);
  color: var(--ink);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card-hover);
}
/* the exported glyph is white, which is the one colour that cannot sit on the
   accent. brightness(0) drives any colour to black while leaving the alpha
   alone, so the icon lands on the same ink as the type beside it. */
.why-stat__ico {
  width: clamp(1.9rem, 3.2vw, 2.6rem);
  height: auto;
  filter: brightness(0);
  opacity: 0.82;
}
.why-stat__body { min-width: 0; }
.why-stat__num {
  display: block;
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-size: var(--fs-h4);
  line-height: 1;
  letter-spacing: var(--ls-heading);
}
.why-stat__label {
  display: block;
  margin-top: 0.35rem;
  font-family: var(--font-mono);
  font-size: var(--fs-ui-sm);
  text-transform: uppercase;
  letter-spacing: var(--ls-ui);
  line-height: 1.4;
  /* the label is a fixed six words set in tracked caps, so it lands within a
     word of the card's width at almost every size and drops a one-word last
     line. Balanced, it splits evenly instead. */
  text-wrap: balance;
}

/* ---------- the argument ---------- */
.why-copy { min-width: 0; }
.why-title {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-size: var(--fs-h2);
  text-transform: uppercase;
  letter-spacing: var(--ls-display);
  line-height: var(--lh-display);
  text-wrap: balance;
}
.why-title span { color: var(--orange); }

.why-lead {
  max-width: var(--measure);
  margin-top: clamp(1.1rem, 2.5vh, 1.6rem);
  color: var(--muted);
  line-height: var(--lh-body);
  text-wrap: pretty;
}

/* The three proofs take a dark plate on the cream, which is the same contrast
   the reference used and the reverse of the ground around it — the one block
   in the band you cannot skim past. The accent rule down its leading edge is
   the label bar at section scale. */
.why-points {
  margin-top: clamp(1.8rem, 4vh, 2.6rem);
  padding: clamp(1.35rem, 3vw, 1.85rem) clamp(1.35rem, 3vw, 1.9rem);
  background: var(--charcoal);
  border-left: 4px solid var(--orange);
  border-radius: 0 var(--radius-card) var(--radius-card) 0;
  box-shadow: var(--shadow-card);
  display: grid;
  gap: 0.8rem;
}
.why-points li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.7rem;
  align-items: start;
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body);
  color: var(--cream-mute);
  text-wrap: pretty;
}
.why-points svg {
  width: 1.15rem;
  height: 1.15rem;
  color: var(--orange);
  /* optically centred on the first line rather than its box */
  margin-top: 0.3em;
}
.why-points strong { color: var(--cream); font-weight: 600; }

/* ---------- footer row: the call, and who is behind it ---------- */
.why-foot {
  margin-top: clamp(1.8rem, 4vh, 2.6rem);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(1.1rem, 3vw, 2.25rem);
}
.why-sign {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 0.9rem;
}
/* the ring is drawn in shadow rather than in border and padding: a padded
   border would round the border box and leave the photo square inside it */
.why-sign img {
  width: clamp(3rem, 5vw, 3.6rem);
  height: clamp(3rem, 5vw, 3.6rem);
  border-radius: var(--radius-pill);
  object-fit: cover;
  box-shadow: 0 0 0 3px var(--cream-plate), 0 0 0 5px var(--orange);
}
.why-sign__name {
  display: block;
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-size: var(--fs-body-sm);
  text-transform: uppercase;
  letter-spacing: var(--ls-caps);
  line-height: 1.2;
}
.why-sign__role {
  display: block;
  margin-top: 0.2rem;
  font-family: var(--font-mono);
  font-size: var(--fs-ui-sm);
  text-transform: uppercase;
  letter-spacing: var(--ls-ui);
  color: var(--muted);
}

/* ============================================================
   PROCESS
   The section takes the same printed ground as the services field, and shares
   it with .why: the plate is no longer this section's own child but the
   wrapper's, and one plate spans the pair. What stays here is the position
   and the isolation — each keeps the corner mark above that plate rather than
   behind it, since both sit at z-index -1.
   ============================================================ */
.process {
  position: relative;
  isolation: isolate;
  padding: var(--section-y) var(--gutter);
  max-width: var(--container);
  margin: 0 auto;
}
/* the label sat alone above the steps before; it now introduces a heading, so
   it takes the tighter label gap the other section heads use */
.process-head { max-width: 46rem; }
.process-head .section-label { margin-bottom: clamp(0.9rem, 2vh, 1.35rem); }
.process-title {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-size: var(--fs-h2);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
  text-wrap: balance;
  margin-bottom: clamp(2rem, 5vw, 3.5rem);
}

.process-list {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0;
  counter-reset: step;
  border-top: 2px solid var(--ink);
}
.process-step {
  position: relative;
  overflow: hidden;
  padding: 2rem 1.75rem 2.5rem 0;
  border-right: 1px solid color-mix(in srgb, var(--ink) 15%, transparent);
  padding-left: 1.75rem;
}
.process-step:first-child { padding-left: 0; }
.process-step:last-child { border-right: none; }

/* soft accent glow behind each step - a warm bloom from the icon corner,
   faint enough to read as light on the cream, not as a filled card. It
   lifts a touch on hover so the whole step warms with the icon. */
.process-step::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(70% 55% at 82% 6%, rgba(var(--orange-rgb), 0.10) 0%, transparent 62%),
    radial-gradient(85% 65% at 10% 104%, rgba(var(--orange-rgb), 0.05) 0%, transparent 70%);
  opacity: 0.8;
  transition: opacity 0.45s var(--ease-out);
}
.process-step:hover::before { opacity: 1; }

/* decorative watermark icon, top-right â€” neutral by default, blooms orange on hover */
.process-ico {
  position: absolute;
  top: 1.6rem;
  right: 1.25rem;
  width: clamp(2.6rem, 4.5vw, 3.6rem);
  height: auto;
  z-index: 0;
  opacity: 0.14;
  filter: grayscale(1) brightness(0.35);
  transition: opacity 0.4s var(--ease-out), filter 0.4s var(--ease-out), transform 0.5s var(--ease-out);
  pointer-events: none;
}
/* Listen (01) and Build (03) icons 10% larger */
.process-step:nth-child(1) .process-ico,
.process-step:nth-child(3) .process-ico {
  width: clamp(2.86rem, 4.95vw, 3.96rem);
}
.process-step:last-child .process-ico { right: 0; }
.process-step:hover .process-ico {
  opacity: 0.85;
  filter: grayscale(0) brightness(1);
  transform: translateY(-3px) rotate(-4deg);
}

.process-num, .process-step h3, .process-step p { position: relative; z-index: 1; }
.process-num {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  /* --fs-h1 taken down 8%. Scaling the token rather than pinning a size keeps
     the step fluid, so the numeral still interpolates with the viewport and
     still sits in a fixed relation to every other h1-scale thing on the page -
     it is just a consistent 8% quieter than it was at each width. The stroke
     stays at 1.5px: 8% of it is a sixth of a pixel, and hairline strokes below
     1.5 render unevenly across browsers. */
  font-size: calc(var(--fs-h1) * 0.92);
  color: transparent;
  -webkit-text-stroke: 1.5px var(--ink);
  display: block;
  line-height: 1;
  transition: color 0.4s;
}
.process-step:hover .process-num { color: var(--orange); -webkit-text-stroke-color: var(--orange); }
.process-step h3 {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-size: var(--fs-h5);
  margin: 1.2rem 0 0.5rem;
  text-transform: uppercase;
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-caps);
}
.process-step p {
  /* four columns hold the measure on their own, but the list drops to two
     columns at 900 and one at 720 — where a step would otherwise run the full
     container width */
  max-width: var(--measure);
  font-size: var(--fs-body-sm);
  color: var(--muted);
  line-height: var(--lh-body);
  text-wrap: pretty;
}

/* ============================================================
   FAQ + CONTACT
   ============================================================ */
.connect {
  background: var(--cream-deep);
  padding: var(--section-y) var(--gutter);
  border-top: 1px solid color-mix(in srgb, var(--ink) 10%, transparent);
}
.connect-grid {
  max-width: var(--container);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(2.5rem, 6vw, 6rem);
  align-items: start;
}
.connect-heading {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-size: var(--fs-h2);
  text-transform: uppercase;
  letter-spacing: var(--ls-display);
  line-height: var(--lh-display);
  margin-bottom: clamp(2rem, 5vh, 3.5rem);
}
.connect-heading-light { color: var(--cream); }

/* ---- FAQ accordion ----
   Each question is its own raised card rather than a row in one ruled list.
   The column sits beside a solid dark plate now, and a single hairline-ruled
   list reads as unbuilt next to that much mass - a stack of separate cards
   answers it with weight of its own. The card carries the horizontal padding
   and the button keeps the vertical, so an open answer stays inside the same
   plate as the question that opened it. */
.faq-list {
  display: flex;
  flex-direction: column;
  gap: 0;
  /* the heavier rule that opens the list, the way a printed table of contents
     opens on one */
  border-top: 2px solid var(--ink);
}
.faq-item {
  background: none;
  border: 0;
  /* one hairline between questions and nothing else: no plate, no corner, no
     drop. The list sits straight on the warm ground it is printed on. */
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 16%, transparent);
  border-radius: 0;
  padding: 0;
  box-shadow: none;
}
/* the open row takes the accent on its question only — the rule underneath is
   structure, and tinting it would make the list look like it had lost a row */
.faq-item.open .faq-q { color: var(--orange); }

.faq-q {
  width: 100%;
  background: none;
  border: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  text-align: left;
  padding: clamp(1.4rem, 3vh, 2rem) 0;
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-size: var(--fs-h5);
  text-transform: uppercase;
  /* caps at 22px want a positive fit — the display tracking is cut for 40px+
     and closes these questions up */
  letter-spacing: var(--ls-caps);
  line-height: var(--lh-heading);
  color: var(--ink);
  transition: color 0.3s var(--ease-out);
}
.faq-q:hover { color: var(--orange); }

/* plus icon that rotates to an x on open */
.faq-icon {
  position: relative;
  width: 1.1rem;
  height: 1.1rem;
  flex-shrink: 0;
  transition: transform 0.35s var(--ease-out);
}
.faq-icon::before,
.faq-icon::after {
  content: "";
  position: absolute;
  background: var(--orange);
  border-radius: 2px;
  transition: transform 0.35s var(--ease-out);
}
.faq-icon::before { top: 50%; left: 0; width: 100%; height: 2px; transform: translateY(-50%); }
.faq-icon::after { left: 50%; top: 0; width: 2px; height: 100%; transform: translateX(-50%); }
.faq-item.open .faq-icon { transform: rotate(135deg); }

.faq-a {
  overflow: hidden;
  height: 0;
  opacity: 0;
  transition: height 0.35s var(--ease-out), opacity 0.3s var(--ease-out);
}
.faq-item.open .faq-a { opacity: 1; }
.faq-a-inner { padding-bottom: clamp(1.4rem, 3vh, 2rem); }
.faq-a-inner p {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--muted);
  max-width: var(--measure);
  text-wrap: pretty;
}

/* ---- contact panel ---- */
.contact-panel {
  background: var(--ink);
  color: var(--cream);
  border-radius: var(--radius-panel);
  padding: clamp(2rem, 4vw, 3.2rem);
}
.contact-panel .section-label { color: var(--muted-light); }

/* gap was 2rem. Each field already reserves a line under it for its validation
   message, so the space between one control and the next label was that reserve
   plus a full 2rem on top - the stack read padded rather than airy. */
.contact-form { display: flex; flex-direction: column; gap: 1.3rem; }

.field { position: relative; }

/* Two controls to a line - the first/last name pair, which is the only pair in
   the form whose labels are short enough to hold a half column at every width
   the panel reaches. The column gap is the same 1.3rem the form stacks with, so
   a pair reads as one interval wide rather than a tighter or looser one, and
   grid rows keep the two halves' baselines together even when only one of them
   has an error message showing. */
.field-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.3rem;
}
.field input,
.field textarea,
.field select {
  width: 100%;
  background: none;
  border: none;
  border-bottom: 1.5px solid color-mix(in srgb, var(--cream) 30%, transparent);
  /* 0.7rem before. This lands the text controls at 45px, a pixel over the 44px
     floor for a comfortable touch target, so the padding does not come down any
     further. The select renders ~4px shorter than the rest whatever it is given
     - a UA line-height difference that predates this - which is why the arrow
     below is positioned against the select's own box rather than a shared one. */
  padding: 0.55rem 0;
  font-family: var(--font-body);
  font-size: var(--fs-body);
  color: var(--cream);
  border-radius: 0;
  transition: border-color 0.3s var(--ease-out);
}
.field textarea { resize: vertical; min-height: 3rem; }
.field input:focus,
.field textarea:focus,
.field select:focus { outline: none; border-bottom-color: var(--orange); }

/* Autofill.
   A browser that has filled a field paints its own plate and its own dark text
   over it - Chrome a pale blue, Firefox a pale yellow - which on this panel
   reads as a lit box dropped onto the black, with the value in near-black on
   top of it. background-color cannot be set on a filled control, the UA rule
   wins, so the plate is covered instead: an inset shadow with a spread wide
   enough to flood the box, in the same --ink the panel is painted with, so it
   is invisible against it. The glyphs need -webkit-text-fill-color rather than
   color, which the UA style also overrides, and the long transition outruns the
   UA's own fade back to its colour after the field is filled.

   Two rules rather than one list: :autofill is the standard pseudo-class and
   :-webkit-autofill the prefixed one, and a browser that does not know a
   selector throws out the whole rule it appears in - so listing them together
   would lose the declarations in every browser that only knows one of them. */
.field input:-webkit-autofill,
.field input:-webkit-autofill:hover,
.field input:-webkit-autofill:focus,
.field textarea:-webkit-autofill,
.field select:-webkit-autofill {
  -webkit-text-fill-color: var(--cream);
  box-shadow: 0 0 0 100px var(--ink) inset;
  caret-color: var(--cream);
  transition: background-color 9999s ease-in-out 0s;
}
.field input:autofill,
.field input:autofill:hover,
.field input:autofill:focus,
.field textarea:autofill,
.field select:autofill {
  -webkit-text-fill-color: var(--cream);
  box-shadow: 0 0 0 100px var(--ink) inset;
  caret-color: var(--cream);
  transition: background-color 9999s ease-in-out 0s;
}

/* floating labels */
.field label {
  position: absolute;
  left: 0;
  /* matches the control's top padding: the resting label has to sit on the same
     line the typed value will, so this moves whenever that padding does */
  top: 0.55rem;
  font-family: var(--font-mono);
  font-size: var(--fs-body-sm);
  letter-spacing: 0.02em;
  color: var(--muted-light);
  pointer-events: none;
  transition: transform 0.28s var(--ease-out), font-size 0.28s var(--ease-out), color 0.28s var(--ease-out);
  transform-origin: left center;
}
.field input:focus + label,
.field input:not(:placeholder-shown) + label,
.field textarea:focus + label,
.field textarea:not(:placeholder-shown) + label,
.field select:focus + label,
.field select:valid + label {
  /* 1.6rem before. The label now starts 0.15rem higher, so the float shortens by
     the same amount and the raised label ends up exactly where it used to. */
  transform: translateY(-1.45rem);
  font-size: var(--fs-ui-sm);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--orange);
}

/* custom select */
.field select {
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  padding-right: 2rem;
}
.field select option { background: var(--ink); color: var(--cream); }
.field-select { position: relative; }
.select-arrow {
  position: absolute;
  right: 0.2rem;
  /* centred on a 44px control rather than the old 49px one */
  top: 0.85rem;
  width: 0.7rem;
  height: 0.7rem;
  border-right: 2px solid var(--orange);
  border-bottom: 2px solid var(--orange);
  transform: rotate(45deg);
  pointer-events: none;
  transition: transform 0.3s var(--ease-out);
}
.field-select:focus-within .select-arrow { transform: rotate(225deg); top: 1.05rem; }

/* validation */
.field-msg {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-ui-sm);
  letter-spacing: 0.06em;
  color: var(--orange);
  margin-top: 0.3rem;
  /* Left at 0.9rem on purpose while everything around it came down. This is the
     space held open for a validation message so the form does not jump when one
     appears, and it is already 5px short of the line it has to hold - taking it
     lower to save height would buy ~6px and make that jump worse. */
  min-height: 0.9rem;
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity 0.25s var(--ease-out), transform 0.25s var(--ease-out);
}
.field.invalid .field-msg { opacity: 1; transform: translateY(0); }
.field.invalid input,
.field.invalid textarea,
.field.invalid select { border-bottom-color: var(--orange); }

/* submit â€” accent button on the dark panel */
.form-submit {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  background: var(--orange);
  color: var(--ink);
  border: 1.5px solid var(--orange);
  cursor: pointer;
  /* a real <button>, styled to match .btn - so it takes the same 700 */
  font-family: var(--font-body);
  font-size: var(--fs-ui);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--ls-ui);
  padding: 0.9em 1.8em;
  border-radius: var(--radius-pill);
  transition: transform 0.3s var(--ease-out), background 0.3s var(--ease-out), border-color 0.3s var(--ease-out);
}
.form-submit svg { width: 1.05rem; transition: transform 0.3s var(--ease-out); }
.form-submit:hover { transform: translateY(-2px); background: var(--cream); border-color: var(--cream); }
.form-submit:hover svg { transform: translateX(4px); }

/* sending, and the failed-to-send note under the button */
.form-submit[disabled] { opacity: 0.6; cursor: default; }
.form-note {
  margin-top: 1rem;
  font-family: var(--font-mono);
  font-size: var(--fs-ui-sm);
  letter-spacing: 0.06em;
  color: var(--orange);
  line-height: var(--lh-body);
}
.form-note a { color: inherit; text-underline-offset: 0.2em; }

/* success state */
.form-success { text-align: center; padding: clamp(2rem, 5vh, 4rem) 0; }
.success-mark {
  display: inline-grid;
  place-items: center;
  width: 3.5rem;
  height: 3.5rem;
  border-radius: 50%;
  background: var(--orange);
  color: var(--ink);
  margin-bottom: 1.5rem;
  animation: pop-in 0.4s var(--ease-out) both;
}
.success-mark svg { width: 1.6rem; }
.form-success h3,
.form-modal h3 {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-size: var(--fs-h3);
  text-transform: uppercase;
  letter-spacing: -0.02em;
  margin-bottom: 0.8rem;
}
.form-success p,
.form-modal p { color: var(--muted-light); max-width: 42ch; margin: 0 auto; line-height: var(--lh-body); text-wrap: pretty; }
@keyframes pop-in {
  from { transform: scale(0); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

/* ---- the same confirmation, raised into a modal ----
   Styled off .contact-panel rather than given a look of its own: same black
   plate, same cream copy, same panel radius, so the message that lands in
   front reads as the panel talking and not as a second thing on the page.
   The heading and the copy share .form-success's rules above - one message in
   two places should not be two sets of type rules that can drift apart.

   border: 0 and the width are both doing real work. A <dialog> arrives with a
   UA border and shrink-to-fit sizing, so left alone it is a bordered box as
   wide as its longest line. The width caps the measure and the gutter keeps
   it off the edge on a narrow screen. */
.form-modal {
  border: 0;
  /* The reset at the top of this sheet zeroes every margin, and the UA rule it
     overrides - dialog { margin: auto } - is the one that centres an open modal
     in the viewport. Without this the panel sits in the top-left corner. */
  margin: auto;
  padding: clamp(2rem, 5vw, 3rem);
  width: min(30rem, calc(100vw - 2 * var(--gutter)));
  background: var(--ink);
  color: var(--cream);
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow-card-hover);
  text-align: center;
}
/* The page behind is a cream section; dimming alone leaves it bright enough to
   compete with the plate, so the backdrop blurs as well as darkens. */
.form-modal::backdrop {
  background: rgba(var(--black-rgb), 0.6);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.form-modal p { margin-bottom: 2rem; }
.form-modal-close {
  background: var(--orange);
  color: var(--ink);
  border: 1.5px solid var(--orange);
  cursor: pointer;
  font-family: var(--font-body);
  font-size: var(--fs-ui);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--ls-ui);
  padding: 0.9em 1.8em;
  border-radius: var(--radius-pill);
  transition: transform 0.3s var(--ease-out), background 0.3s var(--ease-out), border-color 0.3s var(--ease-out);
}
.form-modal-close:hover { transform: translateY(-2px); background: var(--cream); border-color: var(--cream); }

/* [open] rather than the bare class: a closed dialog is display:none, and an
   animation declared on it would run against nothing and be spent by the time
   it opened. */
@media (prefers-reduced-motion: no-preference) {
  .form-modal[open] { animation: modal-in 0.35s var(--ease-out) both; }
  .form-modal[open]::backdrop { animation: backdrop-in 0.35s var(--ease-out) both; }
}
@keyframes modal-in {
  from { transform: translateY(1rem) scale(0.96); opacity: 0; }
  to { transform: none; opacity: 1; }
}
@keyframes backdrop-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* ============================================================
   FINAL CTA BANNER (full-bleed)
   ============================================================ */
.cta-banner {
  position: relative;
  width: 100%;
  overflow: hidden;
  background:
    radial-gradient(90% 120% at 50% 20%, rgba(var(--orange-rgb), 0.06) 0%, transparent 60%),
    var(--cream-side);
  padding: var(--section-y) var(--gutter);
  text-align: center;
  border-top: 1px solid color-mix(in srgb, var(--ink) 8%, transparent);
}

/* scattered service icons spread all over the field */
.cta-icons {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}
.cta-ico {
  position: absolute;
  display: block;
  object-fit: contain;
  pointer-events: none;
  will-change: transform;
}
/* keep the centred message crisp above a denser field */
.cta-banner-inner::before {
  content: "";
  position: absolute;
  inset: -12% -8%;
  z-index: -1;
  background: radial-gradient(60% 62% at 50% 50%, var(--cream) 32%, color-mix(in srgb, var(--cream) 55%, transparent) 62%, transparent 82%);
  pointer-events: none;
}

.cta-banner-inner {
  position: relative;
  z-index: 1;
  max-width: 56rem;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* rounded badge with orange dot */
.cta-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  background: var(--cream-panel);
  border: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
  box-shadow: 0 2px 12px rgba(var(--black-rgb), 0.07), inset 0 1px 0 rgba(255, 255, 255, 0.7);
  padding: 0.55rem 1.15rem;
  border-radius: 0.85rem;
  font-family: var(--font-mono);
  font-size: var(--fs-ui);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--ink);
}
.cta-pill-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--orange);
  box-shadow: 0 0 0 3px rgba(var(--orange-rgb), 0.18);
}

.cta-title {
  max-width: 24ch;
  margin-inline: auto;
  text-wrap: balance;
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  /* the banner used to take --fs-mega, the largest step below the hero. It now
     joins the rest of the sections on --fs-h2: the band already carries its own
     ground and its own button, so it does not need scale as well to land. */
  font-size: var(--fs-h2);
  letter-spacing: var(--ls-display);
  line-height: var(--lh-display);
  color: var(--ink);
  margin-top: clamp(1.4rem, 3vh, 2rem);
}
.cta-title span { color: var(--orange); }

.cta-sub {
  /* the headline is one row at full width now, so it needs more air under it */
  margin-top: clamp(1.5rem, 3vh, 2.1rem);
  /* centred, so it runs shorter than the page measure — both rags have to
     stay readable, and a centred 65ch block is hard to re-enter line to line */
  max-width: 52ch;
  text-wrap: pretty;
  color: var(--muted);
  font-size: var(--fs-lead);
  line-height: var(--lh-body);
}

.cta-btn { margin-top: clamp(2.2rem, 5vh, 3.4rem); }

/* ============================================================
   FOOTER
   ------------------------------------------------------------
   Two bands inside one dark slab. The upper band pairs a brand
   column with a three-column directory; below it the studio name
   is set at display scale and cut by the legal bar's rule, so the
   footer closes on a piece of typography rather than a list.
   ============================================================ */
.site-footer {
  position: relative;
  isolation: isolate;
  overflow: hidden;          /* the wordmark is meant to bleed past its box */
  background: var(--ink);
  color: var(--cream);
  padding: clamp(3rem, 6.5vh, 5rem) var(--gutter) 0;
}
/* One pool of warm light at the foot of the page. It sits behind the wordmark
   so the letters read as lit from below and the black gains a floor, rather
   than the section ending on a flat field. */
.site-footer::before {
  content: "";
  position: absolute;
  inset: auto 0 -25% 0;
  height: 65%;
  background: radial-gradient(58% 100% at 50% 100%,
              rgba(var(--orange-rgb), 0.11), transparent 72%);
  pointer-events: none;
  z-index: -1;
}
/* The container query context. Everything in the footer measures against this
   box, which is what lets the wordmark below fill the column at any width
   without a breakpoint per size. */
.footer-inner {
  max-width: var(--container);
  margin: 0 auto;
  container-type: inline-size;
}

.footer-top {
  position: relative;      /* the orbit mark's corner */
  display: grid;
  /* the directory carries three columns to the brand block's one, so it takes
     the larger share; minmax(0, .) stops a long link from widening its track */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr);
  gap: clamp(2.5rem, 6vw, 6rem);
  padding-bottom: clamp(2rem, 5vh, 3.5rem);
}

/* ---------- brand column ---------- */
.footer-brand { display: flex; flex-direction: column; align-items: flex-start; }
.footer-brand .logo-mark { color: var(--cream); }
.footer-logo-link { color: var(--cream); margin-bottom: 1.5rem; }
.footer-tagline {
  /* deliberately short of the page measure: this is a column of copy beside a
     directory, not a paragraph of running text */
  max-width: 34ch;
  color: var(--muted-light);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  margin-bottom: 1.6rem;
}
/* pinned to the foot of the column with an auto margin, so the contact block
   lands level with the base of the directory beside it and the brand column
   reads as a deliberate top-and-bottom split rather than a short list with
   space left over. Where there is no free space to take - the stacked phone
   layout - the auto margin collapses and the tagline's own margin holds the
   gap. */
.footer-email {
  margin-top: auto;
  font-family: var(--font-mono);
  font-size: var(--fs-body-sm);
  letter-spacing: 0.02em;
  color: var(--cream);
  border-bottom: 1px solid color-mix(in srgb, var(--orange) 70%, transparent);
  padding-bottom: 3px;
  transition: color 0.3s, border-color 0.3s;
}
.footer-email:hover,
.footer-email:focus-visible { color: var(--orange); border-color: var(--orange); }

/* The three marks are inlined so they inherit currentColor - the accent is
   declared once on the link and the hover state inverts the whole button.
   Inlined rather than masked or <img>-tagged because a mask image is same-origin
   only, and the page has to survive being opened straight off disk. All three sit
   on the same 24-unit grid, and the envelope and the camera are drawn at the same
   1.8 stroke, so one size covers both. */
.footer-social {
  display: flex;
  gap: 0.7rem;
  margin-top: 1.9rem;
}
.social-link {
  display: grid;
  place-items: center;
  width: 2.6rem;
  height: 2.6rem;
  border-radius: 50%;
  color: var(--orange);
  border: 1px solid color-mix(in srgb, var(--orange) 40%, transparent);
  transition:
    color 0.3s,
    background 0.3s,
    border-color 0.3s,
    transform 0.3s var(--ease-out);
}
.social-link svg {
  width: var(--icon-size, 1.05rem);
  height: var(--icon-size, 1.05rem);
  display: block;
}
/* the X mark is a solid glyph where the other two are outlines, so it reads
   heavier at the same box - a hair smaller evens the row out */
.social-x { --icon-size: 0.95rem; }
.social-link:hover,
.social-link:focus-visible {
  color: var(--ink);
  background: var(--orange);
  border-color: var(--orange);
  transform: translateY(-2px);
}

/* ---------- directory column ---------- */
/* auto-fit rather than a fixed three, so the directory falls to two columns and
   then to one on its own wherever the room runs out, instead of at a breakpoint
   guessed in advance. The 9rem floor is set at the width where a phone still
   gets two columns rather than one twelve-link scroll. */
.footer-nav {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: clamp(1.75rem, 3vw, 2.5rem);
}
/* an h3 since the outline was jumping h2 -> h4 here. Nothing about the
   appearance changes: this is a mono eyebrow either way, and the level is only
   ever read by a screen reader or a crawler. */
.footer-col h3 {
  font-family: var(--font-mono);
  font-size: var(--fs-ui-sm);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--orange);
  margin-bottom: 0.95rem;
}
.footer-col ul {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}
/* The underline grows out of a background image rather than a border or a
   pseudo-element, so nothing about the link's box changes on hover - the row
   beneath it stays exactly where it was. */
.footer-col a {
  display: inline-block;
  width: fit-content;
  font-size: var(--fs-body-sm);
  line-height: 1.45;
  color: var(--muted-light);
  background-image: linear-gradient(var(--orange), var(--orange));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0 1px;
  transition: color 0.3s var(--ease-out), background-size 0.45s var(--ease-out);
}
.footer-col a:hover,
.footer-col a:focus-visible { color: var(--cream); background-size: 100% 1px; }

/* ---------- the cut wordmark ---------- */
/* Sized in container-inline units so the name spans the measure at every width;
   the figure is calibrated to Poppins 700 at this tracking. The box is
   shorter than the line, which is what does the cutting - the bar's rule below
   lands exactly on the crop, so the letters run into it rather than stop above
   it. In this face the cap top sits 0.13em under the top of the line box, so
   the text is pulled up by exactly that much: the band is then all letterform
   and none of it air, which is where the footer's height was going. A viewport-unit clamp stands in where container queries are unsupported. */
.footer-wordmark {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  height: 0.47em;
  margin-top: clamp(1rem, 2.5vh, 2rem);
  overflow: hidden;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2.5rem, 17.9vw, 18.3rem);
  line-height: 1;
  letter-spacing: -0.045em;
  white-space: nowrap;
  user-select: none;
}
@supports (container-type: inline-size) {
  .footer-wordmark { font-size: 21.7cqi; }
}
.footer-wordmark-text {
  margin-top: -0.038em;  /* the face's own gap between line-box top and cap top */
  /* lit at the top, falling away to the footer's own black at the crop: the
     name reads as moulded out of the surface rather than laid on top of it.
     The flat colour is the fallback where background-clip: text is ignored. */
  color: color-mix(in srgb, var(--cream) 14%, var(--ink));
  background-image: linear-gradient(180deg,
      color-mix(in srgb, var(--cream) 24%, var(--ink)),
      color-mix(in srgb, var(--cream) 5%,  var(--ink)));
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
}
/* the orange disc from the logo mark, set at the width of a full stop and rested
   on the crop line - the one lit thing in a wordmark that is otherwise all
   surface, and the only place the accent appears at this scale */
.footer-wordmark-dot {
  flex: none;
  align-self: flex-end;
  width: 0.085em;
  height: 0.085em;
  margin: 0 0 0.035em 0.055em;
  border-radius: 50%;
  background: var(--orange);
}

/* Once the footer stacks, the directory is the whole column and there is no
   right edge for the mark to sit against; it centres under the links instead,
   the same move the about panel's copy makes at its own stacking width. */
@media (max-width: 900px) {
  .orbit-btn.orbit-btn--footer {
    position: static;
    margin: clamp(2rem, 5vh, 3rem) auto 0;
  }
}

/* ---------- legal bar ---------- */
.footer-bar {
  border-top: 1px solid color-mix(in srgb, var(--cream) 14%, transparent);
  padding: 1.15rem 0 1.3rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.75rem 2rem;
  font-family: var(--font-mono);
  font-size: var(--fs-ui-sm);
  text-transform: uppercase;
  letter-spacing: var(--ls-ui);
  color: var(--muted-light);
  flex-wrap: wrap;
}
/* the aside is the quietest thing in the bar - a step down from the two lines
   either side of it, which are the ones anyone actually looks for */
.footer-mark { color: color-mix(in srgb, var(--muted-light) 65%, transparent); }
/* the row is set in mono at --fs-ui-sm, which left this link 19px tall - under
   the 24px minimum for a touch target. The padding is vertical only, so the
   bar's own rhythm is untouched. */
.back-top {
  transition: color 0.3s;
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  min-height: 24px;
  padding-block: 0.3rem;
}
.back-top span {
  display: inline-block;
  transition: transform 0.3s var(--ease-out);
}
.back-top:hover,
.back-top:focus-visible { color: var(--orange); }
.back-top:hover span,
.back-top:focus-visible span { transform: translateY(-3px); }

/* ============================================================
   RESPONSIVE
   ============================================================ */
/* tablet-ish: side icon strips get too narrow â€” widen the inset panels instead */
@media (max-width: 1080px) {
  .services { width: 92%; }

  /* The process steps go two-up here rather than at 900. Four columns inside a
     1024px viewport leaves each step about 220px, which sets the copy at ~22
     characters a line — as unreadable at the short end as a full-width block is
     at the long end, and it forces a four-word sentence into five lines. Two
     columns put it back near 55. */
  .process-list { grid-template-columns: repeat(2, 1fr); }
  .process-step:nth-child(2) { border-right: none; }
  .process-step:nth-child(odd) { padding-left: 0; }
  .process-step:nth-child(3), .process-step:nth-child(4) {
    border-top: 1px solid color-mix(in srgb, var(--ink) 15%, transparent);
  }
}

@media (max-width: 900px) {
  /* the nav and the header CTA share one line right down to the 720
     hamburger cut, and between 720 and here they run out of room: the links
     reach the button and the button's label breaks onto a second line.
     Tighten the links' own padding and hold the label on one line — no size
     change, so the header still matches the desktop one. */
  .main-nav { gap: 0; }
  .main-nav a { padding: 0.62em 0.75em; }
  .header-cta { white-space: nowrap; padding-inline: 1.25em; }

  /* The two halves stop being halves. Below this the copy column is too
     narrow to set a headline this size against an object of any use, so the
     split stacks: copy first, object under it. The object keeps a ceiling of
     its own rather than filling the column - at full width it would be the
     first and largest thing on the page under the headline, which is the
     opposite of what it is for. */
  .hero-inner {
    grid-template-columns: minmax(0, 1fr);
    row-gap: clamp(2rem, 5vh, 3rem);
    justify-items: center;
  }
  .hero-copy { max-width: 40rem; }
  .hero-object { max-width: clamp(13rem, 42vw, 21rem); }

  .connect-grid { grid-template-columns: 1fr; gap: clamp(3rem, 8vh, 5rem); }

  /* the why band stacks on the same cut as every other split on the page. The
     cluster takes a ceiling rather than the full column: at full width the
     photograph would open the section larger than the headline that follows
     it, and the overlaps are percentages, so the stat card and the framed
     inset would scale up with it into the wrong weight entirely. */
  .why-grid { grid-template-columns: minmax(0, 1fr); gap: clamp(2.5rem, 7vw, 3.5rem); }
  .why-stage { max-width: 34rem; }


  .footer-top { grid-template-columns: 1fr; gap: 2.5rem; }
}

/* the last 80px before the hamburger takes over: five nav links and the CTA
   still have to share the bar, and tightened padding alone no longer buys the
   room. The pill steps down to the small UI size for this band only. */
@media (max-width: 800px) {
  .main-nav { font-size: var(--fs-ui-sm); }
  .main-nav a { padding: 0.6em 0.6em; }
}

@media (max-width: 720px) {
  /* one body size for the whole phone view: the secondary step exists to sit
     card and list copy under page copy on a wide screen, and at phone width
     there is no such comparison to make — only the reading, which wants the
     larger size. Both sides of the breakpoint are flat, so this is a single
     1px step rather than type that resizes as the viewport moves. */
  :root { --fs-body-sm: 1.0625rem; }

  .main-nav, .header-cta { display: none; }
  .nav-toggle {
    display: grid;
    gap: 6px;
    background: none;
    border: none;
    padding: 0.5rem;
    cursor: pointer;
  }
  .nav-toggle span {
    width: 26px; height: 2px;
    background: var(--ink);
    transition: transform 0.35s var(--ease-out), opacity 0.3s;
  }
  body.nav-open .nav-toggle span:first-child { transform: translateY(4px) rotate(45deg); }
  body.nav-open .nav-toggle span:last-child { transform: translateY(-4px) rotate(-45deg); }
  /* The panel below is `position: fixed; inset: 0`, and a fixed box resolves
     its inset against the nearest ancestor carrying a filter, a transform or
     a backdrop-filter - which is the header itself. Left alone, the header's
     blur makes it the containing block and the full-screen menu comes out the
     size of the header bar. Dropping the blur for as long as the menu is open
     hands the panel back to the viewport, where it belongs. */
  body.nav-open .site-header { backdrop-filter: none; }
  /* the panel is a child of the header, so its z-index puts it over its own
     siblings too - the mark and the close button have to be lifted back over
     it or the menu opens with no way out of it */
  body.nav-open .logo,
  body.nav-open .nav-toggle { position: relative; z-index: 95; }
  /* The panel itself — its ground, its list and its button — is built at the
     foot of the sheet, under THE PHONE MENU. It has to sit after the Services
     dropdown rules it overrides, and those are the last block in the file. */

  .hero { padding-top: 6.5rem; min-height: 72svh; }
  /* around this width the first headline row starts to wrap, so a row can be a
     fragment of a line rather than a whole one. Display leading that binds
     three rows into a block at 65px reads as a collision at ~43px: the closer
     the headline gets to reading size, the closer its leading has to get to
     reading leading, so the token opens a step here. */
  .hero-title { --lh-hero: 1.12; }
  .process-list { grid-template-columns: 1fr; }
  .process-step { border-right: none; padding-left: 0; border-top: 1px solid color-mix(in srgb, var(--ink) 15%, transparent); }
  .process-step:first-child { border-top: none; }
  /* At phone width the split has already collapsed to one column (see the
     900 step) and the copy centres in it: a ~340px measure is too narrow for
     the eye to read a left edge as an alignment, and a left-ranged block
     between a centred object and two full-width buttons only reads as a
     mistake. Everything ranges centre together. */
  .hero-copy { align-items: center; }
  .hero-title { text-align: center; }
  .hero-sub { text-align: center; }
  .hero-actions { justify-content: center; }

  .hero-scrollhint { display: none; }
  .hero-actions { width: 100%; }
  .hero-actions .btn { width: 100%; }

  .intro { padding-left: 1.5rem; padding-right: 1.5rem; }
  .services { width: 94%; }
  .contact-panel { padding: 1.5rem 1.25rem; }
  .cta-btn { width: 100%; }
  .footer-bar { justify-content: center; text-align: center; }
}

@media (max-width: 560px) {
  /* The cluster stops being a cluster. There is no longer a plate wide enough
     to break an edge of without the loose pieces covering the photograph
     rather than overlapping it — a 42% inset on a 330px stage is 140px of
     picture, and the stat card's label would set at three words a line. So
     .why-cluster stops being display:contents, becomes a real box, and the two
     pieces drop out of the composition and stack under the plate at full
     width, where both are legible. It sits down here for the same reason the
     rules above it do: source order is what decides it. */
  .why-stage { max-width: none; padding-bottom: 0; }
  .why-shot--main { margin-left: 0; }
  .why-cluster { display: grid; gap: 0.85rem; margin-top: 0.85rem; }
  .why-shot--inset {
    position: static;
    width: 100%;
    aspect-ratio: 16 / 10;
  }
  .why-stat { position: static; width: auto; }
  .why-foot { gap: 1.4rem; }
}

/* small phones. No font-size overrides live here any more: every step in the
   scale is already fluid down to 320px, and re-declaring sizes at a breakpoint
   is what used to make the type jump mid-resize. Only layout changes. */
@media (max-width: 460px) {
  :root { --gutter: 1.1rem; }
  /* at 320-460px the viewport is the measure — every cap above is already
     wider than the column, so they simply stop applying. What does still bite
     is display type set to a fixed character count: balance it instead. */
  .process-title, .cta-title, .connect-heading { text-wrap: balance; }

  /* The name pair holds a half column down to 460px, where each half is still
     182px against a 126px floated label. Below that the halves fall to ~112px
     and the label no longer fits one, so the two names stack like the rest of
     the form. */
  .field-row { grid-template-columns: 1fr; }
}

/* ============================================================
   PAGE HEAD — the banner a secondary page opens on
   ============================================================
   The home page opens on a full-height hero with an object in it. A secondary
   page opens on a short dark band carrying the page's name and the trail back
   to the home page, and nothing else: it is a signpost, not a stage. It reuses
   the studio's own object shot, and the fixed bar goes transparent over it for
   the same reason it does over the hero, which is what main.js decides.

   One photograph under every banner. It was the hero's own plate for a while,
   which made a page name read as a hero that had lost its copy - the same
   room, the same light, at a third of the height. The object shot is the other
   way round: it is a picture with a hole in it. The composition sits hard
   right and the left half is bare wall, which is exactly where the crumb and
   the title land, so the band has somewhere to put its type instead of laying
   it over the busiest part of a room.

   The scrim is the hero's 15% weighted to the left rather than laid flat. That
   wall is the lightest part of the picture and cream over it at a flat 15% was
   closer to legible-if-you-squint than to read-at-a-glance; the right-hand
   side, where the glass is, keeps the 15% it always had and stays lit. */
.page-head {
  position: relative;
  isolation: isolate;
  background-color: var(--black);
  background-image:
    linear-gradient(to right,
      rgba(17, 17, 17, 0.62),
      rgba(17, 17, 17, 0.34) 45%,
      rgba(17, 17, 17, 0.15) 70%),
    url("../assets/img/hero.webp");
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center right;
  color: var(--cream);
  /* The top figure clears the fixed bar the way the hero's does; the foot is
     roughly half of it, because there is nothing under the title to hold open
     and a banner as deep as the hero reads as a hero that failed to load. */
  padding: clamp(7.5rem, 17vh, 11rem) var(--gutter) clamp(3.5rem, 8vh, 5.5rem);
  overflow: hidden;
}
.page-head-inner {
  max-width: var(--container);
  margin: 0 auto;
  position: relative;
  z-index: 1;
}
.page-title {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  /* One step for every page name on the site, and it is the same step every
     section heading on the home page sits on (--fs-h2). It used to be
     --fs-mega, which put a secondary page's name above a home-page section
     heading and one notch under the hero — so About shouted louder than WHY US
     and the whole site had two competing top sizes. There is one now: the hero
     outranks everything, and a page name ranks with a section heading. */
  font-size: var(--fs-h2);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
  margin-bottom: 1rem;
}
.page-title span { color: var(--orange); }
.page-deck {
  /* Contact is the only page that carries a deck, and it sits under the largest
     title on the site. Five per cent off --fs-lead is the step that stops it
     competing with the h1 above it without dropping it to body size. */
  font-size: calc(var(--fs-lead) * 0.95);
  line-height: var(--lh-lead);
  color: var(--cream-soft);
  max-width: 46ch;
  margin-top: 1.4rem;
  text-wrap: pretty;
}

/* ---------- a banner with nothing under the title ----------
   About, Services and the service detail pages carry no deck: the first
   paragraph of the page underneath is the deck, and printing a second one over
   the photograph only said the same thing twice. With nothing under the title
   the foot of the band has nothing to hold open, so it closes to roughly the
   height of the crumb it now ends on — otherwise the banner reads as a box
   waiting for copy that never arrives.

   Contact keeps its deck, and so keeps the taller foot: there the line under
   the title is the instruction for the form, not a restatement of the page. */
.page-head--tight {
  padding-bottom: clamp(2.5rem, 5.5vh, 3.75rem);
}
.page-head--tight .page-title { margin-bottom: 0; }

/* Narrow, the band is taller than it is wide against the picture's own 2.33,
   so cover starts cropping sideways instead of top-and-bottom - and anchored
   right that crop is the glass, printed straight behind a title that now runs
   the full width of the screen. Anchored left it is the wall again, which is
   the half of this photograph the type was given in the first place. */
@media (max-width: 700px) {
  .page-head { background-position: center left; }
}

/* the trail back. Set in the mono the section labels take, so it reads as
   chrome rather than as the first line of the page. */
.crumbs {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--muted-light);
}
.crumbs a { color: var(--muted-light); transition: color 0.3s var(--ease-out); }
.crumbs a:hover { color: var(--orange); }
.crumbs [aria-current] { color: var(--cream); }
.crumbs-sep { color: var(--orange); }

/* ============================================================
   REACH — the ways in, and the picture beside them
   ============================================================ */
.reach {
  padding: var(--section-y) var(--gutter);
  position: relative;
}
.reach-grid {
  max-width: var(--container);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(2.5rem, 6vw, 5.5rem);
  align-items: center;
}
.reach-lead {
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
  color: var(--muted);
  max-width: var(--measure);
  margin-bottom: clamp(2rem, 4vh, 2.75rem);
  text-wrap: pretty;
}
/* The heading sits closer to its copy here than a section heading does to a
   section: .connect-heading holds a full section's worth of space under it,
   which is right above six questions and far too much above a paragraph that
   finishes the same thought. */
.reach .connect-heading { margin-bottom: 1.25rem; }
/* .section-label holds a section's worth of space under it by default, which
   is the interval above a band, not above the line it introduces. */
.reach .section-label { margin-bottom: 1.5rem; }

.reach-list { display: flex; flex-direction: column; }
.reach-item {
  display: flex;
  align-items: flex-start;
  gap: clamp(1rem, 2vw, 1.4rem);
  padding: clamp(1.1rem, 2.4vh, 1.5rem) 0;
  border-top: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
}
.reach-item:last-child { border-bottom: 1px solid color-mix(in srgb, var(--ink) 12%, transparent); }
/* The disc is the one piece of solid colour in the column, so it carries the
   accent and the glyph inside it goes to the ink — the same way round as
   .btn-accent, which is the only other orange plate on a cream ground. */
.reach-ico {
  width: 3rem;
  height: 3rem;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--orange);
  color: var(--ink);
  display: grid;
  place-items: center;
  transition: transform 0.35s var(--ease-out);
}
.reach-ico svg { width: 1.3rem; height: 1.3rem; }
.reach-item:hover .reach-ico { transform: translateY(-3px) rotate(-6deg); }
.reach-item h3 {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-size: var(--fs-h5);
  text-transform: uppercase;
  letter-spacing: var(--ls-caps);
  line-height: var(--lh-heading);
  margin-bottom: 0.3rem;
}
.reach-item p, .reach-item a {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--muted);
}
.reach-item a { transition: color 0.3s var(--ease-out); }
.reach-item a:hover { color: var(--orange); }
/* the same buttons as the footer row, on a cream ground. Both rules restate what
   .social-link already says, because .reach-item a is the more specific selector
   and would otherwise mute the glyph and hold it orange against its own orange
   hover plate. */
.reach-social { display: flex; gap: 0.6rem; margin-top: 0.55rem; }
.reach-item .social-link { color: var(--orange); }
.reach-item .social-link:hover,
.reach-item .social-link:focus-visible { color: var(--ink); }
.reach-note {
  font-family: var(--font-mono);
  font-size: var(--fs-meta);
  line-height: var(--lh-body);
  color: var(--muted);
  margin-top: clamp(1.5rem, 3vh, 2rem);
}
.reach-note strong { color: var(--ink); font-weight: 500; }

/* the picture. One frame, the page's panel radius, the page's drop. */
.reach-visual {
  border-radius: var(--radius-panel);
  overflow: hidden;
  box-shadow: var(--shadow-card);
  aspect-ratio: 4 / 3;
}
.reach-visual img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ============================================================
   WRITE — the form, centred on its own band
   ============================================================ */
.write-inner { max-width: 54rem; margin: 0 auto; }
.write-head { text-align: center; margin-bottom: clamp(2rem, 5vh, 3rem); }
/* .section-label is a flex row with a bar in it, so centring the block is not
   enough — the row itself has to centre its own contents. */
.write-head .section-label { justify-content: center; margin-bottom: 1.25rem; }
.write-head .connect-heading { margin-bottom: 1rem; }
.write-head p {
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
  color: var(--muted);
  max-width: 52ch;
  margin: 0 auto;
  text-wrap: pretty;
}

/* ============================================================
   FAQ ASIDE — the dark card the questions sit beside
   ============================================================
   The questions used to share their row with the contact form. The form is its
   own page now, and what takes the other half is a plate that says what the
   column is and where to go if the answer is not in it. It stretches to the
   height of the questions beside it (the grid is align-items:start, so this has
   to say so itself) and the picture takes whatever slack is left, which is what
   keeps the two columns level without a fixed height on either. */
.faq-aside {
  align-self: stretch;
  display: flex;
  flex-direction: column;
  background: var(--ink);
  color: var(--cream);
  border-radius: var(--radius-panel);
  padding: clamp(2rem, 4vw, 3.2rem);
  overflow: hidden;
}
.faq-aside .section-label { color: var(--muted-light); margin-bottom: 1.5rem; }
.faq-aside .connect-heading { margin-bottom: 0; }
.faq-aside-media {
  /* The picture is taken out of the flow inside this box, and the box is what
     the flex line sizes. Left in the flow it would be the other way round: an
     img at width:100% has a definite width and an intrinsic ratio, so it sets
     its own height, and height:100% cannot answer it because the parent's own
     height is not definite yet - the plate would end up as tall as the
     photograph happened to be rather than as tall as the questions beside it. */
  position: relative;
  flex: 1;
  min-height: clamp(11rem, 22vh, 16rem);
  margin: clamp(1.75rem, 4vh, 2.5rem) 0;
  border-radius: var(--radius-card);
  overflow: hidden;
}
.faq-aside-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.6s var(--ease-out);
}
.faq-aside:hover .faq-aside-media img { transform: scale(1.04); }
.faq-aside-foot h3 {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-size: var(--fs-h5);
  text-transform: uppercase;
  letter-spacing: var(--ls-caps);
  line-height: var(--lh-heading);
  margin-bottom: 0.85rem;
}
.aside-link {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--orange);
  transition: gap 0.3s var(--ease-out), color 0.3s var(--ease-out);
}
.aside-link svg { width: 1rem; height: 1rem; }
.aside-link:hover { gap: 0.85rem; color: var(--cream); }

/* ---------- responsive: the new blocks ---------- */
@media (max-width: 900px) {
  /* .connect-grid has already dropped to one column by here, so the plate is
     full width with the questions under it. Stretching it to a row height it
     no longer shares would make it as tall as the whole accordion. */
  .faq-aside { align-self: auto; }
  .reach-grid { grid-template-columns: 1fr; gap: clamp(2.5rem, 6vh, 3.5rem); }
  /* the picture leads on a phone: it is the only thing on the page that is not
     type, and burying it under six lines of contact detail wastes it */
  .reach-visual { order: -1; aspect-ratio: 16 / 10; }
}

/* ============================================================
   ABOUT PAGE
   ============================================================
   Four blocks that exist only on /about, all of them reworkings of the
   reference layout the page was drawn from:

     .story    the reference's "Get to Know" pair — a photograph cluster
               against the studio's argument
     .figures  its dark statistics slab
     .recent   the four work tiles that straddle the foot of that slab

   The services row between the first two is not here: that section is the home
   page's, markup and stylesheet both, and it needed nothing added to it to run
   on a second page. Everything below pulls from the same tokens the rest of the
   sheet does — no block introduces a colour, a size or a radius of its own.
   ============================================================ */

/* ------------------------------------------------------------
   STORY — the studio, and the two photographs of it
   ------------------------------------------------------------
   The same anatomy as .why on the home page, and deliberately not the same
   composition. .why indents its plate from the left, hangs the framed inset off
   the bottom-right corner and sits the accent plate at the top left. This
   mirrors all three, which is what the reference does and what stops the two
   pages reading as one layout used twice — they draw on the same two
   photographs, and only the arrangement tells them apart. */
.story {
  position: relative;
  isolation: isolate;
  padding: var(--section-y) var(--gutter) clamp(3rem, 7vh, 5rem);
  max-width: var(--container);
  margin: 0 auto;
}
/* the label introduces the whole band rather than the heading directly under
   it, so it takes the wider shared gap rather than the tight head gap */
.story > .section-label { margin-bottom: clamp(2rem, 5vw, 3.25rem); }

.story-grid {
  display: grid;
  /* the stage takes slightly less than the copy, for the reason .why-grid
     gives: a photograph carries at a smaller size than a paragraph does, and
     the copy column has a heading, a deck, three lines and a footer row to
     hold without any of them cramping */
  grid-template-columns: minmax(0, 0.95fr) minmax(0, 1fr);
  gap: clamp(2.5rem, 6vw, 5.5rem);
  align-items: center;
}

/* ---------- the image cluster ---------- */
.story-stage {
  position: relative;
  /* the room the framed inset hangs into above the main plate */
  padding-top: clamp(1.75rem, 4.5vw, 3rem);
}
.story-shot {
  position: relative;
  margin: 0;
  overflow: hidden;
  border-radius: var(--radius-card);
}
.story-shot img { display: block; width: 100%; height: 100%; object-fit: cover; }

/* Inset from both edges rather than one: the badge breaks the left edge and the
   framed inset breaks the right, so the plate needs an edge to spare on each
   side. The reference hangs both outside the column, which a fluid grid cannot
   do without risking overflow at the narrow end — indenting the photograph
   instead buys the same overlap and stays on grid. */
.story-shot--main {
  margin-left: 7%;
  margin-right: 15%;
  aspect-ratio: 460 / 514;
  box-shadow: var(--shadow-card);
}

/* the second photograph, framed in the accent and lifted over the plate's
   top-right corner, so the group has a diagonal to read down */
.story-shot--inset {
  position: absolute;
  right: 0;
  top: 0;
  z-index: 1;
  width: 42%;
  aspect-ratio: 203 / 257;
  border: 4px solid var(--orange);
  box-shadow: var(--shadow-card-hover);
}

/* Until the narrow block at the foot of this section, this wrapper generates no
   box at all: the inset and the badge resolve their absolute positions against
   .story-stage exactly as if they were its own children. It exists so that both
   can be pulled out of the composition together at the narrow end. */
.story-cluster { display: contents; }

/* The one lit surface in the block — orange plate, ink type, which is the
   page's accent convention (see .btn-accent and .why-stat) rather than the
   white-on-orange the reference used. */
.story-badge {
  position: absolute;
  left: 0;
  bottom: 9%;
  z-index: 2;
  width: min(52%, 13.5rem);
  padding: 1rem 1.15rem;
  background: var(--orange);
  color: var(--ink);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card-hover);
}
.story-badge__num {
  display: block;
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-size: var(--fs-h4);
  line-height: 1;
  letter-spacing: var(--ls-heading);
}
.story-badge__label {
  display: block;
  margin-top: 0.35rem;
  font-family: var(--font-mono);
  font-size: var(--fs-ui-sm);
  text-transform: uppercase;
  letter-spacing: var(--ls-ui);
  line-height: 1.4;
  text-wrap: balance;
}

/* ---------- the argument ---------- */
.story-copy { min-width: 0; }
.story-title {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-size: var(--fs-h2);
  text-transform: uppercase;
  letter-spacing: var(--ls-display);
  line-height: var(--lh-display);
  text-wrap: balance;
}
.story-title span { color: var(--orange); }

.story-lead {
  max-width: var(--measure);
  margin-top: clamp(1.1rem, 2.5vh, 1.6rem);
  color: var(--muted);
  line-height: var(--lh-body);
  text-wrap: pretty;
}

/* Three ruled lines rather than the home page's dark proof plate. This is the
   opening argument of the page and it wants to read as a paragraph broken into
   breaths — a second dark card here would put two of them on one screen, and
   the plate on the home page would stop being the block you cannot skim past.
   The rules are the page's hairline; the marker is the label bar at list
   scale. */
.story-lines {
  max-width: var(--measure);
  margin-top: clamp(1.6rem, 3.5vh, 2.2rem);
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
}
.story-lines li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: 0.85rem;
  padding: 0.85rem 0;
  border-top: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body);
  text-wrap: pretty;
}
.story-lines li::before {
  content: "";
  width: 1.15rem;
  height: 2px;
  border-radius: 2px;
  background: var(--orange);
  /* optically centred on the first line rather than on its box */
  margin-top: 0.72em;
}

/* ---------- footer row: the way in, and the call ---------- */
.story-foot {
  margin-top: clamp(1.8rem, 4vh, 2.6rem);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(1.1rem, 3vw, 2.25rem);
}
.story-call {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  color: inherit;
}
.story-call__ico {
  flex: none;
  display: grid;
  place-items: center;
  width: clamp(2.9rem, 5vw, 3.4rem);
  height: clamp(2.9rem, 5vw, 3.4rem);
  border-radius: var(--radius-pill);
  background: var(--cream-panel);
  color: var(--orange);
  box-shadow: var(--shadow-lit);
  transition: background 0.3s var(--ease-out), color 0.3s var(--ease-out);
}
.story-call__ico svg { width: 1.15rem; height: 1.15rem; }
.story-call:hover .story-call__ico { background: var(--orange); color: var(--ink); }
.story-call__label {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-ui-sm);
  text-transform: uppercase;
  letter-spacing: var(--ls-ui);
  color: var(--muted);
}
.story-call__value {
  display: block;
  margin-top: 0.2rem;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--fs-body-sm);
  letter-spacing: var(--ls-heading);
  transition: color 0.3s var(--ease-out);
}
.story-call:hover .story-call__value { color: var(--orange); }

/* ------------------------------------------------------------
   FIGURES — the dark slab, and the head over the work tiles
   ------------------------------------------------------------
   The reference's statistics band. Its figure is a count of jobs delivered,
   which a studio this new has no honest version of, so the number here is the
   one it can stand behind on every project instead. The slab carries a deep
   foot: the four tiles below are pulled back up into it and need black to land
   on. */
.figures {
  position: relative;
  isolation: isolate;
  width: 100%;
  background: var(--black);
  color: var(--cream);
  padding: var(--section-y) var(--gutter) clamp(7rem, 16vh, 11rem);
  overflow: hidden;
}
.figures-inner {
  max-width: var(--container);
  margin: 0 auto;
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  gap: clamp(1.5rem, 5vw, 4rem);
  align-items: center;
}
.figures-stat__label {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--muted-light);
  margin-bottom: 0.6rem;
}
/* The one place on the site where type is set larger than the home page's
   headline. It is a single figure with no descenders and nothing set under it,
   so it can take a leading of 1 and a tracking tighter than the hero's. */
.figures-stat__num {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-size: clamp(3.5rem, 10vw, 7rem);
  line-height: 1;
  letter-spacing: -0.045em;
  color: var(--orange);
}
.figures-stat__num span { font-size: 0.55em; }
.figures-note {
  max-width: var(--measure);
  padding-left: clamp(1.5rem, 4vw, 3.5rem);
  border-left: 1px solid color-mix(in srgb, var(--cream) 18%, transparent);
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body);
  color: var(--cream-mute);
  text-wrap: pretty;
}

.figures-head {
  max-width: var(--container);
  margin: clamp(3rem, 8vh, 5rem) auto 0;
  padding-top: clamp(2.5rem, 6vh, 4rem);
  border-top: 1px solid color-mix(in srgb, var(--cream) 14%, transparent);
}
.figures-head .section-label { margin-bottom: clamp(0.9rem, 2vh, 1.35rem); }
.figures-title {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-size: var(--fs-h2);
  text-transform: uppercase;
  letter-spacing: var(--ls-display);
  line-height: var(--lh-display);
  color: var(--cream);
}

/* ------------------------------------------------------------
   RECENT — four tiles straddling the foot of the slab
   ------------------------------------------------------------
   Named .recent and not .showcase: the services list above already owns the
   .showcase__* family, and two unrelated blocks answering to one word is how a
   stylesheet starts lying about itself.

   The row is pulled up into the black by a little under half a tile, so it
   belongs to both grounds at once — which is the whole of the effect the
   reference gets here. The lift is a negative margin rather than a transform,
   so the cream below closes up behind it; a transform would leave the space the
   row used to occupy standing empty. */
.recent {
  position: relative;
  z-index: 1;
  max-width: var(--container);
  /* The lift is a fraction of a tile, so it had to come down when the tiles
     went from portrait to square: -11rem was a little under half of a 3/4 tile
     and would be nearly two thirds of a square one. This range keeps the row
     straddling the edge at roughly the same proportion it always did, and it
     stays inside the foot .figures holds open for it at every viewport.

     It is held as a positive length and negated here rather than written as a
     negative margin, because the printed ground below has to start exactly
     where the black slab ends and that is this same distance measured the
     other way. One number, so the two can never drift apart. */
  --recent-lift: clamp(6rem, 11vh, 8.5rem);
  margin: calc(-1 * var(--recent-lift)) auto 0;
  padding: 0 var(--gutter) var(--section-y);
}
/* The same printed ground the services list stands on, carried under the tiles.
   Without it the page finished on a sheet of flat body cream: the row of work
   sat on nothing, and the depth of section padding below it read as an empty
   white margin rather than as a surface.

   It starts at the lift rather than at the section's top edge. .recent is
   raised into the black slab and carries a z-index of its own, so a plate at
   inset:0 would sit inside that stacking context and paint cream over the part
   of the slab the tiles are straddling — which is the whole effect the row is
   here for. */
.recent > .cream-plate { top: var(--recent-lift); }
/* And it ends on the bar, not short of it. Same case .cream-field makes one
   page over: the gap above the marquee is there to separate two cream sections,
   and a plate meeting the bar's black rule is a seam between two materials
   rather than a join that needs breathing room. */
.recent + .marquee { margin-top: 0; }
.recent-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(0.75rem, 1.8vw, 1.5rem);
}
.recent-tile {
  position: relative;
  display: block;
  overflow: hidden;
  /* Square, and not the portrait this row started as. Three of the four pieces
     in it are 1:1 campaign artwork with their own type set inside the frame,
     and cropping a poster to 3/4 takes a quarter of its width off — which cut
     ICONIC BY DESIGN down to CONIC Y DESIGN and ran PHOTOGRAPHY off the edge.
     Work with words in it has to be shown at the shape it was drawn at. Same
     call, for the same reason, as the 1:1 rows on the projects page. */
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card-hover);
  color: var(--cream);
}
.recent-tile img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.6s var(--ease-out);
}
.recent-tile:hover img { transform: scale(1.05); }
/* The scrim the caption sits on — kept to the lower third, so it takes nothing
   off the work itself. It has to close to near-solid at the foot rather than to
   the 70% that would carry over a photograph: three of the four pieces in this
   row are campaign artwork that sets its own type along the foot of the frame,
   and a cream caption laid over a client's strapline is two labels fighting.
   The foot goes to near-black so the caption is the only thing reading there.
   Squared at the foot on purpose — it stops inside the tile's own rounded
   corners, so it never shows an edge. */
.recent-tile::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 48%;
  /* Near-solid is reached before the foot, not at it. The type these posters
     carry sits a little way up from their lower edge, so a ramp that only
     arrives at full strength on the last pixel leaves it showing through
     behind the caption; the ramp lands early instead and the last fifth of the
     tile is effectively black. Everything above 55% is untouched. */
  background: linear-gradient(to bottom,
    transparent,
    rgba(var(--black-rgb), 0.5) 55%,
    rgba(var(--black-rgb), 0.96) 82%,
    rgba(var(--black-rgb), 0.98));
  pointer-events: none;
}
.recent-tile__cap {
  position: absolute;
  z-index: 1;
  left: clamp(0.75rem, 2vw, 1.15rem);
  right: clamp(0.75rem, 2vw, 1.15rem);
  bottom: clamp(0.75rem, 2vw, 1.15rem);
  font-family: var(--font-mono);
  font-size: var(--fs-ui-sm);
  text-transform: uppercase;
  letter-spacing: var(--ls-ui);
  text-wrap: balance;
}
.recent-all {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  margin-top: clamp(1.75rem, 4vh, 2.5rem);
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--ink);
  transition: gap 0.3s var(--ease-out), color 0.3s var(--ease-out);
}
.recent-all svg { width: 1rem; height: 1rem; }
.recent-all:hover { gap: 0.9rem; color: var(--orange); }

/* ---------- responsive: the about page's own blocks ---------- */

@media (max-width: 900px) {
  /* The picture leads: it is the one thing in the block that is not type, and a
     cluster this size stops being a composition once it is a third of the
     screen wide. Same call .reach-visual makes on the contact page. */
  .story-grid { grid-template-columns: 1fr; gap: clamp(2.25rem, 6vh, 3.25rem); }
  .story-stage { max-width: 32rem; }

  /* the rule between the figure and its note becomes a rule under it */
  .figures-inner { grid-template-columns: 1fr; gap: clamp(1.5rem, 4vh, 2.25rem); }
  .figures-note {
    padding-left: 0;
    padding-top: clamp(1.25rem, 3vh, 1.75rem);
    border-left: 0;
    border-top: 1px solid color-mix(in srgb, var(--cream) 18%, transparent);
  }

  /* four tiles across a tablet are 9rem wide and unreadable */
  .recent-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* only the lift moves — the margin and the plate both follow it */
  .recent { --recent-lift: clamp(5rem, 11vh, 8rem); }
}

@media (max-width: 640px) {
  /* Below this there is not enough tile left to straddle anything with: half of
     a two-up row lifted into the slab reads as a layout that has slipped rather
     than as one that overlaps. The row drops onto the cream on its own, and the
     slab gives back the foot it was holding open for it. */
  .figures { padding-bottom: var(--section-y); }
  /* The lift goes negative: the row drops clear of the slab and there is now a
     gap of cream above it instead of an overlap. The plate follows it up into
     that gap and still starts on the black edge. */
  .recent { --recent-lift: calc(-1 * clamp(2rem, 6vh, 3rem)); }
}

@media (max-width: 560px) {
  /* The stage runs out of room to overlap in. The cluster stops being
     display:contents and becomes a real row under the plate, so the inset and
     the badge sit side by side beneath the photograph instead of on top of it —
     the same retreat .why-cluster makes at this width. */
  .story-stage { padding-top: 0; }
  .story-shot--main { margin-inline: 0; }
  .story-cluster {
    display: grid;
    grid-template-columns: minmax(0, 0.8fr) minmax(0, 1fr);
    align-items: stretch;
    gap: 0.85rem;
    margin-top: 0.85rem;
  }
  .story-shot--inset {
    position: static;
    width: auto;
    aspect-ratio: 203 / 257;
  }
  .story-badge {
    position: static;
    width: auto;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
}

/* ============================================================
   SERVICE DETAIL — a sticky rail beside one long article
   ------------------------------------------------------------
   The template this is built from puts a service index, a contact
   card and a download bar down the left of a two-column page. The
   structure is kept; none of the material is. The red plate
   becomes the studio's one orange, the white pill cards become
   the cream panels the FAQ and the process steps already use, and
   every heading goes back into the display face in caps — so the
   page reads as another sheet off the same press rather than as a
   template someone dropped in.
   ============================================================ */
.svc-detail {
  position: relative;
  padding: var(--section-y) var(--gutter) clamp(3rem, 7vh, 5rem);
}
.svc-grid {
  max-width: var(--container);
  margin: 0 auto;
  display: grid;
  grid-template-columns: minmax(0, 20.5rem) minmax(0, 1fr);
  gap: clamp(2rem, 4vw, 4rem);
  /* content height, not stretched: the rail has to be shorter than its row
     for sticky to have anywhere to travel */
  align-items: start;
}
/* The article comes FIRST in the markup, so a keyboard and a screen reader
   reach the page's own content before the index of the four services it is
   not about. These two declarations put the rail back in the left column for
   the wide layout — a left/right arrangement, which is not a reading
   sequence the way a stack is. The narrow layout below simply drops the
   placement and lets the two stack in source order, so there is no `order`
   anywhere on this page and the visual order never disagrees with the
   document order. */
.svc-rail    { grid-column: 1; grid-row: 1; }
.svc-article { grid-column: 2; grid-row: 1; }

/* ---------- the rail ----------
   Sticks under the fixed bar once the article starts running past it. The
   offset is the bar's own height plus a little air, so the first card never
   sits flush against it. */
.svc-rail {
  position: sticky;
  top: clamp(5.75rem, 11vh, 7.25rem);
  display: flex;
  flex-direction: column;
  gap: clamp(1rem, 2.2vh, 1.5rem);
}

/* ---- the service index ----
   Every service is its own raised card rather than a row in one ruled list,
   which is the same call .faq-list makes and for the same reason: a single
   hairline list reads as unbuilt next to the orange plate below it. */
.svc-index { display: flex; flex-direction: column; gap: 0.55rem; }
.svc-index__label { margin-bottom: 0.35rem; }

.svc-link {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: clamp(0.9rem, 2vh, 1.1rem) clamp(1rem, 2vw, 1.25rem);
  background: var(--cream-panel);
  border: 1px solid color-mix(in srgb, var(--ink) 7%, transparent);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  color: var(--ink);
  text-decoration: none;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--fs-body-sm);
  text-transform: uppercase;
  letter-spacing: var(--ls-caps);
  line-height: var(--lh-heading);
  transition: transform 0.35s var(--ease-out),
              box-shadow 0.35s var(--ease-out),
              border-color 0.35s var(--ease-out),
              color 0.3s var(--ease-out),
              background 0.3s var(--ease-out);
}
.svc-link__num {
  font-family: var(--font-mono);
  font-size: var(--fs-ui-sm);
  font-weight: 400;
  letter-spacing: var(--ls-label);
  color: var(--muted);
  flex-shrink: 0;
  transition: color 0.3s var(--ease-out);
}
.svc-link__name { flex: 1 1 auto; min-width: 0; }

/* the badge the template carries on the right, redrawn as the same pill the
   rest of the site puts an arrow in */
.svc-link__go {
  flex-shrink: 0;
  width: 2.1rem;
  height: 2.1rem;
  border-radius: var(--radius-pill);
  display: grid;
  place-items: center;
  background: var(--cream);
  border: 1px solid color-mix(in srgb, var(--ink) 10%, transparent);
  color: var(--ink);
  transition: background 0.3s var(--ease-out),
              color 0.3s var(--ease-out),
              border-color 0.3s var(--ease-out),
              transform 0.35s var(--ease-out);
}
.svc-link__go svg { width: 0.9rem; height: 0.9rem; display: block; }

.svc-link:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-card-hover);
  border-color: color-mix(in srgb, var(--orange) 35%, transparent);
  color: var(--orange);
}
.svc-link:hover .svc-link__go {
  background: var(--orange);
  border-color: var(--orange);
  color: var(--ink);
  transform: translateX(2px);
}

/* the page you are on: the card inverts, and the badge takes the accent */
.svc-link[aria-current="page"] {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--cream);
  box-shadow: var(--shadow-card-hover);
}
.svc-link[aria-current="page"] .svc-link__num { color: var(--muted-light); }
.svc-link[aria-current="page"] .svc-link__go {
  background: var(--orange);
  border-color: var(--orange);
  color: var(--ink);
}

/* ---- the advice card ----
   Orange plate, ink type. That is the site's accent convention (see .btn-accent
   and .why-stat) and it is why this is not the white-on-red the template uses:
   nowhere else on the site does cream sit on orange, and one card doing it
   would read as an import. The two capsules behind the type are the template's
   own geometry, redrawn at low alpha so they print on the plate instead of
   introducing a fourth colour. */
.svc-card {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--orange);
  color: var(--ink);
  border-radius: var(--radius-panel);
  padding: clamp(1.6rem, 3.2vw, 2.1rem);
  box-shadow: var(--shadow-card);
}
.svc-card::before,
.svc-card::after {
  content: "";
  position: absolute;
  z-index: -1;
  border-radius: var(--radius-pill);
  pointer-events: none;
}
.svc-card::before {
  width: 13rem;
  height: 30rem;
  left: -7.5rem;
  top: 4rem;
  transform: rotate(45deg);
  background: rgba(var(--black-rgb), 0.1);
}
.svc-card::after {
  width: 10rem;
  height: 22rem;
  right: -5.5rem;
  top: -9.5rem;
  transform: rotate(48deg);
  background: rgba(var(--cream-rgb), 0.22);
}
.svc-card__badge {
  width: 3.1rem;
  height: 3.1rem;
  border-radius: var(--radius-pill);
  background: var(--cream);
  color: var(--ink);
  display: grid;
  place-items: center;
  margin-bottom: clamp(1rem, 2.2vh, 1.35rem);
}
.svc-card__badge svg { width: 1.35rem; height: 1.35rem; display: block; }
.svc-card__title {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-size: var(--fs-h4);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
  margin-bottom: 0.85rem;
}
.svc-card__note {
  font-family: var(--font-mono);
  font-size: var(--fs-ui);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  /* solid ink, not ink mixed back toward the plate: the mix read as the right
     weight but measured 4.0:1 on orange, and this is 12px uppercase. Ink on
     the orange is 5.8:1 and clears AA — the mono's tracking is what keeps it
     reading as a label rather than as a second line of the heading. */
  color: var(--ink);
  margin-bottom: 0.5rem;
}
.svc-card__line {
  display: inline-block;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--fs-body);
  line-height: var(--lh-heading);
  color: var(--ink);
  text-decoration: none;
  overflow-wrap: anywhere;
  border-bottom: 2px solid rgba(var(--black-rgb), 0.25);
  padding-bottom: 2px;
  transition: border-color 0.3s var(--ease-out);
}
.svc-card__line:hover { border-bottom-color: var(--ink); }

/* ---- the action bar ----
   The template's split "download pdf" bar. There is no PDF to hand out, so the
   component keeps its shape and takes the job the page actually has: the one
   link that starts a quote. The strip on the left is the wipe's starting
   width — on hover it runs the whole bar. */
.svc-action {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: clamp(1.05rem, 2.4vh, 1.35rem) clamp(1.1rem, 2.2vw, 1.4rem);
  padding-left: calc(clamp(1.1rem, 2.2vw, 1.4rem) + 1.5rem);
  background: var(--ink);
  color: var(--cream);
  border-radius: var(--radius-card);
  text-decoration: none;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-ui);
  text-transform: uppercase;
  letter-spacing: var(--ls-ui);
  transition: color 0.35s var(--ease-out);
}
.svc-action::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: 0 auto 0 0;
  width: 1.5rem;
  background: var(--orange);
  transition: width 0.45s var(--ease-out);
}
.svc-action:hover { color: var(--ink); }
.svc-action:hover::before { width: 100%; }
.svc-action svg { width: 1.1rem; height: 1.1rem; flex-shrink: 0; }
.svc-action__go { margin-left: auto; }

/* ---------- the article ---------- */
.svc-article {
  display: flex;
  flex-direction: column;
  gap: clamp(2.25rem, 5vh, 3.25rem);
  min-width: 0;
}
.svc-figure {
  /* Ten per cent off the lead plate, and centred rather than squared to the
     column. At the full width of the article the picture came out at 939px on
     a 1440 screen while every one of the five sources is 900px wide - the
     browser was scaling them up, which is the one thing a screenshot of a
     screenshot cannot survive. Held to 90% the plate lands at 845px, inside
     the source's own pixels on a 1x screen, so the type inside the mockup
     stays sharp instead of being interpolated.
     Centred, because the alternative reads as a mistake: keeping the left edge
     and pulling only the right one in leaves a single ragged gutter beside a
     column of squared text. An even inset on both sides reads as a deliberate
     plate - and the article below still shares its edges with the duo figures
     it sits above, which is where that rule was actually earning its keep. */
  width: 90%;
  margin: 0 auto;
  border-radius: var(--radius-panel);
  overflow: hidden;
  background: var(--cream-panel);
  box-shadow: var(--shadow-card);
}
.svc-figure img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

/* ---------- a veil on the lead plate ----------
   The social page's lead is a bright daylight shot, and it arrived on the page
   several stops hotter than the four beside it: cream section, cream plate,
   then a white-hot photograph the eye goes to before the page name. A soft
   layer of the site's own black settles it into the cream without dimming it
   into a different picture - the same trick the page head plays on the hero,
   at roughly the same weight, with a little more of it at the foot so the
   caption side of the frame sits down rather than glares. */
.svc-figure--veil { position: relative; }
.svc-figure--veil::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom,
      rgba(17, 17, 17, 0.22),
      rgba(17, 17, 17, 0.30) 55%,
      rgba(17, 17, 17, 0.42));
  pointer-events: none;
}
.svc-heading {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  /* A step down, from --fs-h3 to --fs-h4. These are subheads inside one
     article, not sections of the page, and once the banner above came down to
     --fs-h2 they were sitting within a few points of the page's own name —
     "SERVICE OVERVIEW" read as loudly as "WEB DESIGN & DEVELOPMENT". A clear
     step between the two puts the article back under its title. */
  font-size: var(--fs-h4);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
  margin-bottom: clamp(0.9rem, 2vh, 1.2rem);
}
/* Everything in the article shares one left edge with the picture above it and
   runs out to the picture's right edge — headings, copy, the quote, the ticks
   and the captions. That is the one deliberate exception to --measure on this
   site: the block is squared to the image rather than held to a reading
   measure, so the column reads as one edge instead of a left edge with a
   ragged right beside a full-width photograph. */
.svc-block p {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--muted);
  max-width: none;
  text-wrap: pretty;
}
.svc-block p + p { margin-top: 1.1rem; }

/* ---- pull quote ----
   The card is gone. A near-white plate on a warm printed ground was the one
   surface on this page that did not belong to the site — cream-panel is for
   raised UI (the rail's cards, the process steps), and a quote is not UI. What
   marks it now is the same thing that marks a section label: one orange rule
   and a change of type. Nothing is painted behind it, so it reads on the
   page's own ground the way the FAQ list above it does. */
.svc-quote {
  position: relative;
  margin: clamp(1.75rem, 3.5vh, 2.4rem) 0;
  padding: clamp(0.35rem, 1vh, 0.6rem) 0
           clamp(0.35rem, 1vh, 0.6rem) clamp(1.5rem, 3vw, 2.1rem);
  background: none;
  border-radius: 0;
  box-shadow: none;
}
.svc-quote::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  border-radius: 3px;
  background: var(--orange);
}
.svc-quote p {
  font-family: var(--font-display);
  font-weight: 600;
  /* A step up from the running copy: with no plate to lift it, size and weight
     are what separate the quote from the paragraph above it. It follows the
     heading down — the heading took --fs-h4 when it dropped a step, and a
     quote set level with the head above it, on a longer line and in a colour
     that does not step back, ends up the loudest thing in the article. One
     step under keeps the order: heading, then quote, then copy. */
  font-size: var(--fs-h5);
  line-height: var(--lh-lead);
  letter-spacing: var(--ls-heading);
  color: var(--ink);
  max-width: none;
  text-wrap: pretty;
}

/* ---- what you get ---- */
.svc-checks {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.65rem clamp(1.25rem, 3vw, 2rem);
  margin-top: clamp(1.5rem, 3vh, 2rem);
}
.svc-checks li {
  display: flex;
  gap: 0.65rem;
  align-items: flex-start;
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body);
  color: var(--muted);
}
/* a tick rather than a bullet, set in the mono so it lands on the cap line of
   the first word instead of floating above it */
.svc-checks li::before {
  content: "\2713";
  font-family: var(--font-mono);
  font-size: 0.95em;
  color: var(--orange);
  flex-shrink: 0;
}

/* ---- the two-up ---- */
.svc-duo {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(1.1rem, 2.4vw, 1.75rem);
  margin-top: clamp(1.75rem, 3.5vh, 2.25rem);
}
.svc-duo figure { margin: 0; }
.svc-duo img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
}
/* The app page's two-up is a pair of phone renders rather than two website
   shots, and a portrait frame forced into the 4:3 above loses the top and the
   bottom of the handset - which is most of what there was to look at. The
   modifier turns the pair upright; 3/4 is the native ratio of those renders, so
   nothing is cropped at all. */
.svc-duo--tall img { aspect-ratio: 3 / 4; }
/* ...and held well under the column they sit in. Two portrait frames at the
   full half-width of the article came out around 400x533 each, which put two
   phone renders on the page at nearly the size of the lead plate above them -
   a supporting picture shouting over the thing it supports. Capped and centred
   in their columns they read as a pair of handsets shown at hand size, and the
   caption tracks the frame rather than running out past it on both sides. */
.svc-duo--tall figure {
  max-width: 17rem;
  margin-inline: auto;
}
.svc-duo figcaption {
  margin-top: 0.85rem;
  font-size: var(--fs-meta);
  line-height: var(--lh-body);
  color: var(--muted);
  text-wrap: pretty;
}

/* the accordion is the site's own — only the space above it is this page's */
.svc-article .faq-list { margin-top: clamp(1.5rem, 3vh, 2rem); }

@media (max-width: 1024px) {
  /* One column, in source order: the article, then the rail. A visitor who
     opened a service wants to read it, not to be handed the index of the five
     they did not open — and because that is the markup order too, it is what
     a tab press and a screen reader get as well, not just what the eye gets. */
  .svc-grid { grid-template-columns: minmax(0, 1fr); }
  .svc-rail,
  .svc-article { grid-column: auto; grid-row: auto; }
  .svc-rail { position: static; }
}

@media (max-width: 560px) {
  .svc-checks,
  .svc-duo { grid-template-columns: minmax(0, 1fr); }
}

/* The service rows are anchor targets now — the detail page's rail links back
   to the four it does not cover. The bar is fixed, so a row jumped to from
   another page would otherwise land underneath it. */
.project-row { scroll-margin-top: clamp(6rem, 12vh, 8rem); }

/* ============================================================
   PROJECTS — the index of the work, on the printed ground
   ------------------------------------------------------------
   The home page shows the work as a moving thing: three columns
   scrubbed off the page's own scroll, on black, with the cards
   moulded out of the ground. That is a showreel — it is meant to
   be watched. This page is the opposite job. It is the index, and
   an index has to hold still long enough to be read, so the same
   twelve pieces are laid out flat on the cream field the About and
   Services pages run: no stage, no scrub, no black.

   There is no caption on a tile - the pill, the title and the
   line under it came off, and what is left is twelve pictures on
   one grid. What each one is has not been dropped, only undrawn:
   it is carried by the link's aria-label and the image's alt, so
   a screen reader still hears "SJU Advokater, a concept law firm
   site" where an eye now sees only the picture.

   The tiles keep their own ratios rather than being trimmed to one
   box. Each card carries its ratio as --ar, and with the caption
   gone a card IS its picture - so a row is level exactly when its
   three ratios agree. The order is chosen so they do: the websites
   run 3:2, the storefronts 4:3, and the last six are square.

   Nothing is enlarged. Every source is 900px on its long edge and
   the widest a tile ever gets is 423px at the container's cap,
   which is still inside the file's own pixels at 2x - so the grid
   is sharp on a retina screen rather than merely not-blurry on a
   1x one.
   ============================================================ */
.projects {
  position: relative;
  padding: var(--section-y) var(--gutter) clamp(3rem, 7vh, 5rem);
}
.projects-inner {
  max-width: var(--container);
  margin: 0 auto;
}
/* the note is the one paragraph the head carries, and it sits under the
   heading rather than beside it: the link on the right already has that seat */
.projects-note {
  margin-top: clamp(1.1rem, 2.5vh, 1.6rem);
  max-width: 58ch;
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body);
  color: var(--muted);
  text-wrap: pretty;
}

.proj-grid {
  margin-top: clamp(2.25rem, 5vh, 3.5rem);
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1rem, 2vw, 1.75rem);
}
/* Two up, then one.
   Dropping to two columns re-pairs the twelve, and two of the new pairs would
   straddle a ratio change: Corretto (3:2) lands beside Fashion Store (4:3),
   and Aurel Eyewear (1:1) beside Velor (4:5). A row whose two pictures are
   different heights puts their titles on different lines, which is exactly the
   raggedness the per-row grouping exists to prevent — so the second card of
   each straddling pair takes its neighbour's ratio for the width of the
   two-column layout and hands it back at three.
   Neither override costs anything: Velor is natively square, so at 1:1 it is
   not cropped at all, and Fashion Store is a centred phone-in-hand shot that
   loses 5% off each margin at 3:2. Below 620px every card is alone in its row
   and any ratio sits level with itself, so the overrides stop there. */
/* 1024 is the site's own tablet break - it is where .svc-grid drops its rail
   beside the article - and three columns were already too tight by then: at
   901px a card measured 264px, which puts a two-line title and a four-line
   caption into a box the width of a phone. */
@media (max-width: 1024px) {
  .proj-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* Set on the frame, not on the card: --ar is written as an inline style per
   card, and an inline custom property beats a stylesheet rule at any width, so
   redeclaring --ar here would never take. The frame's own aspect-ratio has no
   inline value to lose to.
   Only one card needs this now. The second straddling pair fixed itself when
   the last row went square: the back half of the grid is six square cards, so
   every two-column pair in it already matches. */
@media (min-width: 621px) and (max-width: 1024px) {
  .proj-card--2up-wide .proj-media { aspect-ratio: 3 / 2; }
}
@media (max-width: 620px) {
  .proj-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- the card ----------
   A picture and nothing else. The pill, the title and the caption are gone, so
   what is left is the plate the image sits on: the page's own card radius, the
   page's own drop shadow, and no cream showing anywhere - the frame fills the
   card edge to edge. The name of each piece has not disappeared from the page,
   it has moved into the image's alt, which is where a caption that is not
   drawn still has to live.

   A <figure> and not a link. These twelve used to carry you off to the service
   that built them, which put the index of the work in the odd position of
   being the one page you could not stay on: every picture on it was an exit,
   and the exits led to five pages the bar already lists twice over. The work
   is the point of this page, so it is now a page of work rather than a menu
   wearing photographs. Nothing here is hover-lit or lifted either - a plate
   that rises to a cursor is a plate promising something to click.
   `display: block` rather than the old column flex: there is one child now,
   and margin:0 because a figure brings its own. */
.proj-card {
  position: relative;
  display: block;
  margin: 0;
  border-radius: var(--radius-card);
  overflow: hidden;
  box-shadow: var(--shadow-card);
}
.proj-media {
  position: relative;
  display: block;
  aspect-ratio: var(--ar, 4 / 3);
  /* the plate's own tone, so the few renders that carry their own margin sit
     on the page rather than on a white card the site does not otherwise use */
  background: var(--cream-plate);
  /* The frame is the whole card now, so it takes the card's corners and does
     its own clipping. Rounding only the parent and letting a transformed child
     be cut by it is where the stray light-coloured pixel in a rounded corner
     comes from on hover - the corner is composited from the parent's clip
     while the child is on its own layer. Clipping on the element that actually
     holds the moving image keeps the arc exact. */
  border-radius: inherit;
  overflow: hidden;
  /* the image is a separate layer while it is scaling; this keeps the rounded
     clip and that layer in one stacking context rather than two */
  isolation: isolate;
}
/* The Qwick render is the one picture on the page that cannot be cropped: it
   is a single device, centred, and `cover` in a square frame clips the top and
   bottom off the phone. So this one is fitted instead of filled - and the
   letterbox that would normally give that away is invisible here, because the
   render's own ground is #000 (sampled, not guessed) and the plate is set to
   exactly that. Not var(--black), which is #111 and would print a visible
   lighter border around a darker picture. */
.proj-media--fit {
  background: #000;
  /* Handed down rather than selected for. "proj-media--fit" is one class token,
     not two, so `.proj-media--fit img` and `.proj-media img` carry exactly the
     same specificity and the one written later simply wins - which it did.
     A custom property sidesteps the contest: it inherits to the image, and the
     base rule below reads it. */
  --fit: contain;
}
.proj-media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: var(--fit, cover);
  /* --bleed, set per card, is how far past its own edge a picture is pushed.
     Three of the website mockups are not bare screenshots: each is a screenshot
     drawn on a cream card with rounded corners, and that card is baked into the
     file - 50px of cream down each side of SJU, 47px top and bottom on iLaw.
     With a caption under the tile that frame read as part of the furniture. On
     a bare tile it read as a mistake, and an inconsistent one: the wide crop
     took iLaw's side margins off but left its top and bottom, so one tile was
     framed on four sides, one on two and one on none.
     Each value is the exact figure that puts that file's own frame outside the
     tile - 1/(1 - 2f), f being the band as a fraction of the rendered picture -
     so it is a crop to the artwork's edge rather than a guess at a nice number,
     and being a ratio it holds at every viewport. Nothing else carries a frame,
     so nothing else carries a bleed. */
  transform: scale(var(--bleed, 1));
}

/* ---------- the foot ----------
   Same shape as the services list's foot: one aside on the left, one round
   arrow on the right, collapsing to a stack on a narrow screen. */
.projects-foot {
  margin-top: clamp(2rem, 4.5vh, 3rem);
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  align-items: flex-start;
}
@media (min-width: 768px) {
  .projects-foot { flex-direction: row; justify-content: space-between; align-items: center; }
}
.projects-foot p {
  max-width: 54ch;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body);
  text-wrap: pretty;
}

/* ============================================================
   HEADER NAV — the Services dropdown
   ------------------------------------------------------------
   Services is the only item in the bar with a page under it, so it
   is the only one that opens. The link and the disclosure are two
   separate controls on purpose: the word still goes to /services
   for anyone who just wants the list, and the caret beside it opens
   the five. A single control cannot do both without stealing one
   of the two jobs.

   Placed after .site-header.over-hero's nav rules so the panel's
   own links stay ink-on-cream even while the bar is inverted over
   a photograph — the panel is its own surface, not part of the bar.
   ============================================================ */
.nav-item {
  position: relative;
  display: inline-flex;
  align-items: center;
}
/* the pill loses its right cheek so the caret sits inside the same shape */
.main-nav .nav-item > a { padding-right: 0.55em; }

.nav-caret {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 0;
  cursor: pointer;
  color: inherit;
  padding: 0.62em 0.7em 0.62em 0.15em;
  border-radius: var(--radius-pill);
  transition: background 0.28s var(--ease-out), color 0.28s var(--ease-out);
}
.nav-caret svg {
  width: 0.62rem;
  height: 0.62rem;
  display: block;
  transition: transform 0.3s var(--ease-out);
}
.nav-caret:hover { background: var(--ink); color: var(--cream); }
.site-header.over-hero .nav-caret:hover { background: var(--cream); color: var(--ink); }
.nav-item.open .nav-caret svg { transform: rotate(180deg); }

.nav-sub {
  position: absolute;
  top: calc(100% + 0.55rem);
  left: 0;
  z-index: 120;
  min-width: 16rem;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.5rem;
  background: var(--cream-panel);
  border: 1px solid color-mix(in srgb, var(--ink) 8%, transparent);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card-hover);
  /* visibility, not display: the panel has to be able to transition, and a
     display:none box cannot. It is also taken out of the tab order while
     hidden, which display:none does for free and opacity alone does not. */
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity 0.25s var(--ease-out),
              transform 0.25s var(--ease-out),
              visibility 0.25s var(--ease-out);
}
/* hover only where there is a real pointer — on a touch screen the caret is
   the way in, and a hover rule there fires on the tap that was meant to
   follow the link */
@media (hover: hover) and (pointer: fine) {
  .nav-item:hover .nav-sub { opacity: 1; visibility: visible; transform: translateY(0); }
}
/* Deliberately NOT :focus-within. The panel is `visibility: hidden` when
   closed, so its links are out of the tab order and focus can only be inside
   it once the caret has already opened it — which means :focus-within would
   never open anything it did not already have open. What it WOULD do is make
   Escape impossible: the handler closes the menu and hands focus back to the
   caret, and a :focus-within rule reads that returning focus as "still open"
   and reopens on the spot. Keyboard state is the .open class only, set by the
   caret and cleared on focusout in main.js. */
.nav-item.open .nav-sub { opacity: 1; visibility: visible; transform: translateY(0); }

/* Every rule here is written twice, once plain and once under .over-hero.
   That is not belt-and-braces, it is arithmetic: `.site-header.over-hero
   .main-nav a` is three classes and turns nav links cream so they can sit on
   the hero photograph. A two-class `.main-nav .nav-sub a` loses to it no
   matter how far down the sheet it is written — being later only breaks ties
   at EQUAL specificity — and the panel's links came out cream on a cream
   panel, i.e. invisible. The panel is its own surface and is never on the
   photograph, so it always sets ink on cream. */
.main-nav .nav-sub a,
.site-header.over-hero .main-nav .nav-sub a {
  padding: 0.62em 0.85em;
  border-radius: calc(var(--radius-card) - 0.5rem);
  color: var(--ink);
  /* One percent over the --fs-ui-sm step the rest of the micro-UI takes. It is
     deliberately not a step of its own: the panel's rows only needed to clear
     the pills they sit under by a hair, and promoting them to --fs-ui would
     have set them level with the bar's own links. */
  font-size: calc(var(--fs-ui-sm) * 1.01);
  letter-spacing: var(--ls-ui);
  white-space: nowrap;
}
.main-nav .nav-sub a:hover,
.site-header.over-hero .main-nav .nav-sub a:hover { background: var(--ink); color: var(--cream); }
/* the service this page is */
.main-nav .nav-sub a[aria-current="page"],
.site-header.over-hero .main-nav .nav-sub a[aria-current="page"] { background: var(--orange); color: var(--ink); }

/* The last row is the way to the whole list rather than to one service.
   Note the `a` in each selector — it is load-bearing. The rules above end in
   an element selector, so a bare `.nav-sub__all` (four classes, no element)
   scores UNDER `.site-header.over-hero .main-nav .nav-sub a` and loses the
   colour it is trying to set. Qualifying the class with a.  puts each of
   these one step above the rule it has to override. */
.main-nav .nav-sub a.nav-sub__all,
.site-header.over-hero .main-nav .nav-sub a.nav-sub__all {
  margin-top: 0.3rem;
  padding-top: 0.55rem;
  border-top: 1px solid color-mix(in srgb, var(--ink) 10%, transparent);
  border-radius: 0;
  color: var(--muted);
}
.main-nav .nav-sub a.nav-sub__all:hover,
.site-header.over-hero .main-nav .nav-sub a.nav-sub__all:hover { background: none; color: var(--orange); }

/* ============================================================
   THE PHONE MENU — the panel behind the burger
   ------------------------------------------------------------
   What was here was the desktop bar stood on its end: five pills centred on a
   flat sheet of cream, no ground of its own, no order, and nothing to say
   where the page you are on sits among them. On a phone the menu is not a
   strip of chrome at the top of a page, it IS the page for as long as it is
   open, so it gets treated as one — a dark plate of the same ink the footer
   and the page banners are set on, an editorial list ranged left with the
   items numbered, and the studio's one hit of orange spent on the row you are
   standing in and on the button out.

   Everything that touches the bar is scoped to body.nav-open, so the closed
   header is untouched.
   ============================================================ */
@media (max-width: 720px) {

  /* ---------- the plate ----------
     Ink rather than cream, with the same pool of warm light at its foot that
     the footer runs, so the black has a floor instead of ending flat. It also
     scrolls: five rows plus an open sub-list plus the button is taller than a
     short phone in landscape, and a menu you cannot reach the bottom of is
     worse than a plain one. */
  body.nav-open .main-nav {
    display: flex;
    flex-direction: column;
    position: fixed;
    inset: 0;
    z-index: 90;
    background: var(--ink);
    color: var(--cream);
    /* the bar's own controls stay lifted over this, so the head clears them */
    padding: 6.25rem var(--gutter) 8.5rem;
    align-items: stretch;
    justify-content: flex-start;
    gap: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    counter-reset: navrow;
    font-size: inherit;
    text-transform: none;
    letter-spacing: normal;
  }
  /* No glow at the foot. The footer runs one because it is the bottom of a
     long scroll and the black needs a floor; a menu is neither, and a warm
     wash under the button only competed with the one orange thing on the plate
     that is meant to be looked at. Flat ink, and the colour spent once. */
  /* The panel names itself, in the same mono every section label on the site
     is set in. Without it the list starts on nothing and reads as five words
     dropped on a black field. */
  body.nav-open .main-nav::before {
    content: "Menu";
    font-family: var(--font-mono);
    font-size: var(--fs-label);
    font-weight: 500;
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--orange);
    margin-bottom: 1.5rem;
    flex: 0 0 auto;
  }

  /* the page behind stops scrolling under the plate */
  body.nav-open { overflow: hidden; }

  /* ---------- the bar, while the plate is up ----------
     The mark and the burger are lifted over the panel by z-index, which means
     they are now sitting on ink and have to invert with it. Both selectors
     carry .site-header so they outscore the .over-hero pair further up, which
     would otherwise still be setting the burger cream-on-cream the moment the
     menu is opened from anywhere below the hero. */
  body.nav-open .site-header .logo,
  body.nav-open .site-header .logo-mark { color: var(--cream); }
  body.nav-open .site-header .nav-toggle span { background: var(--cream); }
  /* The plate scrolls, and the two lifted controls do not — so on a short
     phone a row slides straight under the wordmark on its way past. This is
     the ground they need: ink across the height of the bar, then a short fade
     so the rows dissolve into it instead of being cut off on a hard line. It
     sits between the plate (90) and the controls (95). */
  body.nav-open .site-header::after {
    content: "";
    position: fixed;
    inset: 0 0 auto 0;
    height: 5.5rem;
    z-index: 94;
    background: linear-gradient(180deg,
                rgba(var(--black-rgb), 1) 0%,
                rgba(var(--black-rgb), 1) 62%,
                rgba(var(--black-rgb), 0) 100%);
    pointer-events: none;
  }

  /* ---------- the rows ----------
     One rule for the four plain links and the Services wrapper alike, so the
     five sit on one rhythm: a hairline above, an index on the left, the name
     ranged off it. */
  body.nav-open .main-nav > a,
  body.nav-open .main-nav > .nav-item {
    counter-increment: navrow;
    position: relative;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    /* flex-shrink: 0 is load-bearing, not tidying. The plate is a column flex
       box, so its rows shrink to fit by default — on a short phone with the
       services list open that compressed all five rows into the viewport and
       stacked the sub-list straight over Work, Process and Contact instead of
       letting the panel scroll. Refusing to shrink is what makes the overflow
       real, and the overflow is what the panel scrolls. */
    flex: 0 0 auto;
    width: 100%;
    min-height: 4rem;
    gap: 0.9rem;
    padding: 1.1rem 0;
    border-top: 1px solid rgba(var(--cream-rgb), 0.13);
    border-radius: 0;
    background: none;
    color: var(--cream);
    font-family: var(--font-display);
    font-weight: var(--fw-display);
    font-size: var(--fs-h4);
    line-height: var(--lh-display);
    letter-spacing: var(--ls-display);
    text-transform: uppercase;
    transition: color 0.28s var(--ease-out), transform 0.28s var(--ease-out);
  }
  body.nav-open .main-nav > :first-child { border-top: 0; }

  /* The index. Mono, and the only thing in the row that is not cream — it is
     the rule the eye runs down. */
  body.nav-open .main-nav > a::before,
  body.nav-open .main-nav > .nav-item::before {
    content: counter(navrow, decimal-leading-zero);
    flex: 0 0 auto;
    font-family: var(--font-mono);
    font-size: var(--fs-micro);
    font-weight: 500;
    letter-spacing: var(--ls-label);
    color: var(--muted-light);
    transition: color 0.28s var(--ease-out);
  }

  /* The bar's pill hover is ink-on-cream and would paint the row black on
     black. On a touch screen there is no hover worth the name anyway, so the
     feedback that matters is the press: the row warms and shifts a hair. */
  body.nav-open .main-nav > a:hover,
  body.nav-open .main-nav > a:focus-visible { background: none; color: var(--orange); }
  body.nav-open .main-nav > a:active { color: var(--orange); transform: translateX(3px); }
  body.nav-open .main-nav > a:active::before { color: var(--orange); }

  /* the page you are standing in */
  body.nav-open .main-nav > a[aria-current],
  body.nav-open .main-nav .nav-item > a[aria-current] { color: var(--orange); }
  body.nav-open .main-nav > a[aria-current]::before { color: var(--orange); }

  /* ---------- the Services row ----------
     Still two controls: the word goes to /services for anyone who wants the
     list, the caret opens the five. The caret grows into a real target on the
     right-hand end — a 12px chevron with 6px of padding round it is not
     something a thumb can hit. */
  .nav-item { flex-direction: row; }
  body.nav-open .main-nav .nav-item > a {
    flex: 1 1 auto;
    padding: 0;
    border-radius: 0;
    background: none;
    color: inherit;
  }
  body.nav-open .main-nav .nav-item > a:hover { background: none; color: var(--orange); }
  body.nav-open .nav-caret {
    flex: 0 0 auto;
    margin-left: auto;
    width: 2.15rem;
    height: 2.15rem;
    padding: 0;
    border: 1px solid rgba(var(--cream-rgb), 0.22);
    border-radius: var(--radius-pill);
    background: none;
    color: var(--cream);
  }
  body.nav-open .nav-caret svg { width: 0.8rem; height: 0.8rem; }
  body.nav-open .nav-item.open .nav-caret,
  body.nav-open .nav-caret:hover {
    background: var(--orange);
    border-color: var(--orange);
    color: var(--ink);
  }

  /* ---------- the five under it ----------
     Not a dropdown any more — there is nothing to drop over. It becomes an
     indented list hanging off a rule, set in the mono the rest of the site's
     secondary UI takes so it cannot be mistaken for another top-level row.
     display is the switch rather than visibility, so a closed list takes no
     room in the column. */
  .nav-sub {
    position: static;
    display: none;
    min-width: 0;
    background: none;
    border: 0;
    box-shadow: none;
    opacity: 1;
    visibility: visible;
    transform: none;
  }
  .nav-item.open .nav-sub { display: flex; }
  body.nav-open .main-nav .nav-sub {
    flex-basis: 100%;
    gap: 0;
    /* the indent lines up under the row's title, past the index */
    margin: 0.35rem 0 0.5rem 2.35rem;
    padding: 0.25rem 0 0.15rem 1.1rem;
    border-left: 1px solid rgba(var(--cream-rgb), 0.16);
    /* stretch, not flex-start: shrink-wrapped rows give the All-services rule
       above the last item the width of the words rather than the width of the
       list, and halve the target every row offers a thumb. */
    align-items: stretch;
  }
  body.nav-open .main-nav .nav-sub a,
  body.nav-open .site-header.over-hero .main-nav .nav-sub a {
    padding: 0.52rem 0;
    border-radius: 0;
    background: none;
    color: var(--cream-mute);
    font-family: var(--font-mono);
    font-size: var(--fs-meta);
    font-weight: 500;
    letter-spacing: 0.01em;
    text-transform: none;
    text-align: left;
    white-space: normal;
  }
  body.nav-open .main-nav .nav-sub a:hover,
  body.nav-open .main-nav .nav-sub a:active,
  body.nav-open .site-header.over-hero .main-nav .nav-sub a:hover {
    background: none;
    color: var(--orange);
  }
  body.nav-open .main-nav .nav-sub a[aria-current="page"],
  body.nav-open .site-header.over-hero .main-nav .nav-sub a[aria-current="page"] {
    background: none;
    color: var(--orange);
  }
  /* the way to the whole list, kept apart from the five */
  body.nav-open .main-nav .nav-sub a.nav-sub__all,
  body.nav-open .site-header.over-hero .main-nav .nav-sub a.nav-sub__all {
    margin-top: 0.3rem;
    padding-top: 0.6rem;
    border-top: 1px solid rgba(var(--cream-rgb), 0.16);
    color: var(--muted-light);
    font-size: var(--fs-micro);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
  }

  /* ---------- the way out ----------
     The bar's button is hidden on a phone, which left the menu as five links
     and no action — the one thing the whole site is pointed at was the one
     thing the phone menu did not offer. It comes back here, pinned to the
     foot of the plate and taken to orange: on ink, .btn-solid's ink fill is
     invisible, and this is the only place in the panel besides the current
     row that earns the colour. */
  body.nav-open .site-header .header-cta {
    display: inline-flex;
    position: fixed;
    bottom: max(1.75rem, calc(env(safe-area-inset-bottom) + 1rem));
    z-index: 95;
    justify-content: center;
    /* 3% off the whole box, taken evenly on all four sides.
         width  — 97% of the gutter-to-gutter span the plate's rows run to,
                  auto margins putting the 3% back as equal air left and right.
         height — the block padding carries it. 1.05em is what the bar's button
                  takes; 0.9905em is the value that lands the finished height on
                  97% of that, given the line box between the two paddings does
                  not shrink with them.
       Done geometrically rather than with transform: scale(.97), which would
       have been one declaration but resamples the type off the pixel grid and
       leaves the site's primary call to action looking soft. */
    left: 0;
    right: 0;
    margin-inline: auto;
    width: calc((100% - 2 * var(--gutter)) * 0.97);
    padding: 0.9905em 1.5em;
    background: var(--orange);
    border-color: var(--orange);
    color: var(--ink);
  }
  body.nav-open .site-header .header-cta:active { transform: translateY(1px); }

  /* ---------- the open ----------
     A transition cannot carry this: the rows go from display:none to laid out
     in the same frame, and a transition has no start value to run from. An
     animation does, and it fires the moment the box first exists. */
  @media (prefers-reduced-motion: no-preference) {
    body.nav-open .main-nav::before,
    body.nav-open .main-nav > a,
    body.nav-open .main-nav > .nav-item {
      animation: nav-row-in 0.5s var(--ease-out) backwards;
    }
    body.nav-open .main-nav > :nth-child(1) { animation-delay: 0.05s; }
    body.nav-open .main-nav > :nth-child(2) { animation-delay: 0.10s; }
    body.nav-open .main-nav > :nth-child(3) { animation-delay: 0.15s; }
    body.nav-open .main-nav > :nth-child(4) { animation-delay: 0.20s; }
    body.nav-open .main-nav > :nth-child(5) { animation-delay: 0.25s; }
    body.nav-open .site-header .header-cta { animation: nav-row-in 0.5s var(--ease-out) 0.3s backwards; }
  }
}

@keyframes nav-row-in {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}
