/* ==========================================================================
   bypaul.io  |  tokens + base + home page
   Light-first, black and white base, single rust accent.
   ========================================================================== */

/* ---- Fonts ---- */
@font-face {
  font-family: 'Stelvio Grotesk';
  src: url('../fonts/stelvio-regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Stelvio Grotesk';
  src: url('../fonts/stelvio-medium.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* ---- Tokens ---- */
:root {
  /* Neutral (warm-tinted) */
  --neutral-50:  #fffdfd;
  --neutral-100: #faf5f5;
  --neutral-200: #e8e2e2;
  --neutral-300: #cdc7c7;
  --neutral-400: #ada7a7;
  --neutral-500: #8c8585;
  --neutral-600: #6b6565;
  --neutral-700: #4f4949;
  --neutral-800: #393333;
  --neutral-900: #2b2525;
  --neutral-950: #262020;

  /* Rust (brand accent) */
  --rust-50:  #fff4f4;
  --rust-100: #f9e1e1;
  --rust-200: #debaba;
  --rust-300: #bb8d8f;
  --rust-500: #96585c;
  --rust-700: #653d3f;

  /* System (functional feedback only, never decorative).
     Three roles per colour: 50 background, 500 border, 700 text. */
  --success-50: #ecfcec;
  --success-500: #49854c;
  --success-700: #1d5e23;
  --warning-50: #fef6e9;
  --warning-500: #956c1a;
  --warning-700: #654914;
  --error-50: #fff4f3;
  --error-500: #c0463e; /* 4.95:1 on the light bg */
  --error-700: #81322c;

  /* Type */
  --font-sans: 'Stelvio Grotesk', 'Helvetica Neue', Arial, sans-serif;
  --text-display: clamp(2.5rem, 6vw, 4.5rem);
  --text-body: 1.25rem;       /* 20px */
  --text-secondary: 1.125rem; /* 18px, one step down: controls and card copy */
  --text-small: 0.875rem;     /* 14px */
  --leading-body: 1.55;

  /* Layout */
  --container: 90rem;         /* 1440px */
  --gutter: clamp(1.25rem, 4vw, 2.5rem);
  --section-y: clamp(4.5rem, 10vw, 8rem);

  /* Breakpoints (used in media queries, which can't read custom properties;
     documented here as the single source of truth):
     40rem   - phone: nav becomes the overlay, facts stack to one column
     47.9rem - tablet: content grids (sectors, projects, testimonials, footer)
               collapse */

  /* Motion. One ease-out curve site-wide, plus three duration steps and a
     single stagger, so every animation shares the same language. GSAP mirrors
     this with power2.out (the same cubic ease-out). */
  --ease: cubic-bezier(0.33, 1, 0.68, 1);
  --dur-1: 0.3s;    /* interactive feedback: colour, underline, buttons */
  --dur-2: 0.45s;   /* hover movement: row shift, image zoom, overlay */
  --dur-3: 0.9s;    /* entrances and scroll reveals */
  --stagger: 0.09s;
}

/* ---- Reset / base ---- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  overflow-x: clip; /* full-bleed banners use 100vw */
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: 400;
  line-height: var(--leading-body);
  color: var(--neutral-950);
  background: var(--neutral-50);
  -webkit-font-smoothing: antialiased;
}

img, svg { display: block; max-width: 100%; }

a { color: inherit; }

.container {
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* Accent applied identically everywhere: links get a rust underline */
main p a,
main h1 a {
  color: inherit;
  text-decoration: underline; /* normal underline: text colour */
  text-decoration-thickness: 0.09375rem; /* matches the animated underline */
  text-underline-offset: 0.2em;
  transition: color var(--dur-1) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  /* Prose links behave the same wherever they lead: part of a sentence,
     so the underline holds; the whole link deepens to rust on hover. */
  main p a:hover,
  main h1 a:hover { color: var(--rust-700); }
}

:focus-visible {
  outline: 0.125rem solid var(--rust-500);
  outline-offset: 0.1875rem;
}

/* Dark surfaces: rust-500 measures 2.94:1 against neutral-950, under the 3:1
   WCAG minimum for focus indicators. The paler rose passes at 5.6:1 and is
   the palette's own dusty-rose-on-dark rule. */
.closing :focus-visible,
.site-footer :focus-visible,
body.nav-open .site-header :focus-visible {
  outline-color: var(--rust-300);
}

::selection {
  background: var(--rust-100);
  color: var(--neutral-950);
}

/* Visually hidden until keyboard focus: the skip link and the marquee pause
   button share this pattern so keyboard users get them without any visual
   cost to everyone else. */
.skip-link,
.clients__pause {
  position: absolute;
  left: -100rem;
  z-index: 200;
  font-size: 1rem;
  font-weight: 500;
  color: var(--neutral-50);
  background: var(--neutral-950);
  border: 0;
  padding: 0.75rem 1.25rem;
  text-decoration: none;
  cursor: pointer;
}
.skip-link:focus,
.clients__pause:focus {
  left: var(--gutter);
}
.skip-link { top: 0.75rem; }
.clients__pause { top: 50%; transform: translateY(-50%); }

/* ---- Buttons ---- */
.button {
  display: inline-block;
  font-size: var(--text-secondary);
  font-weight: 500;
  text-decoration: none;
  padding: 0.85rem 1.75rem;
  border: 0.0625rem solid transparent;
  transition: background-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .button:hover { transform: translateY(-0.0625rem); }
}
.button:active { transform: translateY(0.0625rem); }

.button--primary {
  background: var(--rust-500);
  color: var(--neutral-50);
}
@media (hover: hover) and (pointer: fine) {
  .button--primary:hover { background: var(--rust-700); }
}

/* Secondary inherits its context: dark text on the light hero, light text in
   the dark closing section. */
.button--secondary {
  background: transparent;
  color: inherit;
  border-color: var(--neutral-400);
}
@media (hover: hover) and (pointer: fine) {
  .button--secondary:hover { border-color: var(--neutral-950); }
}
/* Hover rule on dark surfaces: rust lightens 500 -> 300 (on light it
   deepens 500 -> 700). Ink label keeps the pale fill readable. */
.closing .button--primary { transition: background-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease); }
@media (hover: hover) and (pointer: fine) {
  .closing .button--primary:hover,
  .sg__row--dark .button--primary:hover {
    background: var(--rust-300);
    color: var(--neutral-950);
  }
}
.closing .button--secondary,
.sg__row--dark .button--secondary { border-color: var(--neutral-600); }
@media (hover: hover) and (pointer: fine) {
  .closing .button--secondary:hover,
  .sg__row--dark .button--secondary:hover { border-color: var(--neutral-50); }
}

.text-link {
  display: inline-block;
  margin-top: 2.5rem;
  line-height: 1;
  padding-bottom: 0.2em;
  font-weight: 500;
  color: var(--neutral-950);
  text-decoration: none;
  background: linear-gradient(var(--rust-500), var(--rust-500)) no-repeat left bottom / 0% 0.09375rem;
  transition: background-size var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .text-link:hover {
    color: var(--rust-700);
    background-size: 100% 0.09375rem;
  }
}

/* "All testimonials" and its section-head kin sit alone against a band edge,
   so they carry the prose underline at rest rather than the hover-grow */
.section-head .text-link {
  text-decoration: none;
  background: linear-gradient(var(--rust-500), var(--rust-500)) no-repeat right bottom / 100% 0.09375rem;
  transition: background-size var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .section-head .text-link:hover {
    color: var(--rust-700);
    background-size: 0% 0.09375rem;
  }
}

/* balance, not pretty: the hero split wraps each word in an inline-block span,
   which Chrome's pretty logic won't re-wrap around; balance works geometrically
   so long titles break evenly instead of orphaning the last word. */
h1 { text-wrap: balance; }
/* Regular for display through row scale; hierarchy comes from size and
   spacing. Small headings sitting against body copy (card titles, FAQ
   questions, legends, legal h2) carry 500 in their own rules. */
h1, h2, h3, h4, h5, h6 { font-weight: 400; }

/* ---- Section title ---- */
.section-title {
  font-size: var(--text-body);
  font-weight: 500;
  margin-bottom: clamp(2rem, 5vw, 3.5rem);
}

/* Section header with a trailing link (keeps "view all" anchored to its heading) */
.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  margin-bottom: clamp(2rem, 5vw, 3.5rem);
}
.section-head .section-title { margin-bottom: 0; }
.section-head .text-link { margin-top: 0; }

/* ==========================================================================
   Header
   ========================================================================== */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  border-bottom: 0.0625rem solid var(--neutral-200);
  background: var(--neutral-50);
  transition: background-color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}
.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 4.5rem; /* 72px */
}
.site-header__logo { color: var(--neutral-950); flex-shrink: 0; transition: color var(--dur-1) var(--ease); }
.site-header__logo svg { height: 2.1rem; width: auto; display: block; }
@media (hover: hover) and (pointer: fine) {
  .site-header__logo:hover { color: var(--rust-700); }
}

/* Desktop: links sit inline in the bar */
.site-header__nav {
  display: flex;
  align-items: center;
  gap: clamp(1.25rem, 3vw, 2.5rem);
}
.site-header__nav a {
  text-decoration: none;
  font-size: 1rem;
  font-weight: 500;
  line-height: 1;
  padding-bottom: 0.2em;
  background: linear-gradient(var(--rust-500), var(--rust-500)) no-repeat left bottom / 0% 0.09375rem;
  transition: background-size var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .site-header__nav a:hover { background-size: 100% 0.09375rem; }
}
.site-nav__toggle { display: none; }

