/* ------------------------------------------------------------------
   swstrategies.com -- Direction A
   Deep navy with a single cyan accent. Built against
   plans/SWStrategies - Direction A Styling Spec.md (the spec) and
   plans/Direction A - implementation plan and estimate.md.
   Where the spec and the designer's notes disagree, the spec wins.

   Steps 1-3: tokens, Inter, the nine type roles, the spacing scale, the
   container and section grounds; the chrome; and the components. The
   legacy token bridge is gone -- every rule below names a Direction A
   token. Page grounds (step 4) and the diagram drawings (step 5) are
   what remain.

   Three rules this stylesheet is not allowed to break:
     - no raw hex outside the token block;
     - cyan is never text on a light ground -- accent text on light is
       always --teal;
     - orange has exactly one job, the problem / before state.
   ------------------------------------------------------------------ */

/* ---------- Inter, self-hosted ----------
   Variable, Latin subset only, 48KB. Every character the site's copy
   uses falls inside this range -- the one non-ASCII character in all
   fifteen pages is the em dash, U+2014 -- so no other slice is ever
   fetched and the whole font payload is that single file. Weights 400,
   500 and 600 come off the one axis.

   Helvetica Neue was the designer's choice and is deliberately not
   used: it renders as Arial on Windows, which is where the audience
   is, and Arial has no medium weight. */

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(/assets/inter-latin.3100e775.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;
}

:root {
  /* dark grounds */
  --ink:          #0f1226;  /* headings on light; dark sections; header */
  --ink-2:        #1b1f3a;  /* raised surfaces on dark: cards, footer */
  --ink-line:     #262b4a;  /* hairlines on dark */
  --ink-3:        #3a4068;  /* secondary borders on dark; diagram strokes */

  /* text */
  --body:         #454a66;  /* body on light, 8.7:1 on white */
  --muted-dark:   #a7adc8;  /* body on dark, 8.3:1 on ink */
  --soft-dark:    #c9cde0;  /* brighter secondary on dark; card borders on light */

  /* light grounds */
  --paper:        #ffffff;  /* the default section ground */
  --line:         #dde0ea;  /* hairlines on light */
  --paper-2:      #f4f5f8;  /* alternate light section */

  /* accent */
  --cyan:         #2ec4d9;  /* buttons, rules, eyebrows -- ON DARK ONLY */
  --cyan-soft:    #7fe0ec;  /* cyan text on dark needing more pop, 12:1 */
  --cyan-active:  #22a9bb;  /* primary button :active */
  --teal:         #0e6f7e;  /* ANY accent-coloured text on light, 5.8:1 */
  --cyan-tint:    #eaf9fb;  /* light accent background */
  --cyan-tint-2:  #e3f6f9;  /* checked/selected fill, sent panel */

  /* problem / before -- semantic, never decoration */
  --problem:      #ff8a1f;  /* on dark, and as fill/marker on light */
  --problem-text: #9a4700;  /* problem text on light */
  --problem-tint: #ffe8d1;  /* problem background on light */

  /* error, forms only */
  --error:        #b3261e;
  --field-line:   #8a8fa8;  /* form field borders, 3.3:1 on white */

  --ink-active:   #000000;  /* primary-navy and outline :active */

  /* spacing scale -- the design's 10/14/18/28 round onto these */
  --s4:    4px;
  --s8:    8px;
  --s12:  12px;
  --s16:  16px;
  --s24:  24px;
  --s32:  32px;
  --s48:  48px;
  --s64:  64px;
  --s112: 112px;

  --wrap: 1280px;
  --gutter: clamp(20px, 5vw, 32px);
  --section-y: clamp(64px, 10vw, 112px);

  --font: Inter, "Helvetica Neue", Helvetica, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* ---------- reset and base ---------- */

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

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

body {
  font-family: var(--font);
  background: var(--paper);
  color: var(--body);
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img { max-width: 100%; height: auto; display: block; }

/* Links, per the spec: ink text carrying a 2px cyan underline -- the
   underline is decoration, so cyan's 2.1:1 on white is fine there and
   nowhere else -- and the text itself goes teal on hover. Step 2 adds
   the focus ring and the on-dark states. */
a {
  color: var(--ink);
  text-decoration-line: underline;
  text-decoration-color: var(--cyan);
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
}
a:hover { color: var(--teal); }

.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 var(--gutter); }

/* ---------- type: the nine roles ----------
   Consolidated from the design file's ~20 clamp values. Weights 400,
   500 and 600 only -- no 700, no italics for emphasis, and no all-caps
   outside the mono eyebrow.

   These are the roles. Steps 3 and 4 point the component classes at
   them; .h1-hero in particular is home-only and gets applied when the
   home page is built. */

h1, h2, h3, h4 { font-weight: 600; text-wrap: balance; color: var(--ink); }
p { text-wrap: pretty; }

/* 1. Hero H1 -- home page only */
.h1-hero { font-size: clamp(40px, 6.6vw, 104px); line-height: 1.02; letter-spacing: -0.03em; }

/* 2. Page H1 -- merges the design's 64/72/88/96px variants */
h1 { font-size: clamp(36px, 5vw, 72px); line-height: 1.05; letter-spacing: -0.03em; }

/* 3. H2 -- capped at 48px. The design had 60-64px, which competed with
      the H1 and made long pages feel shouty. */
h2 { font-size: clamp(30px, 3.6vw, 48px); line-height: 1.1; letter-spacing: -0.025em; }

/* 4. H3 -- merges the design's 24-32px variants */
h3 { font-size: clamp(22px, 2.2vw, 28px); line-height: 1.25; letter-spacing: -0.02em; }
h4 { font-size: 17px; line-height: 1.4; letter-spacing: 0; }

/* 5. Big number / outcome line */
.bignum { font-size: clamp(28px, 3vw, 40px); font-weight: 600; line-height: 1.1;
  letter-spacing: -0.025em; color: var(--ink); }

/* 6. Lead paragraph */
.lede { font-size: clamp(17px, 1.6vw, 19px); font-weight: 400; line-height: 1.6;
  letter-spacing: -0.01em; color: var(--body); max-width: 60ch; }

/* 7. Body -- set on <body> above. Never below 16px. */
p { max-width: 68ch; }

/* 8. Small / caption */
.small { font-size: 14px; font-weight: 400; line-height: 1.5; letter-spacing: 0; }

/* 9. Eyebrow / label, mono. The boxed blue badge is gone: Direction A
      has a mono eyebrow, not an outlined chip. Step 3 folds .context,
      .tag, .kicker and .role onto this -- without renaming .context,
      which is a mistake that has already shipped once. */
.eyebrow {
  display: block;
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--teal);
  margin-bottom: var(--s16);
}