/* Mobile: menu button opens a full-page dark overlay (toggled by nav.js) */
@media (max-width: 40rem) {
  .site-header__logo { position: relative; z-index: 2; }

  .site-nav__toggle {
    display: flex;
    position: relative;
    z-index: 2;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    margin-right: -0.5rem;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--neutral-950);
    cursor: pointer;
    transition: color var(--dur-1) var(--ease);
  }
  .site-nav__toggle:focus-visible {
    outline: 0.125rem solid var(--rust-500);
    outline-offset: 0.125rem;
  }

  .site-nav__bars,
  .site-nav__bars::before,
  .site-nav__bars::after {
    content: "";
    display: block;
    width: 1.5rem;
    height: 0.125rem;
    background: currentColor;
    transition: transform var(--dur-1) var(--ease), top var(--dur-1) var(--ease), background-color var(--dur-1) var(--ease);
  }
  .site-nav__bars { position: relative; }
  .site-nav__bars::before { position: absolute; left: 0; top: -0.5rem; }
  .site-nav__bars::after { position: absolute; left: 0; top: 0.5rem; }

  .site-header__nav {
    position: fixed;
    inset: 0;
    z-index: 1;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    gap: clamp(1rem, 4vh, 2rem);
    padding: 6rem clamp(1.5rem, 8vw, 3rem) 3rem;
    background: var(--neutral-950);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity var(--dur-2) var(--ease), visibility var(--dur-2) var(--ease);
  }
  .site-header__nav a {
    font-size: var(--text-display);
    line-height: 1.05;
    color: var(--neutral-50);
    padding-bottom: 0.05em;
    background-image: linear-gradient(var(--rust-300), var(--rust-300));
    background-position: left bottom;
  }

  body.nav-open { overflow: hidden; }
  body.nav-open .site-header { background: transparent; border-color: transparent; }
  body.nav-open .site-header__logo { color: var(--neutral-50); }
  body.nav-open .site-nav__toggle { color: var(--neutral-50); }
  body.nav-open .site-header__nav {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
  }
  body.nav-open .site-nav__bars { background: transparent; }
  body.nav-open .site-nav__bars::before { top: 0; transform: rotate(45deg); }
  body.nav-open .site-nav__bars::after { top: 0; transform: rotate(-45deg); }
}

@media (prefers-reduced-motion: reduce) {
  .site-nav__bars,
  .site-nav__bars::before,
  .site-nav__bars::after { transition: none; }
  .site-header__nav { transition: opacity 0.01ms, visibility 0.01ms; }
}

/* ==========================================================================
   Hero
   ========================================================================== */
.hero {
  padding-block: clamp(5rem, 12vw, 9rem);
}
/* Hero option B: headline-led */
.hero__headline {
  font-size: var(--text-display);
  line-height: 1.0;
  letter-spacing: -0.015em;
  max-width: 13em;
}
.hero__sub {
  font-size: clamp(1.25rem, 1.8vw, 1.5rem);
  line-height: 1.5;
  color: var(--neutral-700);
  max-width: 30em;
  margin-top: 1.75rem;
}
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin-top: 2.5rem;
}

/* Signature hero reveal (GSAP). Words rise and fade in with a stagger;
   supporting elements ease up under them. The hidden start state is applied
   only under .has-motion, so there is no flash, and the head safety timer
   restores full visibility if GSAP never runs. */
.hero__word {
  display: inline-block;
}
.has-motion .hero__headline,
.has-motion .hero__sub,
.has-motion .hero__actions,
.has-motion .clients,
.has-motion [data-hero-follow] {
  opacity: 0;
}

/* ==========================================================================
   Client strip
   ========================================================================== */
.clients {
  --logo-gap: clamp(5rem, 11vw, 10rem);
  position: relative; /* anchors the focus-visible pause button */
  border-block: 0.0625rem solid var(--neutral-200);
  padding-block: 2rem;
  overflow: hidden;
}
.clients__marquee {
  display: flex;
  width: max-content;
  gap: var(--logo-gap);
}
.clients__track {
  list-style: none;
  display: flex;
  align-items: center;
  gap: var(--logo-gap);
  flex-shrink: 0;
  font-size: 1rem;
  color: var(--neutral-600);
  white-space: nowrap;
}
.clients__track > li {
  display: flex;
  align-items: center;
  flex: none;
}
/* Logos are optically matched to the text names, not mathematically.
   Each wordmark needs its own height to sit at the same weight. */
.client-logo {
  height: 1.5rem;
  width: auto;
  display: block;
}
.client-logo[aria-label="Kreative Kiddle"]           { height: 0.95rem; }
.client-logo[aria-label="The Pilot Boat"]            { height: 1.07rem; }
.client-logo[aria-label="Airship"]                   { height: 1.86rem; }
.client-logo[aria-label="Juno"]                      { height: 1.87rem; }
.client-logo[aria-label="SIGB"]                      { height: 1.42rem; }
.client-logo[aria-label="Craft House"]               { height: 1.77rem; }
.client-logo[aria-label="Stacked"]                   { height: 1.21rem; }
.client-logo[aria-label="Greene King"]               { height: 2.60rem; }
.client-logo[aria-label="Metro Pub Co"]              { height: 2.45rem; }
.client-logo[aria-label="PubLove"]                   { height: 1.44rem; }
.client-logo[aria-label="Tech on Toast"]            { height: 2.10rem; }
.client-logo[aria-label="TCL Solar"]                 { height: 0.70rem; }
.client-logo[aria-label="TCL SunPower"]              { height: 0.89rem; }
.client-logo[aria-label="Fanzo"]                     { height: 1.15rem; }
.client-logo[aria-label="OpenTable"]                 { height: 1.30rem; }
.client-logo[aria-label="Access"]                    { height: 1.25rem; }
.client-logo[aria-label="DesignMyNight"]             { height: 1.80rem; }
.client-logo[aria-label="Mailchimp"]                 { height: 1.80rem; }
.client-logo[aria-label="Zonal"]                     { height: 1.30rem; }

@media (prefers-reduced-motion: no-preference) {
  .clients__track {
    animation: marquee 40s linear infinite;
  }
  /* Paused via the accessible button (or a tap on touch, see marquee.js) */
  .clients.is-paused .clients__track {
    animation-play-state: paused;
  }
  @media (hover: hover) and (pointer: fine) {
    .clients__marquee:hover .clients__track {
      animation-play-state: paused;
    }
  }
  @keyframes marquee {
    to { transform: translateX(calc(-100% - var(--logo-gap))); }
  }
}
@media (prefers-reduced-motion: reduce) {
  .clients__track[aria-hidden] { display: none; }
  .clients__pause { display: none; } /* nothing moving to pause */
}

/* ==========================================================================
   Facts (quiet credibility row, no icons)
   ========================================================================== */
.facts__list {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2rem clamp(2rem, 5vw, 4rem);
}
/* Both stat rows share the same styling; just spaced apart. */
.facts__list + .facts__list {
  margin-top: clamp(1.75rem, 4vw, 2.75rem);
}
.fact {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}
/* Results row only: divider runs under each stat. The credentials row
   beneath sits clean. */
.facts__list:first-of-type .fact {
  border-bottom: 0.0625rem solid var(--neutral-200);
  padding-bottom: 1.25rem;
}

.fact__value {
  font-size: clamp(1.75rem, 3vw, 2.5rem);
  font-weight: 400;
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--neutral-900);
}

.fact__label {
  font-size: 1rem;
  color: var(--neutral-600);
  max-width: 18ch;
}

@media (max-width: 40rem) {
  .facts__list {
    grid-template-columns: 1fr;
  }
}

/* ==========================================================================
   Sectors (Who I work with)
   ========================================================================== */
.sectors { padding-block: var(--section-y); }

.sectors__list {
  list-style: none;
}
.sector-row {
  display: grid;
  grid-template-columns: minmax(10rem, 1fr) 2fr auto;
  align-items: baseline;
  gap: 1rem clamp(1.5rem, 4vw, 4rem);
  padding-block: clamp(1.5rem, 3.5vw, 2.5rem);
  /* Horizontal padding gives the hover highlight breathing room; the matching
     negative margin keeps the text aligned to the page edge. */
  padding-inline: clamp(1rem, 2vw, 1.75rem);
  margin-inline: calc(clamp(1rem, 2vw, 1.75rem) * -1);
  border-top: 0.0625rem solid var(--neutral-200);
  text-decoration: none;
  transition: background-color var(--dur-1) var(--ease);
}
/* The closing line belongs to the last row, so it reveals with it rather
   than sitting on the list as a lone border before the rows appear. */
.sectors__list li:last-child .sector-row {
  border-bottom: 0.0625rem solid var(--neutral-200);
}
.sector-row h3 {
  font-size: clamp(1.5rem, 2.6vw, 2.25rem);
  line-height: 1.1;
  transition: transform var(--dur-2) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .sector-row:hover h3 { transform: translateX(0.75rem); }
}
.sector-row p {
  color: var(--neutral-700);
  max-width: 34em;
}
.sector-row__arrow {
  font-size: 1.5rem;
  color: var(--rust-500);
  transition: transform var(--dur-2) var(--ease), color var(--dur-1) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .sector-row:hover { background: var(--neutral-100); }
  .sector-row:hover .sector-row__arrow { transform: translateX(-0.75rem); }
}

@media (max-width: 47.9rem) {
  .sector-row { grid-template-columns: 1fr auto; }
  .sector-row p { grid-column: 1 / -1; }
}

/* ==========================================================================
   Projects
   ========================================================================== */
.projects { padding-block: var(--section-y); }
/* Hub variant: the page hero above already provides the top spacing */
.projects--hub { padding-block: 0 var(--section-y); }
.projects--hub .button {
  display: block;
  width: fit-content;
  margin-top: clamp(2rem, 5vw, 3rem);
  margin-inline: auto;
}

.projects__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(1.5rem, 3.5vw, 3rem);
}
.project-card { text-decoration: none; }

.project-card__media {
  height: clamp(16rem, 30vw, 24rem);
  background: var(--neutral-100);
  border: 0.0625rem solid var(--neutral-200);
  overflow: hidden;
  transition: border-color var(--dur-1) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .project-card:hover .project-card__media { border-color: var(--neutral-400); }
}
/* Hover follows the primary/secondary split: the title underline and border
   answer instantly (dur-1), the image zoom is the secondary action and runs
   long (dur-3), Disney-style follow-through. */
.project-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-3) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .project-card:hover .project-card__media img { transform: scale(1.03); }
}
.project-card__meta { padding-top: 1rem; }
/* Project card meta: title and year share the first line, summary beneath */
.project-card__meta-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}
.project-card__year {
  color: var(--neutral-600);
  font-size: 1rem;
  font-variant-numeric: tabular-nums;
}
.project-card__meta h3 {
  font-size: 1.25rem;
  font-weight: 500;
  display: inline-block;
  line-height: 1;
  padding-bottom: 0.2em;
  background: linear-gradient(var(--rust-500), var(--rust-500)) no-repeat left bottom / 0% 0.09375rem;
  transition: background-size var(--dur-1) var(--ease);
}
.project-card__meta p {
  font-size: 1.125rem;
  color: var(--neutral-700);
  margin-top: 0.35rem;
}
@media (hover: hover) and (pointer: fine) {
  .project-card:hover .project-card__meta h3 { background-size: 100% 0.09375rem; }
}

/* View toggle: grid (default) or list. The list view restyles the same cards
   into archive-style rows, so there is one DOM and JS only flips a data
   attribute (persisted per visitor). The active view is marked by a thumb
   that slides between the two buttons on switch. */
.projects__toolbar {
  display: flex;
  justify-content: flex-end;
  margin-bottom: clamp(1.5rem, 3vw, 2.25rem);
}
.view-toggle {
  position: relative;
  display: inline-flex;
  border: 0.0625rem solid var(--neutral-200);
}
/* The active view is marked by a thumb that slides between the two buttons,
   so switching reads as one movement rather than an instant swap. */
.view-toggle::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 2.75rem;
  height: 100%;
  background: var(--neutral-900);
  transition: transform var(--dur-2) var(--ease);
}
.view-toggle[data-active="list"]::before { transform: translateX(2.75rem); }
/* Applied while the saved view is restored on load, so the thumb does not
   slide across on every page load for list-preferring visitors. */
.view-toggle--no-anim::before { transition: none; }
.view-toggle__btn {
  position: relative;
  z-index: 1;
  display: inline-grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--neutral-500);
  cursor: pointer;
  transition: color var(--dur-2) var(--ease);
}
.view-toggle__btn svg { width: 1.125rem; height: 1.125rem; display: block; }
@media (hover: hover) and (pointer: fine) {
  .view-toggle__btn:not([aria-pressed="true"]):hover { color: var(--neutral-800); }
}
.view-toggle__btn[aria-pressed="true"] { color: var(--neutral-50); }
@media (prefers-reduced-motion: reduce) {
  .view-toggle::before { transition: none; }
  /* Drop the hover movement, keep the row tint as the affordance. */
  .projects__grid[data-view="list"] .project-card:hover .project-card__meta-row h3,
  .archive__item:hover h3 { transform: none; }
}

/* List view: hide the media, hoist the meta onto the card grid, and lay the
   title, summary and year out across three columns like the archive rows. */
.projects__grid[data-view="list"] {
  display: block;
}
.projects__grid[data-view="list"] .project-card {
  display: grid;
  grid-template-columns: minmax(10rem, 1fr) 2fr auto;
  align-items: baseline;
  gap: 0.5rem clamp(1.5rem, 4vw, 4rem);
  padding-block: clamp(1.25rem, 2.5vw, 1.75rem);
  padding-inline: clamp(1rem, 2vw, 1.75rem);
  margin-inline: calc(clamp(1rem, 2vw, 1.75rem) * -1);
  border-top: 0.0625rem solid var(--neutral-200);
  transition: background-color var(--dur-1) var(--ease);
}
.projects__grid[data-view="list"] .project-card:last-child {
  border-bottom: 0.0625rem solid var(--neutral-200);
}
.projects__grid[data-view="list"] .project-card__media { display: none; }
.projects__grid[data-view="list"] .project-card__meta,
.projects__grid[data-view="list"] .project-card__meta-row { display: contents; }
.projects__grid[data-view="list"] .project-card__meta-row h3 {
  grid-column: 1;
  grid-row: 1;
  font-size: clamp(1.25rem, 1.8vw, 1.5rem);
  line-height: 1.2;
  /* No underline in list view: the rows use the archive hover (title shifts
     right, row tints) instead of the grid card's gradient underline. */
  background: none;
  padding-bottom: 0;
  transition: transform var(--dur-2) var(--ease);
}
.projects__grid[data-view="list"] .project-card__meta p {
  grid-column: 2;
  grid-row: 1;
  margin-top: 0;
}
.projects__grid[data-view="list"] .project-card__year {
  grid-column: 3;
  grid-row: 1;
}
@media (hover: hover) and (pointer: fine) {
  .projects__grid[data-view="list"] .project-card:hover { background: var(--neutral-100); }
  .projects__grid[data-view="list"] .project-card:hover .project-card__meta-row h3 { transform: translateX(0.75rem); }
}
@media (max-width: 47.9rem) {
  .projects__grid[data-view="list"] .project-card { grid-template-columns: 1fr auto; }
  .projects__grid[data-view="list"] .project-card__year { grid-column: 2; }
  .projects__grid[data-view="list"] .project-card__meta p { grid-column: 1 / -1; grid-row: 2; }
}

@media (max-width: 47.9rem) {
  .projects__grid { grid-template-columns: 1fr; }
}

/* ==========================================================================
   Testimonials
   ========================================================================== */
.testimonials {
  background: var(--rust-50);
  padding-block: var(--section-y);
}
.testimonials__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1.5rem, 4vw, 3.5rem);
}
.quote blockquote {
  font-size: clamp(1.25rem, 1.8vw, 1.5rem);
  line-height: 1.45;
  letter-spacing: -0.01em;
}
.quote figcaption {
  margin-top: 1.25rem;
  font-weight: 500;
  font-size: 1rem;
}
.quote figcaption span {
  display: block;
  font-weight: 400;
  font-size: var(--text-small);
  color: var(--neutral-600);
  margin-top: 0.15rem;
}

@media (max-width: 47.9rem) {
  .testimonials__grid { grid-template-columns: 1fr; }
}

/* ==========================================================================
   Closing CTA + footer (dark)
   ========================================================================== */
.closing {
  /* One step lighter than the 950 footer, so the two dark blocks read as
     separate surfaces rather than one slab. */
  background: var(--neutral-900);
  color: var(--neutral-50);
  padding-block: var(--section-y);
}
.closing__line {
  font-size: var(--text-display);
  line-height: 1.1;
  letter-spacing: -0.015em;
  max-width: 14em;
}
.closing__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin-top: clamp(2rem, 5vw, 3rem);
}

.site-footer {
  background: var(--neutral-950);
  color: var(--neutral-300);
  font-size: 1rem;
  padding-block: clamp(3rem, 7vw, 5rem) 2rem;
}
.site-footer__cols {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 2rem;
}
.site-footer__cols h3 {
  font-size: var(--text-small);
  font-weight: 500;
  /* neutral-400, not 500: 500 on neutral-950 is 4.43:1, just under WCAG AA */
  color: var(--neutral-400);
  margin-bottom: 0.85rem;
}
.site-footer__cols a {
  display: block;
  width: fit-content;
  text-decoration: none;
  color: var(--neutral-300);
  padding-top: 0.7rem;
  line-height: 1;
  padding-bottom: 0.2em;
  background: linear-gradient(var(--rust-300), var(--rust-300)) no-repeat left bottom / 0% 0.09375rem;
  transition: background-size var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .site-footer__cols a:hover {
    color: var(--neutral-50);
    background-size: 100% 0.09375rem;
  }
}