/* Accent-coloured text on light is teal; on dark it is cyan-soft. The
   old blue highlight box-shadow behind .mark is gone. */
.mark { color: var(--teal); font-weight: 500; }

/* ---------- on dark ----------
   Keyed to the SURFACE, not to the section. Two things broke when this was
   written as `section.dark h2`:

     - .cta-band is a dark panel that is not a section, so its headings kept
       the light treatment and rendered --ink on --ink: invisible, on twelve
       of the fifteen pages;
     - a white .card sitting inside a dark section inherited the dark
       treatment and rendered --paper on white: invisible, the other way up.

   So: dark surfaces take the dark treatment, and any light surface nested
   inside one takes the light treatment back. Step 3 moves dark-section cards
   onto --ink-2, which retires the second half of this block. */

.on-dark, section.dark { color: var(--muted-dark); }
:is(.on-dark, section.dark) :is(h1, h2, h3, h4) { color: var(--paper); }
:is(.on-dark, section.dark) .bignum { color: var(--paper); }
:is(.on-dark, section.dark) :is(p, .lede) { color: var(--muted-dark); }
:is(.on-dark, section.dark) .eyebrow { color: var(--cyan); }
:is(.on-dark, section.dark) .mark { color: var(--cyan-soft); }
:is(.on-dark, section.dark) a:not(.btn) { color: var(--cyan-soft); text-decoration-color: var(--cyan); }
:is(.on-dark, section.dark) a:not(.btn):hover { color: var(--paper); }

/* The light surfaces that can appear inside a dark one. */
:is(.on-dark, section.dark) :is(.diagram, .shot) { color: var(--body); }
:is(.on-dark, section.dark) :is(.diagram, .shot) :is(h1, h2, h3, h4) { color: var(--ink); }
:is(.on-dark, section.dark) :is(.diagram, .shot) .bignum { color: var(--ink); }
:is(.on-dark, section.dark) :is(.diagram, .shot) :is(p, .lede) { color: var(--body); }
:is(.on-dark, section.dark) :is(.diagram, .shot) .eyebrow { color: var(--teal); }
:is(.on-dark, section.dark) :is(.diagram, .shot) .mark { color: var(--teal); }
:is(.on-dark, section.dark) :is(.diagram, .shot) a:not(.btn) { color: var(--ink); text-decoration-color: var(--cyan); }
:is(.on-dark, section.dark) :is(.diagram, .shot) a:not(.btn):hover { color: var(--teal); }

/* ...and the outline button on that light surface, which otherwise takes the
   dark section's white-on-white treatment and disappears entirely. */
:is(.on-dark, section.dark) :is(.diagram, .shot) .btn.ghost {
  color: var(--ink); border-color: var(--ink); background: transparent;
}
:is(.on-dark, section.dark) :is(.diagram, .shot) .btn.ghost:hover {
  color: var(--paper); border-color: var(--ink); background: var(--ink);
}

/* ---------- focus ----------
   Two-tone, so one ring works on navy, on cyan and on white alike: a 2px
   white ring hard against the element, then a 2px ink outline in the 2px
   gap outside it. Whichever ground the control sits on, one of the two
   rings contrasts with it. Keyboard only -- a mouse click shows nothing. */

:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
  box-shadow: 0 0 0 2px var(--paper);
  border-radius: 2px;
}
section.dark :focus-visible, .site-header :focus-visible, .site-footer :focus-visible {
  outline-color: var(--paper);
  box-shadow: 0 0 0 2px var(--ink);
}

.skip { position: absolute; left: -9999px; }
.skip:focus {
  left: var(--s16); top: var(--s16); z-index: 200;
  background: var(--paper); color: var(--ink);
  border-radius: 6px; padding: var(--s12) var(--s24);
  text-decoration: none; font-weight: 600;
}