.site-footer__base {
  margin-top: clamp(2.5rem, 6vw, 4rem);
  padding-top: 1.5rem;
  border-top: 0.0625rem solid var(--neutral-800);
  font-size: var(--text-small);
  color: var(--neutral-400);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 1rem 2rem;
}
.site-footer__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1.25rem;
}
.footer-clocks {
  display: flex;
  gap: 1.25rem;
  padding-left: 1.25rem;
  border-left: 0.0625rem solid var(--neutral-800);
  font-variant-numeric: tabular-nums;
}
.footer-clocks__item::before { content: "["; color: var(--neutral-500); }
.footer-clocks__item::after { content: "]"; color: var(--neutral-500); }
.footer-clocks__label {
  color: var(--neutral-500);
  margin: 0 0.375rem 0 0.25rem;
}
.footer-clocks time { margin-right: 0.25rem; }
.footer-clocks time { color: var(--neutral-300); }
@media (max-width: 30rem) {
  .footer-clocks { padding-left: 0; border-left: none; }
}
.site-footer__legal {
  list-style: none;
  display: flex;
  gap: 1.5rem;
}
.site-footer__legal a {
  text-decoration: none;
  color: var(--neutral-400);
  line-height: 1;
  padding-bottom: 0.2em;
  background: linear-gradient(var(--rust-300), var(--rust-300)) no-repeat left bottom / 0% 0.09375rem;
  transition: background-size var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .site-footer__legal a:hover {
    color: var(--neutral-200);
    background-size: 100% 0.09375rem;
  }
}

@media (max-width: 47.9rem) {
  .site-footer__cols { grid-template-columns: 1fr 1fr; }
}

/* ==========================================================================
   Motion: hero load-in + scroll reveals
   CSS only, no JS. Everything collapses to static under reduced motion.
   ========================================================================== */
/* The hero load-in is driven entirely by GSAP (js/hero.js). The old CSS
   keyframe version was removed because its fill state overrode GSAP and
   revealed the sub/CTA before the headline. */

/* Scroll reveals: a tiny IntersectionObserver (js/reveal.js) adds .is-visible
   as each element enters the viewport. Chosen over CSS animation-timeline
   (unsupported on iOS Safari, the mobile benchmark) and over a motion library
   (far heavier for a plain fade-up). The hidden state applies only when
   .has-motion is set, which the head script adds solely when motion is allowed
   AND JS is running, so content is always visible if either is false. */
.has-motion :is(.section-title, .text-link, .fact, .sector-row, .project-card,
.archive__item, .study__item, .study__lead,
.reveal, .quote, .closing__line):not([data-hero-follow] *) {
  opacity: 0;
  transform: translateY(1.75rem);
  /* The reveal (opacity/transform) carries the stagger delay; the common hover
     properties are listed too, with no delay, so a revealed element keeps its
     own hover transition instead of this rule clobbering it. */
  transition: opacity var(--dur-3) var(--ease) var(--reveal-delay, 0s),
              transform var(--dur-3) var(--ease) var(--reveal-delay, 0s),
              background-color var(--dur-1) var(--ease),
              background-size var(--dur-1) var(--ease),
              color var(--dur-1) var(--ease),
              border-color var(--dur-1) var(--ease);
}
.has-motion .section-title.is-visible,
.has-motion .text-link.is-visible,
.has-motion .fact.is-visible,
.has-motion .sector-row.is-visible,
.has-motion .project-card.is-visible,
.has-motion .archive__item.is-visible,
.has-motion .study__item.is-visible,
.has-motion .study__lead.is-visible,
.has-motion .reveal.is-visible,
.has-motion .quote.is-visible,
.has-motion .closing__line.is-visible {
  opacity: 1;
  transform: none;
}

/* Closing CTA buttons reveal (staggered, after the title). Opacity only so the
   buttons keep their own transform-based hover lift, with the reveal delay
   scoped to the opacity transition alone. */
.has-motion .closing__actions .button {
  opacity: 0;
  /* Extra base delay so the buttons clearly follow the title, then stagger
     between themselves via --reveal-delay. */
  transition: opacity var(--dur-3) var(--ease) calc(0.4s + var(--reveal-delay, 0s)),
              background-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease),
              border-color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}
.has-motion .closing__actions .button.is-visible {
  opacity: 1;
}

/* Reduced motion: hover colour and tint feedback stays, but the movement
   (row shift, image zoom, button lift) is dropped. Placed last so it wins
   over the hover rules regardless of source order. */
@media (prefers-reduced-motion: reduce) {
  .sector-row:hover h3,
  .archive__item:hover h3,
  .sector-row:hover .sector-row__arrow,
  .project-card:hover .project-card__media img,
  .button:hover {
    transform: none;
  }
}

/* ==========================================================================
   Contact page
   ========================================================================== */
.hero--contact {
  padding-block: clamp(5rem, 12vw, 9rem) clamp(3rem, 6vw, 5rem);
}
.contact { padding-block: 0 var(--section-y); }
.contact__inner { max-width: 46rem; }

.form-step {
  border: 0;
  padding: 0;
  margin-bottom: clamp(2.5rem, 6vw, 3.5rem);
}
.form-step legend {
  font-size: clamp(1.25rem, 1.8vw, 1.5rem);
  font-weight: 500;
  margin-bottom: 1.25rem;
}
.req { color: var(--rust-500); }

/* Honeypot: parked off-screen, never display:none (bots skip that) */
.hp { position: absolute; left: -100rem; }

.form-step__num {
  color: var(--neutral-400);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  margin-right: 0.75rem;
}
.form-step__num::after { content: "."; }
.form-step__hint {
  font-size: 1rem;
  color: var(--neutral-700);
  margin: -0.75rem 0 1.25rem;
}

/* Radio choices styled as bordered options. The input stays real (hidden but
   focusable) so keyboard and screen reader behaviour is native. */
.choice-group {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}
.choice { position: relative; }
.choice input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  margin: 0;
  cursor: pointer;
}
.choice span {
  display: inline-block;
  font-size: var(--text-secondary);
  padding: 0.7rem 1.2rem;
  border: 0.0625rem solid var(--neutral-300);
  transition: border-color var(--dur-1) var(--ease), background-color var(--dur-1) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .choice:hover span { border-color: var(--neutral-500); }
}
.choice:has(input:checked) span {
  border-color: var(--rust-500);
  background: var(--rust-50);
}
.choice:has(input:focus-visible) span {
  outline: 0.125rem solid var(--rust-500);
  outline-offset: 0.1875rem;
}

/* Text fields */
.field { display: block; }
.field__label {
  display: block;
  font-size: 1rem;
  font-weight: 500;
  margin-bottom: 0.5rem;
}
.contact-form input[type="text"],
.contact-form input[type="email"],
.contact-form textarea {
  width: 100%;
  font: inherit;
  /* One step below body copy. Never below 1rem, which makes iOS zoom on focus. */
  font-size: var(--text-secondary);
  color: var(--neutral-950);
  background: var(--neutral-50);
  border: 0.0625rem solid var(--neutral-300);
  padding: 0.85rem 1rem;
  transition: border-color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
}
/* Focus eases in (border to rust plus a soft ring) instead of the hard
   outline jump; the outline is replaced, not removed. */
.contact-form input[type="text"]:focus-visible,
.contact-form input[type="email"]:focus-visible,
.contact-form textarea:focus-visible {
  outline: none;
  border-color: var(--rust-500);
  box-shadow: 0 0 0 0.1875rem var(--rust-100);
}
.contact-form textarea { resize: vertical; }
.contact-form ::placeholder { color: var(--neutral-600); opacity: 1; }

.field-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem;
}
@media (max-width: 40rem) {
  .field-row { grid-template-columns: 1fr; }
}

.contact-form__submit {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1.25rem;
}
.contact-form__error {
  margin-top: 1.5rem;
  color: var(--error-500);
}
.contact-form__error a { color: inherit; }

.contact-form__done h2 {
  font-size: clamp(1.75rem, 3vw, 2.5rem);
  margin-bottom: 0.75rem;
}
.contact-form__done:focus { outline: none; }

.contact-direct {
  margin-top: clamp(3.5rem, 8vw, 5.5rem);
  border-top: 0.0625rem solid var(--neutral-200);
  padding-top: clamp(2rem, 5vw, 3rem);
}
.contact-direct h2 {
  font-size: clamp(1.25rem, 1.8vw, 1.5rem);
  font-weight: 500;
  margin-bottom: 0.5rem;
}
.contact-direct p { max-width: 34em; }

/* Social links leave the site: the external state. Underlined at rest, the
   underline clears on hover (internal links do the reverse). */
.social-list {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.85rem 2rem;
}
.social-list a {
  display: inline-block;
  width: fit-content;
  color: var(--neutral-950);
  font-weight: 500;
  text-decoration: none;
  line-height: 1;
  padding-bottom: 0.2em;
  background: linear-gradient(var(--rust-500), var(--rust-500)) no-repeat left bottom / 100% 0.09375rem;
  transition: background-size var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .social-list a:hover {
    color: var(--rust-700);
    background-size: 0% 0.09375rem;
  }
}

/* Desktop: form left, direct-contact column sticky on the right. Below 64rem
   everything stacks and the asides follow the form in source order. */
@media (min-width: 64rem) {
  .contact__inner {
    max-width: none;
    display: grid;
    grid-template-columns: minmax(0, 46rem) minmax(14rem, 22rem);
    gap: clamp(4rem, 8vw, 8rem);
    align-items: start; /* stretch would break position: sticky */
  }
  .contact-aside {
    position: sticky;
    top: 6.5rem; /* below the 4.5rem sticky header, plus breathing room */
  }
  .contact-aside .contact-direct:first-child {
    margin-top: 0;
    border-top: 0;
    padding-top: 0;
  }
  .contact-aside .contact-direct {
    margin-top: clamp(2rem, 3vw, 2.5rem);
    padding-top: clamp(2rem, 3vw, 2.5rem);
  }
  .contact-aside .social-list {
    flex-direction: column;
    gap: 0.75rem;
  }
}