/* ---------- buttons ----------
   Three variants, three states each. The old uppercase letter-spaced slab
   with its translate-and-hard-shadow hover is gone; so is white text on a
   cyan fill, which the spec forbids outright.

   44px minimum height everywhere, because that is the tap target, and one
   primary button per viewport. */

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px;
  padding: 14px 22px;
  border: 2px solid transparent;
  border-radius: 6px;
  font-family: inherit;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  text-decoration: none;
  cursor: pointer;
  background: var(--cyan);
  color: var(--ink);
  transition: background-color .12s ease, border-color .12s ease, color .12s ease;
}
.btn:hover  { background: var(--cyan-soft); color: var(--ink); }
.btn:active { background: var(--cyan-active); color: var(--ink); }

/* Primary on a light ground, where cyan would shout. */
.btn.navy        { background: var(--ink); color: var(--paper); }
.btn.navy:hover  { background: var(--ink-line); color: var(--paper); }
.btn.navy:active { background: var(--ink-active); color: var(--paper); }

/* Secondary / outline. On light it fills ink on hover; on dark only the
   border lights up, so the fill never competes with the primary. */
.btn.ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--ink);
}
.btn.ghost:hover  { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.btn.ghost:active { background: var(--ink-active); color: var(--paper); border-color: var(--ink-active); }

.on-dark .btn.ghost, section.dark .btn.ghost, .btn.on-dark.ghost {
  background: transparent;
  color: var(--paper);
  border-color: var(--soft-dark);
}
.on-dark .btn.ghost:hover, section.dark .btn.ghost:hover, .btn.on-dark.ghost:hover {
  background: transparent;
  color: var(--paper);
  border-color: var(--cyan);
}

.btn[disabled], .btn[aria-disabled="true"] {
  opacity: .4;
  cursor: not-allowed;
  pointer-events: none;
}

.btn-row { display: flex; flex-wrap: wrap; gap: var(--s16); margin-top: var(--s32); }

/* ---------- header ----------
   Navy, 64px on desktop and 56px on mobile -- it was 84px and light. The
   logo is the white lockup on both: the dark one carries 48 rows of faint
   erased-content residue above the wordmark that is invisible on near-white
   and plainly visible on navy. */

.site-header {
  position: sticky; top: 0; z-index: 100;
  background: var(--ink);
  border-bottom: 1px solid var(--ink-line);
}
.site-header .wrap {
  display: flex; align-items: center; justify-content: space-between;
  height: 64px; gap: var(--s24);
}
.brand { display: flex; align-items: center; text-decoration: none; }
.brand img { height: 32px; width: auto; }

.nav { display: flex; align-items: center; gap: var(--s32); }
.nav > a:not(.btn) {
  color: var(--soft-dark);
  text-decoration: none;
  font-size: 16px;
  font-weight: 500;
  padding: 4px 0;
  border-bottom: 2px solid transparent;
}
.nav > a:not(.btn):hover { color: var(--paper); }
.nav > a:not(.btn)[aria-current="page"] { color: var(--paper); border-bottom-color: var(--cyan); }
.nav .btn { min-height: 40px; padding: 10px 18px; font-size: 15px; }
.nav .btn:hover { border-bottom-color: transparent; }

/* The hamburger: a 44x44 icon button, no boxed "Menu" label. Its accessible
   name lives in aria-label, and the three bars are decorative. */
.nav-toggle {
  display: none;
  width: 44px; height: 44px;
  align-items: center; justify-content: center;
  background: none; border: 0; border-radius: 6px;
  cursor: pointer; color: var(--paper);
}
.nav-toggle .bars, .nav-toggle .bars::before, .nav-toggle .bars::after {
  display: block; width: 22px; height: 2px;
  background: currentColor; border-radius: 1px;
}
.nav-toggle .bars { position: relative; }
.nav-toggle .bars::before, .nav-toggle .bars::after { content: ""; position: absolute; left: 0; }
.nav-toggle .bars::before { top: -7px; }
.nav-toggle .bars::after  { top:  7px; }
.nav-toggle[aria-expanded="true"] .bars { background: transparent; }
.nav-toggle[aria-expanded="true"] .bars::before { top: 0; transform: rotate(45deg); }
.nav-toggle[aria-expanded="true"] .bars::after  { top: 0; transform: rotate(-45deg); }

@media (max-width: 899.98px) {
  .site-header .wrap { height: 56px; }
  .brand img { height: 28px; }
  .nav-toggle { display: inline-flex; }

  /* A full-width dark panel under the header, not a dropdown. Fixed rather
     than absolute so a long menu scrolls itself instead of the page behind
     it, which is also what makes the focus trap hold. */
  .nav {
    position: fixed; top: 56px; left: 0; right: 0; bottom: 0;
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--ink);
    padding: var(--s8) var(--gutter) var(--s32);
    overflow-y: auto;
    border-top: 1px solid var(--ink-line);
  }
  .nav[hidden] { display: none; }
  .nav > a:not(.btn) {
    font-size: 20px;
    min-height: 48px;
    display: flex; align-items: center;
    padding: 0;
    border-bottom: 1px solid var(--ink-line);
  }
  .nav > a:not(.btn)[aria-current="page"] { border-bottom-color: var(--ink-line); color: var(--cyan); }
  .nav .btn { margin-top: var(--s24); width: 100%; min-height: 48px; font-size: 16px; }
}

/* The page behind the open panel must not scroll with it. */
body.nav-open { overflow: hidden; }

/* ---------- sections ----------
   Three grounds only: white, --paper-2 and --ink. They alternate, and
   two dark sections never sit next to each other -- navy opens and
   closes a page rather than trading places with light all the way down.
   Which page gets which is the map in section 6 of the plan, applied
   when the pages are built in step 4. */

section { padding: var(--section-y) 0; background: var(--paper); }
section.tint { background: var(--paper-2); }
section.dark { background: var(--ink); }

/* Hairlines: 1px, and the only thing that changes is which ground they
   are drawn on. */
.rule { border: 0; border-top: 1px solid var(--line); margin: var(--s48) 0; }
section.dark .rule, .on-dark .rule { border-top-color: var(--ink-line); }

.section-head { max-width: 68ch; margin-bottom: var(--s48); }
.section-head p { margin-top: var(--s16); }
.section-head .lede { margin-top: var(--s16); }

/* ---------- hero ----------
   Every page's hero goes navy in step 4; the padding is the only part
   that belongs to the foundation. Slightly taller than a plain section
   because nothing sits above it but the header. */

.hero { padding: clamp(72px, 11vw, 128px) 0 clamp(56px, 8vw, 96px); }
.hero h1 { max-width: 20ch; }
.hero .lede { margin-top: var(--s32); }

/* ---------- grids ----------
   Intrinsic: `auto-fit` with a `min()` floor collapses to one column without a
   media query, which is what stops cards sitting two-up on a phone. Three
   widths only -- 240 for stat tiles, 320 for cards, 420 for two-up blocks. */

.grid { display: grid; gap: var(--s24); }
.grid.tiles { grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr)); }
.grid.three { grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); }
.grid.two {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr));
  row-gap: var(--s32); column-gap: var(--s48);
}

/* The contact form wants more room than the details beside it. This lives in a
   stylesheet rather than an inline style because an inline grid-template-columns
   beats every media query -- which is exactly how two card grids stayed side by
   side on phones, twice. */
.grid.two.form-split { grid-template-columns: 1.4fr 1fr; column-gap: var(--s48); }
@media (max-width: 899.98px) { .grid.two.form-split { grid-template-columns: 1fr; } }

/* ---------- the one eyebrow ----------
   .context, .tag, .kicker and .role were four names for the same uppercase
   label in four places. They fold onto the single mono eyebrow here.

   .context is NOT renamed to .eyebrow. That exact rename shipped once and put
   every proof-card context line inside a blue outline. */

.eyebrow,
.proof .context,
.card .tag,
.case-head .kicker,
.person .role {
  display: block;
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--teal);
  border: 0;
  padding: 0;
}
.eyebrow { margin-bottom: var(--s16); }
.proof .context { margin: 0 0 var(--s12); }
.card .tag { margin-bottom: var(--s12); }
.person .role { margin-bottom: var(--s8); }

/* The folded eyebrows need the dark treatment too. Teal is the accent on light;
   on a dark card it comes out at 2.76:1, so on dark they all go cyan. */
:is(.on-dark, section.dark) :is(.eyebrow, .proof .context, .card .tag, .case-head .kicker, .person .role) {
  color: var(--cyan);
}

/* ---------- cards ----------
   Flat. No coloured top bars, no drop shadows, and no translate-on-hover: a
   linked card says so by changing its border, and the whole card is the target. */

.card {
  background: var(--paper);
  border: 1px solid var(--soft-dark);
  border-radius: 8px;
  padding: var(--s32) 28px;
  display: flex; flex-direction: column;
}
.card h3 { margin-bottom: var(--s12); }
.card ul { margin: var(--s16) 0 0 var(--s16); }
.card li { margin-bottom: var(--s8); }
.card .more {
  margin-top: auto; padding-top: var(--s24);
  font-weight: 500; font-size: 16px;
  color: var(--ink);
  text-decoration-line: underline;
  text-decoration-color: var(--cyan);
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
}

a.card { text-decoration: none; color: inherit; transition: border-color .12s ease; }
a.card:hover { border-color: var(--teal); }
a.card:hover .more { color: var(--teal); }

/* On a dark ground the card becomes a raised surface rather than a light hole. */
:is(section.dark, .on-dark) .card { background: var(--ink-2); border-color: var(--ink-line); }
:is(section.dark, .on-dark) a.card:hover { border-color: var(--cyan); }
:is(section.dark, .on-dark) .card .more { color: var(--cyan-soft); }

@media (max-width: 599.98px) { .card { padding: var(--s24); } }

/* ---------- proof / stat rail ----------
   Context first, outcome second: the money already spent, or the estimate, is
   what makes the number underneath mean anything -- and reading it the other
   way round implied we had turned $50M into $1M. */

.proof {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
  border: 1px solid var(--soft-dark);
  border-radius: 8px;
  background: var(--paper);
  overflow: hidden;
}
.proof > div { padding: var(--s32) 28px; border-right: 1px solid var(--line); }
.proof > div:last-child { border-right: 0; }
.proof .num {
  font-size: clamp(28px, 3vw, 40px); font-weight: 600;
  line-height: 1.1; letter-spacing: -.025em; color: var(--ink);
}
.proof .num small {
  display: block; font-size: 15px; font-weight: 400;
  color: var(--body); letter-spacing: 0; margin-top: var(--s4); text-transform: none;
}
.proof p { margin-top: var(--s12); font-size: 16px; }
.proof .more {
  display: inline-block; margin-top: var(--s16);
  font-weight: 500; font-size: 16px; color: var(--ink);
  text-decoration-line: underline; text-decoration-color: var(--cyan);
  text-decoration-thickness: 2px; text-underline-offset: 4px;
}
.proof .more:hover { color: var(--teal); }
@media (max-width: 599.98px) {
  .proof > div { border-right: 0; border-bottom: 1px solid var(--line); padding: var(--s24); }
  .proof > div:last-child { border-bottom: 0; }
}