/* Budget guide: quiet expandable under the budget options. */
.budget-guide {
  margin-top: 1.25rem;
  font-size: 1rem;
}
.budget-guide summary {
  cursor: pointer;
  width: fit-content;
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  list-style: none; /* replaces the native disclosure triangle */
}
.budget-guide summary::-webkit-details-marker { display: none; }
.budget-guide__label {
  font-weight: 500;
  line-height: 1;
  padding-bottom: 0.2em;
  background: linear-gradient(var(--rust-500), var(--rust-500)) no-repeat left bottom / 0% 0.09375rem;
  transition: background-size var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
/* The site's typographic arrow (as on the sector rows), turning when open.
   Moves with the panel: 0.45s opening, 0.3s closing (base rule = closing
   speed, [open] rule = opening speed). */
.budget-guide__arrow {
  transition: transform var(--dur-1) var(--ease);
}
.budget-guide[open] .budget-guide__arrow {
  transform: rotate(90deg);
  transition: transform var(--dur-2) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .budget-guide summary:hover .budget-guide__label {
    color: var(--rust-700);
    background-size: 100% 0.09375rem;
  }
}
@media (prefers-reduced-motion: reduce) {
  .budget-guide__arrow { transition: none; }
}
.budget-guide dl { margin-top: 1.25rem; max-width: 38em; overflow: hidden; }
.budget-guide dt { font-weight: 500; }
.budget-guide dd { color: var(--neutral-700); margin-bottom: 0.85rem; }

/* ==========================================================================
   Projects hub: archive list (older work, plain rows, no links)
   ========================================================================== */
.archive { padding-block: 0 var(--section-y); }
.archive__list { list-style: none; }
.archive__item {
  display: grid;
  grid-template-columns: minmax(10rem, 1fr) 2fr auto;
  align-items: baseline;
  gap: 0.5rem clamp(1.5rem, 4vw, 4rem);
  padding-block: clamp(1.25rem, 2.5vw, 1.75rem);
  /* Same padded-hover mechanics as the sector rows */
  padding-inline: clamp(1rem, 2vw, 1.75rem);
  margin-inline: calc(clamp(1rem, 2vw, 1.75rem) * -1);
  border-top: 0.0625rem solid var(--neutral-200);
  text-decoration: none;
  transition: background-color var(--dur-1) var(--ease);
}
.archive__list li:last-child .archive__item { border-bottom: 0.0625rem solid var(--neutral-200); }
@media (hover: hover) and (pointer: fine) {
  .archive__item:hover { background: var(--neutral-100); }
  .archive__item:hover h3 { transform: translateX(0.75rem); }
}
.archive__item h3 { transition: transform var(--dur-2) var(--ease); }
.archive__item h3 {
  font-size: clamp(1.25rem, 1.8vw, 1.5rem);
  line-height: 1.2;
}
.archive__item p { color: var(--neutral-700); }
.archive__year {
  color: var(--neutral-600);
  font-size: 1rem;
  font-variant-numeric: tabular-nums;
}
@media (max-width: 47.9rem) {
  .archive__item { grid-template-columns: 1fr auto; }
  .archive__item p { grid-column: 1 / -1; }
}

/* ==========================================================================
   Case study (/projects/[slug])
   ========================================================================== */
.crumbs {
  font-size: 1rem;
  color: var(--neutral-600);
  margin-bottom: clamp(1.5rem, 3vw, 2rem);
}
/* The trail is a real ordered list (microdata BreadcrumbList lives on it);
   separators are drawn, not markup, so screen readers hear three items. */
.crumbs ol {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.crumbs li {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.crumbs li + li::before {
  content: "\2192" / "";
  color: var(--neutral-400);
}
.crumbs a {
  color: var(--neutral-600);
  text-decoration: none;
  line-height: 1;
  padding-bottom: 0.2em;
  background: linear-gradient(var(--rust-500), var(--rust-500)) no-repeat left bottom / 0% 0.09375rem;
  transition: background-size var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .crumbs a:hover { color: var(--neutral-950); background-size: 100% 0.09375rem; }
}

/* ==========================================================================
   Explainer pages (What is Webflow, Webflow vs WordPress)
   One idea per section so each reveals as its own group; copy at a reading
   measure on the site's left axis.
   ========================================================================== */
/* Title left, copy right: the block spans the full container so the page
   doesn't stack down the left edge. */
.explainer__block {
  display: grid;
  grid-template-columns: minmax(12rem, 1fr) 2fr;
  gap: 1rem clamp(2rem, 6vw, 6rem);
  align-items: start;
}
/* The block titles are real headers, not the quiet home-page section labels */
/* Label tier, like every other heading that introduces a list. It sat a step
   larger, which made it the odd one out beside Projects and What clients say. */
.explainer__block .section-title {
  margin-bottom: 0;
  line-height: 1.15;
  text-wrap: balance;
}
.explainer__block p { color: var(--neutral-700); max-width: 46rem; }
.explainer__block p + p { margin-top: 1rem; }
.explainer__block .text-link { margin-top: 1.75rem; }
.explainer__block > :not(.section-title) { grid-column: 2; }
@media (max-width: 47.9rem) {
  .explainer__block { grid-template-columns: 1fr; }
  .explainer__block .section-title { margin-bottom: 1rem; }
  .explainer__block > :not(.section-title) { grid-column: auto; }
}

/* Statement section (sector pages): one display heading, then the copy
   stacked beneath it and offset right. A large lead line carries the message;
   the supporting paragraph sits under it at body size. */
.statement { padding-block: 0 clamp(3.5rem, 8vw, 6rem); }
.statement .section-title,
.study__section .section-title {
  font-size: clamp(2.25rem, 4.5vw, 3.5rem);
  font-weight: 400;
  line-height: 1.05;
  text-wrap: balance;
  max-width: 22ch;
  margin-bottom: clamp(2rem, 5vw, 3.5rem);
}
/* On a 12-column grid the copy runs from column 6 and ends on column 11,
   so it narrows and stops one column short of the right edge. */
.statement__copy {
  margin-left: 41.6667%;
  margin-right: 8.3333%;
}
/* One copy size throughout, matching the rest of the site */
.statement__copy p { color: var(--neutral-700); }
.statement__copy p + p { margin-top: 1.25rem; }

/* Capability list (services hub): the digital-department scope as two calm
   columns of hairline rows, the FAQ list's grammar without the interaction */
.capabilities {
  list-style: none;
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: clamp(2rem, 5vw, 4rem);
  margin-block: 2rem 1.75rem;
}
/* When the list is the last thing in a block, its bottom margin would add
   to the next section's beat, so it closes flush. */
.capabilities:last-child { margin-bottom: 0; }
.capabilities li {
  font-size: 1rem;
  padding-block: 0.85rem;
  border-top: 0.0625rem solid var(--neutral-200);
  color: var(--neutral-800);
}
@media (max-width: 40rem) {
  .capabilities { grid-template-columns: 1fr; }
}
/* Tablet: relax the left margin so the column holds a readable measure while
   still ending one column short of the right edge. */
@media (min-width: 48rem) and (max-width: 64rem) {
  .statement__copy { margin-left: 25%; }
}
@media (max-width: 47.9rem) {
  .statement__copy { margin-left: 0; margin-right: 0; }
}

/* Photo band (sector pages): the case-study full-bleed strip carrying real
   client venue photography as the page break between copy sections. One per
   page; study.js gives it the same subtle parallax. */
/* The band is a fixed-height window; the image inside is 15% taller and
   study.js slides it through the window as the page scrolls. Without JS the
   image sits centred and static. */
.photo-band {
  height: min(50vh, 34rem);
  min-height: 20rem;
  overflow: hidden;
}
.photo-band img {
  width: 100vw;
  max-width: none;
  margin-left: calc(50% - 50vw);
  height: 115%;
  position: relative;
  top: -7.5%;
  object-fit: cover;
  display: block;
}

/* Logo wall (sector pages): the client wordmarks from the home marquee as a
   titled grid. The per-logo optical heights come from the .client-logo rules;
   the scale lifts them from strip size to grid size without retuning each. */
.logo-wall {
  /* Small bottom pad: the next section brings its own section-y, and the two
     stacked read as a dead zone (228px at desktop, double the page's beat). */
  padding-block: clamp(2.5rem, 6vw, 4.5rem) clamp(1.5rem, 3.5vw, 2rem);
  color: var(--neutral-600);
}
/* Copy sections carry no top padding of their own, so when one follows the
   wall directly (groups page) it needs the cushion restored. */
/* Role angles: three readers, three answers */
.roles { padding-block: 0 var(--section-y); }
/* Statement scale, matching the other section headings on the page. Left at
   the small label size it sat smaller than the role names beneath it. */
.roles .section-title {
  font-size: clamp(2.25rem, 4.5vw, 3.5rem);
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: -0.01em;
}
.roles__list {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(2rem, 5vw, 4rem);
}
.roles__list h3 {
  font-size: clamp(1.5rem, 2.6vw, 2.25rem);
  line-height: 1.1;
}
.roles__list p { color: var(--neutral-700); margin-top: 0.85rem; }
@media (max-width: 63.9rem) {
  .roles__list { grid-template-columns: 1fr; }
}

/* Tick list: single-column reassurances */
.ticks {
  list-style: none;
  margin-top: 2rem;
}
.ticks li {
  display: flex;
  align-items: baseline;
  gap: 0.85rem;
  padding-block: 0.6rem;
  color: var(--neutral-800);
}
.ticks .tick {
  width: 0.9em;
  height: 0.9em;
  flex-shrink: 0;
  align-self: center;
  color: var(--rust-500);
}

/* Hosting marks under the server answer */
.host-logos {
  display: flex;
  align-items: center;
  gap: clamp(2rem, 5vw, 3.5rem);
  margin-top: 2rem;
  color: var(--neutral-500);
}
.host-logo { width: auto; }
.host-logo--aws { height: 2.6rem; }
.host-logo--cloudflare { height: 2.1rem; }

/* Holding blocks: labelled slots for media still to be supplied */
.media-hold {
  display: grid;
  place-items: center;
  background: var(--neutral-100);
  border: 0.0625rem solid var(--neutral-200);
  color: var(--neutral-500);
  font-size: var(--text-small);
  text-align: center;
  padding: 1rem;
}
.photo-band--hold { height: auto; min-height: 0; }
.photo-band--hold .media-hold { height: min(50vh, 34rem); min-height: 20rem; }

/* Full-height split: copy on one side, an image bleeding to the viewport edge
   on the other, the panel standing the height of the screen. The text stays
   aligned to the site's container edge; the image runs to the outer edge. */
.split {
  display: grid;
  grid-template-columns: 5fr 6fr;
  align-items: stretch;
  /* Full height, with an absolute floor so a short window can't crush it. */
  min-height: max(100vh, 42rem);
}
.split__text {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 1.25rem;
  padding-block: var(--section-y);
  padding-left: max(var(--gutter), calc((100vw - var(--container)) / 2 + var(--gutter)));
  padding-right: clamp(2rem, 5vw, 5rem);
}
.split__text .section-title {
  margin-bottom: 0;
  font-size: clamp(2.25rem, 4.5vw, 3.5rem);
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: -0.01em;
}
.split__text p { color: var(--neutral-700); max-width: 36ch; }
.split__text .capabilities { grid-template-columns: 1fr; column-gap: 0; margin-block: 0; max-width: 36ch; counter-reset: cap; }
.split__text .capabilities li {
  border-top: none;
  padding-block: 0.3rem;
  display: flex;
  gap: 0.65rem;
  counter-increment: cap;
}
.split__text .capabilities li::before {
  content: counter(cap, decimal-leading-zero) ".";
  color: var(--neutral-400);
  font-variant-numeric: tabular-nums;
}
/* Underlined at rest, underline removes on hover (the section-head style). */
.split__text .text-link {
  align-self: flex-start;
  margin-top: 0.75rem;
  background: linear-gradient(var(--rust-500), var(--rust-500)) no-repeat right bottom / 100% 0.09375rem;
}
@media (hover: hover) and (pointer: fine) {
  .split__text .text-link:hover {
    color: var(--rust-700);
    background-size: 0% 0.09375rem;
  }
}
.split__text .button { align-self: flex-start; margin-top: 0.75rem; }
.split__media { position: relative; overflow: hidden; }
.split__media > * { width: 100%; height: 100%; }
.split__media img { object-fit: cover; display: block; }
@media (max-width: 47.9rem) {
  .split { grid-template-columns: 1fr; min-height: 0; }
  .split__text { padding: var(--section-y) var(--gutter); }
  .split__media { min-height: 60vh; }
}

/* Onward reading: project-card grammar; the artwork slots await assets */
.card-art {
  aspect-ratio: 1400 / 933;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(1rem, 2.5vw, 1.75rem);
  background: var(--neutral-950);
  color: var(--neutral-50);
  transition: transform var(--dur-3) var(--ease);
}
.card-art__mark { width: auto; }
/* Optical sizing: the W leads, round marks sit slightly taller, the wide
   Wix wordmark slightly shorter */
.card-art__mark--webflow { height: 1.5rem; }
.card-art__mark[aria-label="WordPress"] { height: 2.1rem; }
.card-art__mark[aria-label="Squarespace"] { height: 1.7rem; }
.card-art__mark[aria-label="Wix"] { height: 1.2rem; }
.card-art__vs {
  font-size: var(--text-small);
  color: var(--neutral-400);
}
.card-art__ai {
  font-size: 2rem;
  line-height: 1;
  font-weight: 500;
}
@media (hover: hover) and (pointer: fine) {
  .project-card:hover .card-art { transform: scale(1.04); }
}

/* Centred, on a narrower measure than the page container */
.logo-wall .container { max-width: 60rem; }
.logo-wall .section-title { text-align: center; }
.logo-grid {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(2.5rem, 6vw, 4rem) clamp(2rem, 5vw, 4rem);
  align-items: center;
}
.logo-grid li {
  display: flex;
  justify-content: center;
}
/* Grid-specific optical sizes. Every viewBox is cropped to the artwork's ink
   box, so these heights compare like for like: wordmarks sit near 1.5rem
   (bold marks a touch under, light ones a touch over), the Airship badge
   carries more height because it is a plate, not a line of type. */
/* Partnerships (About): same wall grammar, three marks; the official Webflow
   badge keeps its own colour, the partner wordmarks stay monochrome */
/* The partners wall runs into the rust testimonials band, which brings no
   white space of its own, so this wall keeps a full bottom beat */
.logo-wall--partners { padding-bottom: var(--section-y); }
.logo-grid--partners {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: clamp(2.5rem, 6vw, 4rem) clamp(3rem, 8vw, 6rem);
}

/* Process (About): the six steps under big display numbers, three across.
   The number carries the scale; title and copy sit quietly beneath it. */
.process { padding-block: 0 var(--section-y); }
.process__list {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(3.5rem, 8vw, 6rem) clamp(2rem, 5vw, 4rem);
}
/* The statement-heading step: above the title, below the hero */
.process__num {
  display: block;
  font-size: clamp(2.25rem, 4.5vw, 3.5rem);
  line-height: 1;
  font-weight: 400;
  letter-spacing: -0.015em;
  color: var(--neutral-400);
  font-variant-numeric: tabular-nums;
}
/* Sequence numbers carry a full stop site-wide (01. 02. ...), added here so
   every numbered list matches without repeating it in each page's markup. */
.process__num::after { content: "."; }
/* Same heading step as the sector rows */
/* Titles one step below the number: the sector-row heading size */
.process__list h3 {
  font-size: clamp(1.5rem, 2.6vw, 2.25rem);
  line-height: 1.1;
  margin-top: 1rem;
}
.process__list p { color: var(--neutral-600); margin-top: 0.75rem; }
/* A closing aside under the steps, for the route the list does not cover. */
.process__note {
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
  color: var(--neutral-600);
  font-size: var(--text-secondary);
}
@media (min-width: 48rem) and (max-width: 63.9rem) {
  .process__list { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 47.9rem) {
  .process__list { grid-template-columns: 1fr; gap: 2.75rem; }
}

.logo-grid .client-logo { height: 1.5rem; }
.logo-grid .client-logo[aria-label="Airship"]       { height: 2.4rem; }
.logo-grid .client-logo[aria-label="Webflow"]       { height: 1.35rem; }
.logo-grid .client-logo[aria-label="Whalesync"]     { height: 1.3rem; }
.logo-grid .client-logo[aria-label="standrd"]       { height: 1.35rem; }
.logo-grid .client-logo[aria-label="Fanzo"]         { height: 1.25rem; }
.logo-grid .client-logo[aria-label="Zonal"]         { height: 1.35rem; }
.logo-grid .client-logo[aria-label="DesignMyNight"] { height: 1.15rem; }
.logo-grid .client-logo[aria-label="Mailchimp"]     { height: 1.65rem; }
.logo-grid .client-logo[aria-label="storekit"]      { height: 1.4rem; }
.logo-grid .client-logo[aria-label="SevenRooms"]    { height: 2.2rem; }
.logo-grid .client-logo[aria-label="Airtable"]      { height: 1.5rem; }
.logo-grid .client-logo[aria-label="HubSpot"]       { height: 1.65rem; }
.logo-grid .client-logo[aria-label="Make"]          { height: 1.5rem; }

/* Rotating testimonial: one quote per visit, chosen by the page's inline
   script; the first is server-marked active as the no-JS fallback. */
[data-rotate-quotes] .quote { display: none; }
[data-rotate-quotes] .quote.is-active { display: block; }

/* FAQs (sector pages): the contact-page disclosure pattern as a hairline list */
/* ==========================================================================
   Site map: every page as plain columns of links, label-tier headings
   ========================================================================== */
/* Stacked, one group after another, so the columns never run ragged against
   each other when one list is far longer than the rest. */
.sitemap__cols {
  display: flex;
  flex-direction: column;
}
.sitemap__col {
  padding-block: clamp(1.75rem, 4vw, 2.5rem);
  border-top: 0.0625rem solid var(--neutral-200);
}
.sitemap__col:last-child { border-bottom: 0.0625rem solid var(--neutral-200); }
.sitemap__col .section-title { margin-bottom: 1.25rem; }
.sitemap__list { list-style: none; }
.sitemap__list li + li { margin-top: 0.75rem; }
.sitemap__list a {
  display: inline-block;
  color: var(--neutral-700);
  text-decoration: none;
  font-size: var(--text-secondary);
  line-height: 1;
  padding-bottom: 0.2em;
  background: linear-gradient(var(--rust-500), var(--rust-500)) no-repeat left bottom / 0% 0.09375rem;
  transition: background-size var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .sitemap__list a:hover {
    color: var(--neutral-950);
    background-size: 100% 0.09375rem;
  }
}

.faqs { padding-block: var(--section-y); }
.faq { border-top: 0.0625rem solid var(--neutral-200); }
.faqs__list .faq:last-child { border-bottom: 0.0625rem solid var(--neutral-200); }
.faq summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  padding-block: 1.65rem;
  font-weight: 500;
  transition: color var(--dur-1) var(--ease);
}
.faq summary::-webkit-details-marker { display: none; }
/* Arrow moves with the panel: 0.45s opening, 0.3s closing, one movement.
   faq.js drives both with GSAP when available; these rules are the no-JS
   fallback (base rule = closing speed, [open] rule = opening speed). */
.faq__arrow {
  color: var(--neutral-500);
  transition: transform var(--dur-1) var(--ease);
}
.faq[open] .faq__arrow {
  transform: rotate(90deg);
  transition: transform var(--dur-2) var(--ease);
}
.faq__panel { overflow: hidden; }
@media (hover: hover) and (pointer: fine) {
  .faq summary:hover { color: var(--rust-700); }
}
.faq p { font-size: 1.125rem; color: var(--neutral-700); padding: 0.25rem 0 2rem; max-width: 60ch; }
@media (prefers-reduced-motion: reduce) {
  .faq__arrow { transition: none; }
}
@media (max-width: 47.9rem) {
  .logo-grid { grid-template-columns: repeat(2, 1fr); }
}

.study { padding-block: 0 var(--section-y); }
.study__about {
  max-width: 46rem;
  margin-bottom: clamp(2.5rem, 6vw, 4rem);
}
/* The intro leads without a heading; the site's under-H1 lead size */
.study__about p {
  font-size: clamp(1.25rem, 1.8vw, 1.5rem);
  line-height: 1.5;
  color: var(--neutral-700);
  max-width: 30em;
}
.study__banner {
  border: 0.0625rem solid var(--neutral-200);
  overflow: hidden;
}
.study__banner img,
.study__banner video { width: 100%; height: auto; display: block; }

/* Case study rhythm: the statement pattern, a full-width heading on the left
   with the reading matter in one narrow column offset right, while the media
   below stays the full width of the container. */
.study__section { margin-top: clamp(3.5rem, 8vw, 6rem); }
.study__copy {
  margin-left: 41.6667%;
  margin-right: 8.3333%;
}
/* The details block carries no heading, so it belongs to the copy above it
   rather than starting a beat of its own. */
.study__section--tight { margin-top: clamp(2rem, 4vw, 2.75rem); }
.study__section p { color: var(--neutral-700); }
.study__section p + p { margin-top: 1.25rem; }

/* Details is a stack of hairline rows, label left and value right, sitting in
   the same column as the copy rather than in a rail of its own. */
.study__details { margin-top: 0.5rem; }
.study__detail {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: clamp(1.5rem, 4vw, 3rem);
  padding-block: 1rem;
  border-top: 0.0625rem solid var(--neutral-200);
}
.study__detail:last-child { border-bottom: 0.0625rem solid var(--neutral-200); }
/* A step below the copy: this is reference information, not the argument. */
.study__detail dt,
.study__detail dd { font-size: var(--text-secondary); }
.study__detail dt { color: var(--neutral-600); }
.study__detail dd { text-align: right; color: var(--neutral-900); }

@media (max-width: 63.9rem) {
  .study__copy { margin-left: 0; margin-right: 0; }
}

.study__results {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2rem clamp(2rem, 4vw, 3rem);
  margin-top: clamp(2rem, 5vw, 3rem);
}

/* Outcome is a gallery beat: it slots between media items (statsAfter) and
   spans the container like the homepage stats row */
.study__outcome {
  margin-top: clamp(3.5rem, 8vw, 6rem);
}
.study__outcome h2 {
  font-size: clamp(1.75rem, 3vw, 2.5rem);
  line-height: 1.1;
  margin-bottom: 1rem;
}
.study__outcome p { color: var(--neutral-700); max-width: 65ch; }
.study__outcome .study__results {
  margin-top: 2.75rem;
  gap: 2rem clamp(2rem, 5vw, 4rem); /* the homepage facts rhythm */
}
.study__results .fact {
  border-bottom: 0.0625rem solid var(--neutral-200);
  padding-bottom: 1.25rem;
}
@media (max-width: 40rem) {
  .study__results { grid-template-columns: 1fr; }
}

/* Gallery: a paced sequence, not a uniform stack. Items span the width by
   default; halves pair up side by side; panel items sit on the client's own
   brand colour (their work, their backdrop); laptop items get a device frame. */
.study__gallery {
  margin-top: clamp(3.5rem, 8vw, 6rem);
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(4rem, 10vw, 8rem) clamp(2.5rem, 6vw, 5rem);
}
.study__item { grid-column: span 2; }
.study__item--half { grid-column: span 1; }
.study__item img,
.study__item video {
  width: 100%;
  height: auto;
  display: block;
  border: 0.0625rem solid var(--neutral-200);
}
/* Unframed full-width videos are capped at watching size and centred. No
   forced ratio, so the video shows at its own shape rather than cropped. */
.study__item:not(.study__item--half) > video {
  max-width: 52rem;
  margin-inline: auto;
}
/* The screen takes the media's own shape rather than forcing 16:10, so a
   16:9 recording is never cropped top and bottom to fit the frame. */
.laptop__screen video,
.laptop__screen img {
  width: 100%;
  height: auto;
  display: block;
}

/* Full-span images become edge-to-edge banner strips within the scroll. The
   item itself breaks the container and is the fixed-height window; the taller
   image slides through it (study.js), matching the sector-page photo bands.
   The bleed has to sit on the item, not the image, or the item's own overflow
   clips it back to the container. */
.study__item--strip {
  width: 100vw;
  margin-left: calc(50% - 50vw);
  height: min(50vh, 34rem);
  min-height: 20rem;
  overflow: hidden;
}
.study__item--strip > img {
  width: 100%;
  max-width: none;
  height: 115%;
  position: relative;
  top: -7.5%;
  object-fit: cover;
  border: 0;
}
.study__item--panel {
  background: var(--panel, var(--neutral-100));
  width: 100vw;
  margin-left: calc(50% - 50vw);
  padding: clamp(2rem, 6vw, 5rem) var(--gutter);
  display: flex;
  align-items: center;
  justify-content: center;
}
.study__item--panel img,
.study__item--panel video {
  border: 0;
  max-width: 52rem;
}
.study__item--half.study__item--panel img,
.study__item--half.study__item--panel video { max-width: 100%; }

/* Phone trio: portrait screenshots in slim device frames */
.study__item--phones {
  display: flex;
  justify-content: center;
  align-items: flex-start;
  gap: clamp(1rem, 3vw, 2.5rem);
  max-width: 52rem;
}
.study__item--phones .phone { flex: 0 1 18rem; }
.study__item--phones { margin-inline: auto; }
.phone {
  border: 0.375rem solid var(--neutral-900);
  border-radius: 1.5rem;
  overflow: hidden;
}
/* Screenshots are a uniform 640x1138; reserving the ratio stops the trio
   collapsing before lazy images arrive. Videos likewise reserve their
   recording ratios (16:9 plain, 16:10 in the laptop bezel). */
.phone img { border: 0; width: 100%; display: block; aspect-ratio: 640 / 1138; object-fit: cover; }
@media (max-width: 40rem) {
  .study__item--phones { flex-wrap: wrap; }
  .study__item--phones .phone { flex-basis: 14rem; }
}

/* Copy block: a heading and body dropped between media. It sits on the same
   centred 52rem spine as the videos and frames, so text and media read as
   one joined column rather than floating on different axes. */
/* The one block that stays on the left axis. It is reading matter, so it
   lines up with the Overview copy rather than with the centred media. */
.study__item--copy {
  width: 100%;
  max-width: 52rem;
}
/* Split variant: the site's statement pattern, heading left at full width
   with the copy beneath it in the offset column. Same shape as the Overview,
   so a copy beat mid-gallery reads as part of the page rather than a caption. */
.study__item--copy-split h2 {
  font-size: clamp(2.25rem, 4.5vw, 3.5rem);
  font-weight: 400;
  line-height: 1.05;
  text-wrap: balance;
  max-width: 22ch;
  margin-bottom: clamp(2rem, 5vw, 3.5rem);
}
.study__item--copy-split > div {
  margin-left: 41.6667%;
  margin-right: 8.3333%;
}
.study__item--copy-split p { color: var(--neutral-700); }
@media (max-width: 63.9rem) {
  .study__item--copy-split > div { margin-left: 0; margin-right: 0; }
}
.study__item--copy h2 {
  font-size: clamp(1.75rem, 3vw, 2.5rem);
  line-height: 1.1;
  margin-bottom: 1rem;
}
.study__item--copy p { color: var(--neutral-700); max-width: 65ch; }

/* Lab-only labels on /media-lab */
.lab-note h2 { font-size: 1.25rem; font-weight: 500; }
.lab-note p { color: var(--neutral-600); font-size: 1rem; margin-top: 0.25rem; }
.lab-note { margin-bottom: calc(clamp(2.5rem, 6vw, 5rem) * -0.6); }

/* Minimal laptop frame: dark bezel screen on a slim base, centred */
.laptop { width: min(100%, 52rem); margin-inline: auto; }
.laptop__screen {
  border: 0.5rem solid var(--neutral-900);
  border-radius: 0.75rem 0.75rem 0 0;
  overflow: hidden;
}
.laptop__screen img,
.laptop__screen video { border: 0; }
.laptop__base {
  height: 0.8rem;
  background: var(--neutral-300);
  border-radius: 0 0 0.9rem 0.9rem;
  width: 112%;
  margin-left: -6%;
}

@media (max-width: 47.9rem) {
  .study__gallery { grid-template-columns: 1fr; }
  .study__item, .study__item--half { grid-column: auto; }
}

/* The address sits in the details list as a plain value, so it carries no
   underline at rest. The underline grows on hover like every internal link. */
.study__visit a {
  display: inline-block;
  color: var(--neutral-900);
  text-decoration: none;
  line-height: 1;
  padding-bottom: 0.2em;
  background: linear-gradient(var(--rust-500), var(--rust-500)) no-repeat left bottom / 0% 0.09375rem;
  transition: background-size var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .study__visit a:hover {
    color: var(--rust-700);
    background-size: 100% 0.09375rem;
  }
}

/* Testimonial attribution with headshot */
.quote__attr {
  display: flex;
  align-items: center;
  gap: 0.85rem;
}
.quote__avatar {
  width: 3rem;
  height: 3rem;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}
.quote__who span {
  display: block;
  font-weight: 400;
  font-size: var(--text-small);
  color: var(--neutral-600);
  margin-top: 0.15rem;
}

/* Testimonials page: group label left, quotes right at reading measure with a
   hairline between them. Same split grammar as the statement sections; the
   rust band stays a reserved moment elsewhere. */
.testimonials--page {
  background: transparent;
  padding-block: 0 var(--section-y);
}
/* Same geometry as the statement sections: content starts where their copy
   starts and finishes on column 11 of the 12-column grid. */
.testimonial-group {
  display: grid;
  grid-template-columns: 41.6667% 50% 8.3333%;
  align-items: start;
}
.testimonial-group .section-title {
  position: sticky;
  top: clamp(4.5rem, 10vh, 6.5rem);
  margin-bottom: 0;
}
.quote--page + .quote--page {
  border-top: 0.0625rem solid var(--neutral-200);
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
  padding-top: clamp(2.5rem, 5vw, 3.5rem);
}
.quote--page .quote__attr {
  margin-top: 1.75rem;
  flex-wrap: wrap;
}
.quote__link {
  margin-top: 0;
  margin-left: auto;
  /* Underlined at rest, underline removes on hover (the section-head style). */
  background: linear-gradient(var(--rust-500), var(--rust-500)) no-repeat right bottom / 100% 0.09375rem;
}
@media (hover: hover) and (pointer: fine) {
  .quote__link:hover {
    color: var(--rust-700);
    background-size: 0% 0.09375rem;
  }
}
@media (min-width: 48rem) and (max-width: 63.9rem) {
  .testimonial-group { grid-template-columns: 25% 66.6667% 8.3333%; }
}
@media (max-width: 47.9rem) {
  .testimonial-group { grid-template-columns: 1fr; }
  .testimonial-group .section-title { position: static; }
}

.testimonials--single { padding-block: clamp(3.5rem, 8vw, 5.5rem); }
.testimonials--single .testimonials__grid { grid-template-columns: 1fr; }
.testimonials--single .quote { max-width: 46rem; }

/* Legal pages: one calm reading column */
.legal { padding-block: 0 var(--section-y); }
.legal__body { max-width: 46rem; }
.legal__updated { font-size: var(--text-small); color: var(--neutral-500); }
.legal h2 {
  font-size: clamp(1.25rem, 1.8vw, 1.5rem);
  font-weight: 500;
  margin: clamp(2rem, 4vw, 2.75rem) 0 0.75rem;
}
.legal p { color: var(--neutral-700); }
.legal p + p { margin-top: 0.85rem; }
.legal ul {
  padding-left: 1.25rem;
  color: var(--neutral-700);
  margin-top: 0.85rem;
}
.legal li + li { margin-top: 0.4rem; }
.legal ul + p { margin-top: 0.85rem; }
.legal strong { font-weight: 500; color: var(--neutral-950); }

/* Short pages (404, legal): the footer holds the bottom of the viewport */
#smooth-content {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}
#smooth-content > main { flex: 1 0 auto; }

/* ==========================================================================
   Standardised vertical rhythm (site-wide)

   ONE rule: every section carries the same padding top and bottom. No margins
   anywhere, so a gap is never the sum of a margin and a padding, and any two
   sections are separated by exactly two beats.
   ========================================================================== */
#smooth-content main > section { padding-block: var(--section-y); display: flow-root; }

/* Exceptions, each for a stated reason. */
/* The client marquee is a thin moving strip between sections, not a section. */
#smooth-content main > .clients { padding-block: clamp(1.5rem, 3.5vw, 2rem); }
/* The photo band is a full-bleed image; padding would inset it. */
#smooth-content main > .photo-band { padding-block: 0; }
/* The full-height split is a panel, its grid must not be pushed off centre. */
#smooth-content main > .split { padding-block: 0; display: grid; }
/* A facts row following a statement is a footnote to it, not a new argument,
   so it sits tight underneath instead of starting its own beat. */
#smooth-content main > .statement + .facts { padding-top: 0; }

/* Notices: the system colours in use. 50 carries the surface, 500 the
   border, 700 the text. Functional feedback only, never decorative. */
.notice {
  border: 0.0625rem solid;
  padding: 1rem 1.25rem;
  font-size: 1.125rem;
}
.notice strong { display: block; font-weight: 500; }
.notice--success { background: var(--success-50); border-color: var(--success-500); color: var(--success-700); }
.notice--warning { background: var(--warning-50); border-color: var(--warning-500); color: var(--warning-700); }
.notice--error { background: var(--error-50); border-color: var(--error-500); color: var(--error-700); }

/* ==========================================================================
   Style guide (/style-guide, internal review page)
   ========================================================================== */
.sg__label {
  font-size: var(--text-small);
  font-weight: 500;
  color: var(--neutral-500);
  margin: clamp(2.5rem, 5vw, 3.5rem) 0 1rem;
}
.sg__note { color: var(--neutral-700); max-width: 46rem; }
.sg__swatches {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
  gap: 1rem;
}
.sg__chip { display: block; height: 4rem; border: 0.0625rem solid var(--neutral-200); }
.sg__meta { display: block; font-size: var(--text-small); color: var(--neutral-600); line-height: 1.45; margin-top: 0.4rem; }
.sg__spec { border-top: 0.0625rem solid var(--neutral-200); padding-block: 1.25rem 1.5rem; }
.sg__spec .sg__meta { margin: 0 0 0.75rem; }
.sg__type { margin: 0; }
.sg__display { font-size: var(--text-display); line-height: 1.0; letter-spacing: -0.015em; font-weight: 400; }
.sg__statement { font-size: clamp(2.25rem, 4.5vw, 3.5rem); line-height: 1.05; font-weight: 400; }
.sg__study-h { font-size: clamp(1.75rem, 3vw, 2.5rem); line-height: 1.1; font-weight: 400; }
.sg__row-h { font-size: clamp(1.5rem, 2.6vw, 2.25rem); line-height: 1.1; font-weight: 400; }
.sg__lead { font-size: clamp(1.25rem, 1.8vw, 1.5rem); line-height: 1.5; color: var(--neutral-700); max-width: 30em; }
.sg__body-s { font-size: 1.125rem; color: var(--neutral-700); }
.sg__body-xs { font-size: 1rem; color: var(--neutral-600); }
.sg__small { font-size: var(--text-small); color: var(--neutral-500); }
.sg__no-margin { margin: 0; }
.sg .section-title.sg__no-margin { margin-bottom: 0; }
.sg .text-link { margin-top: 0; }
.sg__row { display: flex; flex-wrap: wrap; gap: 1.5rem 2.5rem; align-items: center; padding-block: 0.5rem; }
.sg__row--dark { background: var(--neutral-900); color: var(--neutral-50); padding: 1.5rem; }
.sg__inline { margin: 0; }
.sg__block { margin-bottom: 0; }
.sg__form { max-width: 34rem; }
.sg__notices { display: grid; gap: 1rem; }
.sg__form input, .sg__form textarea { margin-bottom: 0.85rem; }

/* ==========================================================================
   Quiet delight. The script logo is the site's one personality slot, so it
   writes itself in once per page. Choices give a small press when picked.
   The form's success tick draws itself. All absent under reduced motion.
   ========================================================================== */
@media (prefers-reduced-motion: no-preference) {
  .choice input:checked + span { animation: choice-press 0.25s var(--ease); }
  @keyframes choice-press {
    50% { transform: scale(0.97); }
  }
  .contact-form__done:not([hidden]) .done-tick path {
    stroke-dasharray: 48;
    stroke-dashoffset: 48;
    animation: tick-draw 0.45s var(--ease) 0.1s forwards;
  }
  @keyframes tick-draw {
    to { stroke-dashoffset: 0; }
  }
}
.done-tick {
  width: 2.5rem;
  height: 2.5rem;
  color: var(--success-500);
  margin-bottom: 1.25rem;
}