/* ---------- numbered steps ---------- */

.steps {
  counter-reset: step; display: grid; gap: 0;
  border: 1px solid var(--soft-dark); border-radius: 8px;
  background: var(--paper); overflow: hidden;
}
.steps > li {
  counter-increment: step; list-style: none;
  display: grid; grid-template-columns: 88px 1fr;
  border-bottom: 1px solid var(--line);
}
.steps > li:last-child { border-bottom: 0; }
.steps > li::before {
  content: counter(step, decimal-leading-zero);
  font-family: var(--mono); font-size: 15px; font-weight: 500;
  letter-spacing: .08em; color: var(--teal);
  padding: var(--s32) 0 var(--s32) var(--s32);
}
.steps .body { padding: var(--s32); }
.steps h3 { margin-bottom: var(--s8); }
@media (max-width: 599.98px) {
  .steps > li { grid-template-columns: 1fr; }
  .steps > li::before { padding: var(--s24) var(--s24) 0; }
  .steps .body { padding: var(--s12) var(--s24) var(--s24); }
}

/* ---------- people ---------- */

.people { display: grid; gap: var(--s24); grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); }
.person { border: 1px solid var(--soft-dark); border-radius: 8px; background: var(--paper); overflow: hidden; }
/* Full colour, 4:5, weighted to the upper half so the crop lands on the face. */
.person picture { display: block; }
/* The sources are already cropped 4:5, so cover/position only matter if a
   replacement photograph ever arrives at a different shape. */
.person img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; object-position: 50% 20%; }
.person .body { padding: var(--s24); }
.person h3 { margin: 0 0 var(--s8); }
.person p { font-size: 16px; }

:is(section.dark, .on-dark) .person { background: var(--ink-2); border-color: var(--ink-line); }

/* One person standing alone. A lone card in .grid.two fills the whole column, and
   a portrait then renders at the full column height -- which is what shipped. Side
   by side instead, the picture a fixed width and the card held to a sane measure. */
.person.row { max-width: 720px; grid-template-columns: 220px 1fr; display: grid; }
.person.row img { height: 100%; aspect-ratio: auto; }
@media (max-width: 599.98px) {
  .person.row { grid-template-columns: 1fr; }
  .person.row img { aspect-ratio: 4 / 5; }
}

/* ---------- quote ---------- */

.quote { border-left: 2px solid var(--cyan); padding: var(--s8) 0 var(--s8) var(--s32); max-width: 70ch; }
.quote blockquote { font-size: clamp(22px, 2.2vw, 28px); line-height: 1.35; font-weight: 500; letter-spacing: -.02em; color: var(--ink); }
.quote cite { display: block; margin-top: var(--s16); font-style: normal; font-size: 15px; color: var(--body); }
:is(section.dark, .on-dark) .quote blockquote { color: var(--paper); }
:is(section.dark, .on-dark) .quote cite { color: var(--muted-dark); }

/* ---------- the cyan band ----------
   Light, not navy. The designer moved this moment off navy deliberately: navy
   now opens and closes a page rather than alternating down it, and "the first
   conversation is free" reads as an offer rather than another dark slab. */

.cta-band {
  background: var(--cyan-tint);
  border: 1px solid var(--soft-dark);
  border-radius: 8px;
  padding: clamp(32px, 6vw, 64px);
  color: var(--body);
}
.cta-band h2 { max-width: 20ch; color: var(--ink); }
.cta-band p { margin-top: var(--s16); max-width: 60ch; }

/* ---------- footer ----------
   --ink-2, a raised surface against the --ink the page closes on, divided by
   --ink-line hairlines rather than the old translucent white rule. */

.site-footer { background: var(--ink-2); color: var(--muted-dark); padding: var(--s64) 0 var(--s32); font-size: 15px; }
.site-footer a { color: var(--muted-dark); text-decoration: none; }
.site-footer a:hover { color: var(--paper); }
.site-footer p { max-width: 42ch; }

.footer-grid { display: grid; gap: var(--s32); grid-template-columns: 2fr 1fr 1fr 1fr; }
.footer-grid h4 {
  font-family: var(--mono); font-size: 12px; font-weight: 500;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--soft-dark); margin-bottom: var(--s16);
}
.footer-grid ul { list-style: none; }
.footer-grid li { margin-bottom: var(--s8); }
.footer-brand img { height: 32px; width: auto; margin-bottom: var(--s16); }

.footer-bottom {
  margin-top: var(--s48); padding-top: var(--s24);
  border-top: 1px solid var(--ink-line);
  display: flex; flex-wrap: wrap; gap: var(--s16);
  justify-content: space-between; font-size: 14px;
}
/* One column, not two: the plan's 600px breakpoint stacks the footer. */
@media (max-width: 599.98px) { .footer-grid { grid-template-columns: 1fr; gap: var(--s32); } }

/* ---------- breadcrumb and prose ---------- */

.breadcrumb { font-size: 14px; color: var(--body); padding-top: var(--s32); }
.breadcrumb a { color: var(--body); }
.breadcrumb a:hover { color: var(--teal); }
/* Every page opens on navy now, which is where the breadcrumb lives. */
:is(.on-dark, section.dark) .breadcrumb { color: var(--muted-dark); }
:is(.on-dark, section.dark) .breadcrumb a { color: var(--muted-dark); }
:is(.on-dark, section.dark) .breadcrumb a:hover { color: var(--paper); }

.prose { max-width: 68ch; }
.prose h2 { margin: var(--s48) 0 var(--s16); font-size: clamp(26px, 2.6vw, 32px); }
.prose h3 { margin: var(--s32) 0 var(--s12); }
.prose p { margin-bottom: var(--s16); }
.prose ul, .prose ol { margin: 0 0 var(--s24) var(--s24); }
.prose li { margin-bottom: var(--s8); }

/* ---------- diagrams ----------
   The diagrams are the one place this site spends its boldness. They are content,
   not decoration: a CTO reading one learns something about how we think, which is
   the whole argument.

   They stay on a light ground even inside a dark section -- the designer's diagram
   spec is drawn on #FFFFFF / #F4F5F8. Step 5 rebuilds the drawings themselves; the
   vocabulary below is only repointed off the old palette so the legacy token
   bridge can go. */

.diagram { border: 1px solid var(--soft-dark); border-radius: 8px; background: var(--paper); overflow: hidden; position: relative; }

/* The text alternative. Zero-size, never display:none, so the figure's
   accessible name survives whichever drawing is on screen. */
.diagram-alt { position: absolute; width: 0; height: 0; overflow: hidden; }

.diagram .scroller { overflow-x: auto; }
.diagram-svg { display: block; width: 100%; height: auto; padding: var(--s32) 28px var(--s24); }

/* Two drawings, one diagram. An SVG with a viewBox scales uniformly, so a
   600-wide drawing squeezed into a phone renders its 13px labels at about 6px.
   13px only survives at scale ~1, which means a drawing about as wide as the
   phone's content column -- and a drawing that narrow is useless on a desktop.
   One cannot be both, so there are two, and the breakpoint picks. */
/* Capped, or it stretches: drawn at 280, stretched across a 768px tablet it
   scales 2.4x and renders its 13px labels at 32px. Capped and centred, it sits
   between about 13px and 17px everywhere below the breakpoint. */
.diagram-narrow { display: none; max-width: 360px; margin: 0 auto; }
.diagram-wide   { display: block; max-width: 640px; margin: 0 auto; }

/* The marketplace comparison is the one drawing that is wider than the column
   even on a desktop: both its rows share a time axis and the proportion is the
   argument, so it cannot be reflowed. It scrolls, inside a keyboard-reachable
   region, above the breakpoint only -- below it, the narrow version takes over
   and nothing scrolls. */
.diagram .scroller > .diagram-wide { min-width: 1160px; max-width: none; margin: 0; }
.diagram .scroll-hint {
  font-family: var(--mono); font-size: 12px; font-weight: 500;
  letter-spacing: .08em; text-transform: uppercase; color: var(--body);
  padding: var(--s16) 28px 0; margin: 0;
}
.diagram:has(.scroller)::after {
  content: ""; position: absolute; top: 0; right: 0; bottom: 0; width: 40px;
  background: linear-gradient(to right, rgb(255 255 255 / 0), var(--paper));
  pointer-events: none;
}

@media (max-width: 899.98px) {
  .diagram-wide { display: none; }
  .diagram-narrow { display: block; padding: var(--s24) var(--s16); }
  /* Nothing scrolls sideways below the breakpoint any more, so the scroller,
     its hint and its edge fade all stand down. */
  .diagram .scroller { overflow-x: visible; }
  .diagram .scroll-hint { display: none; }
  .diagram:has(.scroller)::after { content: none; }
}
}
.diagram figcaption { border-top: 1px solid var(--line); padding: var(--s24) 28px; font-size: 16px; color: var(--body); }
/* The rule must run the full width of the frame, so the measure is limited on the
   text inside it rather than on the figcaption itself. */
.diagram figcaption > span { display: block; max-width: 88ch; }

.diagram-svg text { font-family: var(--font); }
.d-stage     { font-family: var(--mono); font-size: 13px; font-weight: 500; fill: var(--teal); letter-spacing: .08em; }
.d-head      { font-size: 20px; font-weight: 600; fill: var(--ink); letter-spacing: -.02em; }
.d-box       { fill: var(--paper); stroke: var(--soft-dark); stroke-width: 1.5; }
.d-legacy    { fill: var(--paper); }
.d-faded     { stroke: var(--line); }
.d-new       { stroke: var(--teal); stroke-width: 2; }
.d-ship      { stroke: var(--teal); stroke-width: 2; fill: var(--cyan-tint-2); }
.d-boxtitle  { font-size: 16px; font-weight: 600; fill: var(--ink); }
.d-boxtitle.d-dim { fill: var(--body); }
.d-boxsub    { font-size: 13px; fill: var(--body); }
.d-row       { fill: var(--paper-2); stroke: none; }
.d-rowtext   { font-size: 13px; fill: var(--body); }
.d-bad       { fill: var(--problem); }
.d-note      { font-size: 14px; fill: var(--body); }
.d-arrow     { stroke: var(--ink-3); stroke-width: 1.5; fill: none; marker-end: url(#d-head); }
.d-tie       { stroke: var(--ink-3); stroke-width: 1.5; fill: none; }
.d-axis      { stroke: var(--line); stroke-width: 1.5; }
.d-tick      { stroke: var(--ink-3); stroke-width: 1.5; }
.d-axislabel { font-size: 13px; fill: var(--body); letter-spacing: .02em; }
.d-end       { text-anchor: end; }

.d-fail      { fill: var(--problem-tint); stroke: var(--problem); stroke-width: 1.5; }
.d-failtext  { font-size: 14px; font-weight: 600; fill: var(--problem-text); }
.d-ok        { fill: var(--teal); }
.d-lane      { fill: var(--paper-2); stroke: none; }
/* 13, not 12: the spec floors diagram text at 13px once rendered. */
.d-lanelabel { font-family: var(--mono); font-size: 13px; font-weight: 500; fill: var(--body); letter-spacing: .08em; }
.d-pill      { fill: var(--paper); stroke: var(--soft-dark); stroke-width: 1.5; }
.d-pilltext  { font-size: 13px; fill: var(--ink); }
.d-metric    { font-size: 28px; font-weight: 600; fill: var(--ink); letter-spacing: -.025em; }
.d-metricsub { font-size: 13px; fill: var(--body); }
.d-dash      { stroke: var(--ink-3); stroke-width: 1.5; stroke-dasharray: 6 5; fill: none; }
.d-feed      { stroke: var(--teal); stroke-width: 1.5; fill: none; marker-end: url(#d-head-blue); }
.d-small     { font-size: 13px; fill: var(--body); }
.d-mid       { text-anchor: middle; }

/* The one orchestrated moment on the site: the connectors draw themselves the
   first time a diagram comes into view.

   Only the connecting LINES animate. Boxes and text are never hidden, not even
   for a frame. An earlier version faded the content in from opacity 0, which
   meant anything rendering mid-animation -- a screenshot, a crawler, a slow
   frame -- saw an empty diagram. Content must never depend on an animation
   having run. A half-drawn connector costs nothing; a missing box costs the
   whole picture. */
@media (prefers-reduced-motion: no-preference) {
  .diagram.is-drawing .d-arrow,
  .diagram.is-drawing .d-tie,
  .diagram.is-drawing .d-feed { stroke-dasharray: 1200; stroke-dashoffset: 1200; animation: d-draw .7s ease forwards; }
  .diagram.is-drawing [data-s="2"] { animation-delay: .18s; }
  .diagram.is-drawing [data-s="3"] { animation-delay: .36s; }
}
@keyframes d-draw { to { stroke-dashoffset: 0; } }

/* ---------- product screenshot ----------
   Deliberately light, even on navy: it is a window into the product, and the
   product is light-themed. A dark capture would show the product as something it
   is not. A 1px --ink-3 frame keeps the light edge from bleeding into the navy. */

.shot { border: 1px solid var(--soft-dark); background: var(--paper); line-height: 0; overflow: hidden; }
.shot picture { display: block; }
.shot img { width: 100%; }
.shot figcaption { border-top: 1px solid var(--line); padding: var(--s16) var(--s24); font-size: 15px; color: var(--body); line-height: 1.6; }
:is(section.dark, .on-dark) .shot { border-color: var(--ink-3); }

.split { display: grid; gap: var(--s48); grid-template-columns: 1fr 1.15fr; align-items: center; }
@media (max-width: 899.98px) { .split { grid-template-columns: 1fr; } }

/* ---------- case studies ---------- */

.case-head { padding: var(--s48) 0 clamp(56px, 7vw, 88px); }
.case-head h1 { margin: var(--s16) 0 var(--s24); max-width: 24ch; }
.case-head .lede { max-width: 70ch; }

/* The three-beat spine every case study follows: what made it hard, what we did,
   what came out. Same shape every time, so they can be compared rather than
   just read. */
.beats {
  display: grid; gap: 0;
  border: 1px solid var(--soft-dark); border-radius: 8px;
  background: var(--paper); overflow: hidden;
}
.beats > section { padding: var(--s32) 28px; border-bottom: 1px solid var(--line); background: transparent; }
.beats > section:last-child { border-bottom: 0; }
.beats h2 { font-size: 20px; letter-spacing: -.01em; margin-bottom: var(--s16); }
.beats h2 .n { font-family: var(--mono); font-size: 13px; font-weight: 500; letter-spacing: .08em; color: var(--teal); margin-right: var(--s12); }
.beats ul { list-style: none; max-width: 68ch; }
.beats li { position: relative; padding-left: 26px; margin-bottom: var(--s12); }

/* The markers carry the meaning (designer's notes, section 8): an orange dash
   for the problem, a teal arrow for what we did, an ink square for what came
   out. The three beats are always in that order, so position is the semantic --
   no extra classes in the markup. */
.beats li::before { content: ""; position: absolute; left: 0; top: .6em; width: 12px; height: 2px; background: var(--problem); }
.beats > section:nth-child(2) li::before {
  content: "→"; width: auto; height: auto; top: 0; background: none;
  color: var(--teal); font-weight: 600; line-height: inherit;
}
.beats > section:nth-child(3) li::before,
.beats .outcome li::before { content: ""; width: 9px; height: 9px; top: .45em; background: var(--ink); }

/* The pull quote is a SIBLING of the three beats in the markup, not a child of
   one, so in a three-column grid it lands in a fourth cell and renders about
   400px wide -- which is how it is on the live site today. Span it across the
   full width instead and separate it with a hairline. Step 4 can lift it out of
   the panel properly; this stops it being a narrow ribbon in the meantime. */
.beats > .quote {
  grid-column: 1 / -1;
  border-top: 1px solid var(--line);
  border-left: 2px solid var(--cyan);
  padding: var(--s32) 28px;
  max-width: none;
}
.beats > .quote blockquote { max-width: 60ch; }

@media (min-width: 900px) {
  .beats { grid-template-columns: 1fr 1fr 1fr; }
  .beats > section { border-bottom: 0; border-right: 1px solid var(--line); }
  .beats > section:nth-of-type(3) { border-right: 0; }
}

.techlist { display: flex; flex-wrap: wrap; gap: var(--s8); margin-top: var(--s8); list-style: none; }
.techlist li {
  border: 1px solid var(--line); border-radius: 6px;
  padding: 6px 12px; font-size: 14px; color: var(--body);
}

.case-nav { display: grid; gap: var(--s24); grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); }

/* ---------- preview banner ----------
   Only ever rendered by `build.py --preview`. A reviewer must never be in any
   doubt about whether they are looking at the live site. This is the single
   place orange is not the problem/before state: it is not part of the design,
   and it never reaches production. */
.preview-bar {
  background: var(--problem); color: var(--ink);
  border-bottom: 1px solid var(--ink);
  padding: 10px var(--s24); text-align: center;
  font-size: 14px; font-weight: 600; letter-spacing: .02em;
}
.preview-bar span { font-weight: 400; }

/* ---------- forms ----------
   52px fields at 16px, because anything smaller makes iOS zoom the page on
   focus and never zoom back. Square corners, a 1px --field-line border (3.3:1
   on white), mono "optional" tags, and no asterisks: a required field is the
   default, so it is the optional one that needs saying. */

.form-grid { display: grid; gap: var(--s16); grid-template-columns: 1fr 1fr; }
.form-grid .wide { grid-column: 1 / -1; }
@media (max-width: 599.98px) { .form-grid { grid-template-columns: 1fr; } }

.field > label {
  display: block; font-size: 15px; font-weight: 500;
  letter-spacing: 0; text-transform: none;
  color: var(--ink); margin-bottom: var(--s8);
}
.field .opt {
  font-family: var(--mono); font-size: 12px; font-weight: 500;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--body); margin-left: var(--s8);
}
.field input, .field textarea {
  width: 100%; font: inherit; font-size: 16px; line-height: 1.5;
  color: var(--ink); background: var(--paper);
  border: 1px solid var(--field-line); border-radius: 0;
  padding: 14px var(--s16); min-height: 52px;
}
.field textarea { min-height: 170px; resize: vertical; }
.field .hint { font-size: 14px; color: var(--body); margin-top: var(--s8); }

/* Errors are checked on submit, never while typing: telling someone their email
   is invalid when they have typed two characters of it is noise, not help. */
.field.is-error input, .field.is-error textarea { border: 2px solid var(--error); }
.field .err { display: none; align-items: flex-start; gap: var(--s8); margin-top: var(--s8); font-size: 14px; color: var(--error); }
.field.is-error .err { display: flex; }
/* The glyph is decoration -- the message text always appears, so the error is
   never signalled by colour or icon alone. */
.field .err::before {
  content: "!"; flex: none;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--error); color: var(--paper);
  font-size: 12px; font-weight: 600; line-height: 18px; text-align: center;
}

.form-summary {
  display: none;
  border: 2px solid var(--error); border-radius: 0;
  padding: var(--s16) var(--s24); margin-bottom: var(--s24);
  color: var(--ink);
}
.form-summary.is-shown { display: block; }
.form-summary h3 { font-size: 17px; margin-bottom: var(--s8); color: var(--error); }
.form-summary ul { margin: 0 0 0 var(--s16); font-size: 15px; }
.form-summary a { color: var(--ink); }

/* Real checkboxes inside a 52px label, so the whole box is the target. */
.checks { display: grid; gap: var(--s12); grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr)); }
.checks label {
  display: flex; gap: var(--s12); align-items: center;
  border: 1px solid var(--field-line); min-height: 52px;
  padding: var(--s12) var(--s16); font-size: 16px; cursor: pointer;
}
.checks label:hover { border-color: var(--ink); }
.checks label:has(input:checked) { border: 2px solid var(--teal); background: var(--cyan-tint-2); }
.checks input { accent-color: var(--teal); width: 18px; height: 18px; flex: none; }

/* The sent panel replaces the form outright. It takes focus and repeats the
   address the reply is going to, so there is no doubt it went somewhere. */
.form-sent {
  background: var(--cyan-tint-2);
  border: 2px solid var(--teal);
  padding: var(--s32) 28px;
  color: var(--body);
}
.form-sent h3 { color: var(--ink); margin-bottom: var(--s12); }
.form-sent p { margin-bottom: var(--s8); }

.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

