/* ============================================================
   AtraX — sub-page layout
   Components that only exist on about / services / work /
   contact. Everything here is built from the tokens in
   styles.css and reuses the primitives in sections.css
   (.wrap, .section, .section-head, .eyebrow, .reveal, .btn).
   ============================================================ */

/* ---------------- Shared sub-page hero ---------------- */
.page-hero {
  position: relative;
  overflow: hidden;
  padding: 172px 0 84px;
  /* The homepage's wash, at the homepage's strength and position, rather than a
     lighter pour of it off in the corner. "Opens the same way" is the whole
     point, so there is no reason for it to be a different gradient.

     Transparent rather than #ffffff underneath. At the top of a page the two
     look identical, because the theme is light there either way - this is not
     fixing a visible bug. It removes a latent one: an opaque white hero is the
     single element on these pages that paints over the theme canvas instead of
     letting it through, so it would be the one thing that did not follow if a
     page ever opened dark. The homepage hero is already transparent. */
  background: radial-gradient(58% 62% at 26% 6%, rgba(255, 150, 110, 0.16), transparent 70%);
  color: var(--ink);
}
.page-hero.ph-tight { padding-bottom: 60px; }
@media (max-height: 900px) { .page-hero { padding-top: 138px; } }


.ph-inner { position: relative; z-index: 2; }

/* Brought into the homepage's type family: same weight, same tracking, same
   line-height, and a size on the same curve.

   NOT the same size, deliberately. The homepage caps at 95px, these at 72px.
   Matching it exactly would have every sub-page shouting as loudly as the front
   door, and a 95px headline on Contact is absurd. 72px also fills a real hole:
   the scale ran 52px straight to 95px with nothing between, which is why these
   pages read as a different site rather than a quieter room in the same one. */
.page-hero h1 {
  font-family: var(--font-display);
  font-size: clamp(30px, 5.4vw, 72px);
  font-weight: 700;
  line-height: 1.02;
  letter-spacing: -0.045em;
  /* Scales with the headline. 22px was set when this capped at 52px, and with
     line-height 1.02 the box hugs the glyphs, so at 72px a flat 22px reads
     visibly tighter than it did at 52 — the 11px kicker above a case-study
     title ended up closer to it than its own cap height. */
  margin: clamp(16px, 2.2vw, 30px) 0 0;
  color: var(--ink);
  max-width: 15ch;
}

/* Centred, for the landing pages - About, Work, Blog and Contact. The nine case
   studies and eleven articles keep the left-aligned hero, because a centred
   headline sitting directly on top of a long left-aligned body reads as a
   mismatch. The homepage gets away with centring precisely because its hero is
   a short statement with nothing under it.

   Services came off this list on 2026-09-22: its mockup sets the hero left, and
   with six chips under the standfirst it has more beneath it than the other
   four do. It sets its own measure below, because the base 15ch is cut for a
   headline that already carries its own line breaks. */
.page-hero.ph-centre { text-align: center; }
.page-hero.ph-centre h1 { max-width: 20ch; margin-left: auto; margin-right: auto; }
.page-hero.ph-centre .ph-sub { margin-left: auto; margin-right: auto; }
.page-hero.ph-centre .ph-jump { justify-content: center; }
.page-hero h1 .h-line { display: block; }

.ph-sub {
  /* Same reason as the h1 margin above. */
  margin-top: clamp(16px, 2.2vw, 30px);
  /* 620px, not 560px. The 560 was already dead: a stray
     `.page-hero h1 + .ph-sub { max-width: 620px }` sat in the case-study block,
     and since every page with a .ph-sub puts it directly after the h1, it won
     everywhere. Stating it once here changes no rendering and stops the next
     person believing the 560. It is also the homepage .hero-sub measure. */
  max-width: 620px;
  font-size: var(--t-lede); line-height: 1.6; color: var(--ink-2);
}

/* jump chips (services) */
/* The accent line in a page-hero h1. .section-head h2 em already does this for
   section heads; an h1 had never needed it until the services hero took its
   third line in accent. */
.page-hero h1 em { font-style: normal; color: var(--accent); }

/* S2 read "bar below readable" against this bar. It was 11px mono in 7px
   padding - a jump-link strip rather than something you could scan. Now a row
   of chips carrying the same accent icon idiom as the rail tiles, at the
   card-body step. Still jump links: every one points at a section that exists.
   .ph-jump keeps its old rule for the pages that still use the plain form. */
.ph-jump { margin-top: 30px; display: flex; flex-wrap: wrap; gap: 8px; }
.ph-jump a {
  font-family: var(--font-mono); font-size: var(--t-2xs); letter-spacing: .04em;
  color: var(--ink-2); border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: 7px 13px; background: rgba(255,255,255,.7);
  transition: color .2s, border-color .2s, transform .2s;
}

body[data-page="services"] .page-hero h1 { max-width: 20ch; }
body[data-page="services"] .ph-jump { gap: var(--s-3); justify-content: flex-start; }
body[data-page="services"] .ph-jump a {
  display: inline-flex; align-items: center; gap: var(--s-3);
  font-family: var(--font-body); font-size: var(--t-sm); font-weight: 600; letter-spacing: -0.005em;
  color: var(--ink); padding: var(--s-3) var(--s-5);
  border-radius: var(--r-sm); background: rgba(255,255,255,.72);
}
body[data-page="services"] .ph-jump a:hover { border-color: var(--accent-line); color: var(--accent); }
.pj-ico { display: inline-flex; color: var(--accent); }
.pj-ico svg { width: 18px; height: 18px; display: block; }
.ph-jump a:hover { color: var(--ink); border-color: var(--ink); transform: translateY(-2px); }

/* ---------------- About — hero ----------------
   Scoped to this page, not added to .ph-centre, because the measure above was
   tuned for the other four centred heroes and they did not ask for it.

   About's headline is two full sentences ("We've done this before. / We know
   what happens next."), which is longer than the 20ch .ph-centre gives. It
   matters more here than it would elsewhere: [data-motion="lines"] clips each
   line to its OWN box, so a line that wrapped inside that box would rise as a
   single two-line shape behind one clip — the effect would still run, it would
   just stop looking deliberate. Widening the measure keeps one sentence per
   .h-line at every width down to the clamp's floor. */
body[data-page="about"] .page-hero.ph-centre h1 { max-width: 27ch; }
/* On a phone the clamp bottoms out at 30px and the second sentence has to wrap
   whatever the measure is. Balanced, it breaks as "We know what / happens
   next." instead of leaving "next." alone on a line of its own. On .h-line, not
   on the h1: each line is its own block, so the h1 has nothing to balance. */
body[data-page="about"] .page-hero h1 .h-line { text-wrap: balance; }

/* ---------------- About — story timeline ----------------
   Runs tighter into the hero than a plain .section does. The hero above is
   centred and about 560px tall — it does not fill a screen the way the
   homepage's does, so there is no centring slack in its trailing space and
   .section's 120px left 220px of plain white between the sub-line and this
   heading. That is the page's own section-to-section gap (208–240px), so the
   opening read as two unrelated blocks rather than as one. 72px is what
   .clients takes for the same job on the homepage: the first band after the
   hero runs tight and the page only settles into the full 120px below it. */
.story { padding-top: var(--s-18); }

.story-grid { display: grid; grid-template-columns: 1fr 0.9fr; gap: clamp(var(--s-8), 5vw, var(--s-18)); align-items: start; }

.timeline { list-style: none; position: relative; }
.timeline::before {
  content: ""; position: absolute; left: 7px; top: 10px; bottom: 10px;
  width: 1px;
  /* Fades out at the top and arrives at accent at the bottom, where the "Today"
     node is. The rule now ENDS somewhere rather than dissolving, which is the
     whole claim the four entries make. */
  background: linear-gradient(to bottom, var(--line), var(--line) 62%, var(--accent-line));
}

/* data-motion="track" — the effect that exists for exactly this component: the
   rule draws, each node pops as the draw reaches it, and the entry's copy drops
   in behind it. This list was on "stagger" (four blocks rising in turn), which
   revealed a sequence without ever drawing one.

   track is written for a HORIZONTAL rule, because its only other user is
   .stage-track on services.html. The three rules below turn the draw through 90
   degrees, and they are deliberately a copy of what .stage-track already does
   for itself below 860px — same override, same reason. They tie with the
   [data-motion="track"] rules in styles.css on specificity and win on order,
   since pages.css loads after it. */
.timeline::before { transform-origin: top center; }
.timeline:not(.is-in)::before { transform: scaleY(0); }
.timeline.is-in::before { animation-name: motion-drop; }

/* --s-26, not the --s-22 this had. Three of the four labels are now year RANGES
   ("2018–2020"), and at --t-xs tracked out to --ls-mono a nine-character label
   fills 88px to the millimetre — it fitted, with nothing left for a font that
   loads a hair wide. The body column gives up 16px and is capped at 44ch
   anyway, so nothing else moves. */
.tl-item { position: relative; display: grid; grid-template-columns: var(--s-26) 1fr; gap: var(--s-5); padding: 0 0 var(--s-10) var(--s-8); }
.tl-item::before {
  content: ""; position: absolute; left: 0; top: 7px; width: 15px; height: 15px;
  border-radius: 50%; background: #fff; border: 1px solid var(--line);
  transition: border-color .3s, box-shadow .3s;
}
.tl-item:hover::before { border-color: var(--accent); box-shadow: 0 0 0 4px var(--accent-soft); }
/* The present is a filled node, not another empty one. It is the same treatment
   :hover already gives a node, held permanently on the one entry that is still
   true — so the timeline reads as arriving rather than as stopping. */
.tl-item:last-child::before { background: var(--accent); border-color: var(--accent); box-shadow: 0 0 0 4px var(--accent-soft); }
.tl-item:last-child { padding-bottom: 0; }

/* Declared here rather than in the shared Responsive block at the foot of this
   file: it is one component's own breakpoint and nothing else in that block
   touches the timeline. */
@media (max-width: 660px) {
  /* A 104px year column costs a third of a phone's measure, and the entries
     that pay for it are the ones with the longest headings. Stacked, the year
     becomes a label ABOVE its entry — which is what it already looks like,
     since it is the only accent-coloured line in the list. */
  .tl-item { grid-template-columns: minmax(0, 1fr); gap: var(--s-2); }
  .tl-year { padding-top: 0; }
}
/* --ls-mono, not 0.08em. Every label on the homepage — .wwd-n, .work-year,
   .work-all, .eyebrow — is tracked at the token; the three on this page were
   hand-set at roughly half of it, which is most of why the sub-page labels read
   as a different typographic system. Same fix on .val-n and .lead-role below. */
.tl-year {
  font-family: var(--font-mono); font-size: var(--t-xs); letter-spacing: var(--ls-mono);
  text-transform: uppercase; color: var(--accent); padding-top: 4px;
}
.tl-body h3 { font-size: var(--t-xl); font-weight: 700; letter-spacing: -0.02em; }
/* --t-base rather than the inherited 16px default: this was the only body line
   on the page off the scale, running a step larger than the gloss under a value
   and the blurb under a portrait, which are the same kind of text. */
.tl-body p { margin-top: var(--s-3); font-size: var(--t-base); color: var(--ink-2); line-height: 1.62; max-width: 44ch; }

.story-photo { border-radius: var(--r-lg); overflow: hidden; border: 1px solid var(--line); box-shadow: var(--shadow-lg); }
.story-photo img { display: block; width: 100%; height: auto; }

/* ---------------- About — the Clutch credential ----------------
   Sits in the stats band, under the four numbers, because that band already IS
   this page's row of credentials — a rating belongs with them and nowhere else
   on the page. It is NOT a fifth .stat: the four cells count up from zero, the
   grid is four columns with its own responsive re-ruling in sections.css, and a
   rating that is not ours to count would have had to opt out of both.

   Built from the .ph-jump chip instead — mono label, hairline, --r-sm, 2px lift
   on hover — so it reads as a control this site already owns. Right-aligned:
   the rule above sweeps left to right and the numbers cascade the same way, so
   the credential lands where that movement finishes. */
.stats-cred { margin-top: var(--s-7); display: flex; justify-content: flex-end; }
.cred {
  display: inline-flex; align-items: center; gap: var(--s-2);
  font-family: var(--font-mono); font-size: var(--t-2xs); letter-spacing: var(--ls-mono);
  text-transform: uppercase; color: var(--page-text-2);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: var(--s-3) var(--s-4);
  transition: color .25s var(--ease), border-color .25s var(--ease), transform .25s var(--ease);
}
/* The one place accent is spent here. Tracking pulled back off --ls-mono for
   these three characters only — a star carries its own space and at 0.14em it
   detached from the 5 it belongs to. */
.cred-stars { color: var(--accent); letter-spacing: 0.04em; font-weight: 700; }
.cred:hover { color: var(--page-text); border-color: var(--ink); transform: translateY(-2px); }
/* .work-all's pair, to the millisecond and the pixel — .arr is never styled
   globally, every component that uses the glyph moves it itself, and an
   external link should travel exactly as far as the homepage's does. */
.cred .arr { transition: transform .25s var(--ease); }
.cred:hover .arr { transform: translate(2px, -2px); }

/* ---------------- About — values ----------------
   A numbered manifesto list, not a card grid: number | claim | gloss on one
   full-measure row each, separated by hairlines. Three columns of text rather
   than three boxes — the rhythm comes from the rules, not from borders.

   Every colour in this section is a --page-* token, which is how the homepage's
   dark .cta band sets its h2 and its lede. It was --d-ink / --d-ink-2 /
   --d-ink-3 and a hard #fff, plus a `color: inherit` on the section head to
   force them past the shared rules, and all of that was wrong for one reason:
   the ground here is not a dark background, it is the global theme canvas
   easing from white to black. app.js only reaches full dark once this section's
   top edge is 31% of the way down the viewport, and the head is already on
   screen well before that — around 620px down a 700px window at the moment the
   tween starts — so white-on-white left the heading invisible for the whole
   light half of the transition.

   The --page-* trio travels with the ground instead, and resolves to exactly
   these same dark values at t = 1, so nothing about the settled section
   changes. It is only ever the same grey as the ground for the couple of frames
   the smootherstep spends near t = 0.5. */
.values { color: var(--page-text); }

/* Six cards, two up, rebuilt from the mockup on the A1 comment. This replaces
   a ruled manifesto list, and the comment that used to sit here argued against
   exactly that ("the rhythm comes from the rules, not from borders"). The owner
   overrode it on 2026-09-22 with the mockup in hand, so the argument is
   recorded rather than silently dropped: if the cards ever come out again, the
   list is what they replaced.

   Not taken from the mockup: the per-card illustrations, which would be six new
   image assets on a page that ships none, and the "THE RESULT" band, whose
   10+/40+/100+ would contradict the stats band directly above it. */
.val-grid {
  list-style: none; margin: 0; padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(var(--s-4), 1.6vw, var(--s-6));
}
.val-card {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas: "n n" "ico t" "ico b";
  align-content: start;
  gap: var(--s-3) var(--s-5);
  padding: clamp(var(--s-6), 2vw, var(--s-8));
  border: 1px solid var(--d-line-soft);
  border-radius: var(--r-lg);
  background: var(--d-bg-2);
  transition: border-color .35s var(--ease), background .35s var(--ease);
}
.val-card:hover { border-color: var(--accent-line); background: var(--d-bg-3, var(--d-bg-2)); }

.val-n {
  grid-area: n;
  font-family: var(--font-mono); font-size: var(--t-xs); letter-spacing: var(--ls-mono);
  color: var(--page-text-3); transition: color .35s var(--ease);
  font-variant-numeric: tabular-nums;
}
.val-card:hover .val-n { color: var(--accent); }

/* Same tile as the services rail: --accent-soft is the fallback, then a mix
   into the dark ground rather than into transparent, which premultiplies. */
.val-ico {
  grid-area: ico;
  width: 44px; height: 44px;
  display: grid; place-items: center;
  border-radius: var(--r-lg);
  color: var(--accent);
  background: var(--accent-soft);
  background: color-mix(in srgb, var(--accent) 13%, var(--d-bg));
  transition: background .35s var(--ease);
}
.val-card:hover .val-ico { background: color-mix(in srgb, var(--accent) 20%, var(--d-bg)); }
.val-ico svg { width: 22px; height: 22px; display: block; }

.val-t { grid-area: t; font-size: var(--t-h4); font-weight: 700; letter-spacing: -0.02em; line-height: 1.2; color: var(--page-text); align-self: end; }
.val-b { grid-area: b; font-size: var(--t-base); line-height: 1.62; color: var(--page-text-2); }

/* The closing band from the same mockup. The glow is a radial mix INTO --d-bg
   rather than out to transparent: mixing to transparent premultiplies alpha,
   which is the trap the theme tween documents avoiding, and on a panel this
   large the muddy edge would show.

   NOTE: "The result" is an eyebrow, and eyebrows were stripped site-wide (54 of
   them) on an earlier instruction from the same owner. This one is here because
   he pointed at the mockup and said to build it. It is currently the only
   eyebrow on the site - if the ban still stands, delete .vr-eyebrow and its
   span and nothing else changes. */
.val-result {
  margin-top: clamp(var(--s-5), 2vw, var(--s-8));
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  align-items: center;
  gap: clamp(var(--s-6), 3vw, var(--s-12));
  padding: clamp(var(--s-7), 3vw, var(--s-10));
  border: 1px solid var(--d-line-soft);
  border-radius: var(--r-lg);
  background: radial-gradient(118% 150% at 100% 0%, color-mix(in srgb, var(--accent) 20%, var(--d-bg)), var(--d-bg) 64%);
}
/* The accent eyebrow. styles.css still carries the site's original .eyebrow -
   muted, with an 18px leading dash - but every use of it was stripped from the
   HTML when the 54 eyebrows came out. The two that came back on 2026-09-22 are
   from the owner's mockups, which draw them accent and without the dash, so
   they share this class rather than each inventing one. */
.eyebrow-accent {
  display: block;
  font-family: var(--font-mono); font-size: var(--t-2xs); letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--accent);
}
.vr-t {
  margin-top: var(--s-4);
  font-size: var(--t-h2s); font-weight: 700; letter-spacing: -0.02em; line-height: 1.12;
  color: var(--page-text);
}
.vr-t em { font-style: normal; color: var(--accent); }

.vr-stats { margin: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.vr-stat { padding-left: clamp(var(--s-4), 2vw, var(--s-7)); border-left: 1px solid var(--d-line-soft); }
.vr-stat:first-child { padding-left: 0; border-left: 0; }
.vr-n {
  font-family: var(--font-head); font-size: var(--t-h3); font-weight: 800;
  letter-spacing: var(--ls-display); line-height: 1; color: var(--page-text);
}
.vr-n .suf { color: var(--accent); }
.vr-l {
  margin: var(--s-3) 0 0;
  font-size: var(--t-sm); line-height: 1.4; color: var(--page-text-3);
}

/* ---------------- About — leadership ----------------
   Three equal 4/5 plates in one row.

   This was an asymmetric spread for a while — a large plate for Gerti against
   a narrower column holding a square plate for Alda and the "where we work"
   type block, and the ratio had been pushed from 1.12/0.88 to 1.32/0.68 on an
   explicit request to make the two differ MORE. That reversed on 2026-09-22,
   when a third portrait was added and all three were asked to match. Do not
   restore the asymmetry from the older comment without asking: it is not a
   layout preference, it is a request that was made twice, in opposite
   directions.

   The type block moved below the row rather than into a fourth cell — as a
   cell it reads as a person who has lost their photograph. */
.lead-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(var(--s-5), 2.6vw, var(--s-8));
  align-items: start;
}

.lead-photo {
  border-radius: var(--r-lg); overflow: hidden; background: var(--bg-muted);
  aspect-ratio: 4 / 5; border: 1px solid var(--line);
}
/* Three equal plates, so they resolve together rather than in scale order —
   data-motion-delay on the figures carries the sequence instead. */
.lead-fig { --plate-dur: 1.1s; }
.lead-photo img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .6s var(--ease); }
.lead-fig:hover .lead-photo img { transform: scale(1.04); }

.lead-meta { margin-top: var(--s-4); display: flex; flex-direction: column; gap: 3px; }
/* --font-display. These two spans and .lead-where-t were the only display-scale
   lines left on the page still setting in Manrope, because a <span> and a <p>
   inherit the body face where an h2/h3 does not. The homepage's equivalent —
   .work-name, the name under a project plate — is Space Grotesk.

   The big plate then takes the display weight/tracking pair (600 / -0.012em,
   as on every h2 on the site) and the small one keeps the card-heading pair
   (700 / -0.02em). That split is already in the scale at 19px; the two names
   just sit either side of it. */
.lead-name { font-family: var(--font-display); font-size: var(--t-xl); font-weight: 700; letter-spacing: -0.02em; }
.lead-role { font-family: var(--font-mono); font-size: var(--t-2xs); letter-spacing: var(--ls-mono); text-transform: uppercase; color: var(--ink-4); }

.lead-where { border-top: 1px solid var(--line); padding-top: var(--s-5);
  margin-top: clamp(var(--s-8), 4vw, var(--s-12)); }
.lead-where-t { margin-top: var(--s-3); font-family: var(--font-display); font-size: var(--t-h4); font-weight: 700; letter-spacing: -0.02em; line-height: 1.25; }
.lead-where-b { margin-top: var(--s-3); font-size: var(--t-base); line-height: 1.62; color: var(--ink-2); max-width: 34ch; }

/* ---------------- Services — detail blocks ----------------
   A ruled list closed at both ends, rather than two blocks of loose text.

   The hero above is centred and these are left-aligned, so the page changes
   axis with nothing to mark the change. A full-measure hairline is how the
   rest of the site marks exactly that — .stats-rule, the top rule on
   .val-list, the frame around .wwd-row — and it is the cheapest thing that
   turns "text that happens to start here" into a band.

   The first block used to drop both its rule and its top padding, which is
   what left a bare "01" hanging under a centred 72px headline with 204px of
   nothing above it. It keeps them now, and the section gives back a third of
   its own top padding so the rule does not float: 88 + the hero's 84 is close
   to the 192 the homepage runs between its bands, minus the part the hero has
   already paid for. */
.svc-detail { padding-top: var(--s-22); }
.svc-block {
  display: grid; grid-template-columns: 120px 1fr; gap: clamp(var(--s-5), 4vw, var(--s-14));
  padding: var(--s-14) 0; border-top: 1px solid var(--page-line);
  scroll-margin-top: 96px;
}
.svc-block:last-child { border-bottom: 1px solid var(--page-line); }
/* The homepage's .wwd-n to the value — 11px at --ls-mono. At 13px/.1em it was
   competing with the tags below it instead of indexing the block. The extra
   2px of padding is the cap-height difference at the smaller size. */
.svc-n { font-family: var(--font-mono); font-size: var(--t-2xs); letter-spacing: var(--ls-mono); color: var(--accent); padding-top: var(--s-3); }
.svc-body { max-width: 62ch; }
.svc-body h2 { font-family: var(--font-display); font-size: var(--t-h2s); font-weight: 600; letter-spacing: -0.012em; line-height: 1.12; }
/* The hero's language carried down into the two blocks it links to (owner,
   2026-09-23: "make it like the hero above it, with the icons and the orange
   text"). Each block takes the glyph its hero chip carries, in the same tinted
   tile the process rail uses further down; one accent word in the heading, set
   like .section-head h2 em; and the tags become the hero's chip. Nothing here
   is a new colour: --accent, --accent-soft and the --page-* family only. */
.svc-ico {
  width: var(--s-12); height: var(--s-12); margin-bottom: var(--s-5);
  display: grid; place-items: center;
  border-radius: var(--r-lg);
  color: var(--accent); background: var(--accent-soft);
}
.svc-ico svg { width: var(--s-6); height: var(--s-6); display: block; }
.svc-body h2 em { font-style: normal; color: var(--accent); }
.svc-body > p { margin-top: var(--s-4); font-size: var(--t-lg); line-height: 1.66; color: var(--page-text-2); }
/* The hero's .ph-jump chip, one step quieter because these are not links: the
   same body face, hairline and --r-sm, the accent glyph on the left, but
   --page-text-2 instead of full ink and a step less padding. */
.svc-points { list-style: none; margin-top: var(--s-6); display: flex; flex-wrap: wrap; gap: var(--s-3); }
.svc-points li {
  display: inline-flex; align-items: center; gap: var(--s-2);
  font-family: var(--font-body); font-size: var(--t-sm); font-weight: 600; letter-spacing: -0.005em;
  color: var(--page-text-2);
  border: 1px solid var(--page-line); border-radius: var(--r-sm);
  padding: var(--s-2) var(--s-4);
}
.svc-points svg { width: var(--s-4); height: var(--s-4); flex: none; display: block; color: var(--accent); }
.svc-body .bs-link { margin-top: var(--s-6); display: inline-flex; color: var(--accent); }

/* ---------------- Services — technology stack ----------------
   A spec band, not a card row: one slate panel divided by vertical hairlines,
   mono labels, tags set as plain lines rather than pills. The values list on
   /about is divided horizontally, so this one divides vertically — same
   information density, deliberately different form.

   This is also where --slate earns its place: a third ground between the white
   page and the near-black section, so the palette has a middle register. */
/* Theme tokens, not the fixed dark pair. At t = 1 these resolve to exactly
   --d-ink and --d-ink-2, so the settled section is unchanged — but the h2
   beside this p already rode --page-text (it comes from .section-head in
   styles.css), so the standfirst was the one thing in the heading that
   snapped to white while the page behind it was still on its way down. Same
   failure the nav's old .navbar--dark class had, at a smaller scale. */
.stack { color: var(--page-text); }
.stack .section-head p { color: var(--page-text-2); }

/* THREE across, not six. Six columns inside a 1176px wrap gave each stack a
   ~165px measure, which is where the whole band stopped being readable: a
   six-word sentence broke over four lines and "PHP / Symfony" wrapped mid-name.
   Three columns over two rows is the same band, the same frame and the same
   six segments, with twice the width for each — and it is what lets the tags
   below become chips that wrap instead of a cramped vertical list. */
.stack-rail {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  background: var(--slate);
  border: 1px solid var(--d-line-soft);
  border-radius: var(--r-lg);
  overflow: hidden;
}
.rail-seg {
  position: relative;
  padding: clamp(var(--s-6), 2.2vw, var(--s-8)) clamp(var(--s-5), 1.8vw, var(--s-7));
  transition: background .35s var(--ease);
}
/* The vertical divider is a pseudo-element, not a border, so data-motion="band"
   can drop it in from the top. It is placed per breakpoint below exactly where
   the border used to be — display:none stands in for `border-left: 0`.
   --d-line, not a fourth hand-written rgba: the same 0.09/0.10 value was
   restated in five places and the token is already exactly this rule. */
.rail-seg::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 1px;
  background: var(--d-line);
  transform-origin: top center;
}
/* The first segment of each row has nothing to its left. :first-child is gone:
   it was a (0,1,1) selector that the :nth-child rules below outrank, so at two
   columns segment 1 would have kept a divider it no longer needed. */
.rail-seg:nth-child(3n+1)::before { display: none; }
.rail-seg:nth-child(n+4) { border-top: 1px solid var(--d-line); }
.rail-seg:hover { background: rgba(255, 255, 255, 0.05); }

/* --ls-mono, like every other uppercase label on the site. The page had seven
   different trackings on the same kind of label (.1, .12, .03, .02, .06, .08)
   and none of them were the token.
   The short accent bar above it is a 2px --accent-line rule cut to a stub: on
   a dark ground a tracked 11px label was the only thing marking where one
   segment started, and it was doing it at the same weight as the body copy
   beneath it. One more size step too — 11px was the size of the tags, so the
   heading and its contents read as one rank. */
.rail-label {
  position: relative; display: block; padding-top: var(--s-4);
  font-family: var(--font-mono); font-size: var(--t-xs);
  letter-spacing: var(--ls-mono); text-transform: uppercase; font-weight: 500;
  color: var(--accent);
}
.rail-label::before {
  content: ""; position: absolute; left: 0; top: 0; width: var(--s-6); height: 2px;
  background: var(--accent);
}
.rail-desc { margin-top: var(--s-3); font-size: var(--t-sm); line-height: 1.55; color: var(--d-ink-2); max-width: 34ch; }
/* Chips that wrap, not a column of words. --r-sm: these are the smallest
   control on the page, and the band around them is the large surface. */
.rail-tags {
  list-style: none; margin: var(--s-5) 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: var(--s-2);
}
.rail-tags li {
  font-family: var(--font-mono); font-size: var(--t-s); letter-spacing: .02em;
  color: var(--d-ink);
  padding: var(--s-1) var(--s-3);
  border: 1px solid var(--d-line); border-radius: var(--r-sm);
  background: rgba(255, 255, 255, 0.05);
  transition: border-color .3s var(--ease), background .3s var(--ease);
}
.rail-seg:hover .rail-tags li { border-color: var(--accent-line); background: var(--accent-soft); }

/* ---------------- Services — technology constellation ----------
   A hub and two concentric rings of tech marks. Positions come from app.js
   (techOrbit): every mark sits at an exact, even position on its band.

   Owner, 2026-09-23: "don't make the icons float around, align them
   perfectly with the circles and remove the circles". So the drift is gone,
   and so is the dark badge each mark sat in; what is left of the circles is
   the ring the marks are strung on, as one hairline each. The square is what
   keeps the rings round: the old 6/5 box with percent positions drew ellipses.

   Hairlines on --page-line, like every other rule on a page that ends in the
   dark CTA. At t = 1 it is exactly --d-line, which the hub used before. */
.tech-orbit {
  position: relative;
  list-style: none;
  width: 100%;
  max-width: 780px;
  margin: 0 auto var(--s-16);
  aspect-ratio: 1;
}
/* Later the same day the owner took the rings away too, for a sphere: see
   techOrbit in app.js. No circles are drawn at all now - the depth is in the
   marks themselves, big and bright at the front, small and faint at the rim. */

/* The hub is just the wordmark and the count at the centre of the sphere. */
.to-hub {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--s-2);
  z-index: 2;
}
/* the AtraX mark + wordmark at the centre of the sphere, no caption */
.to-logo { width: clamp(130px, 20vw, 200px); height: auto; display: block; }

/* A node is only a hit area centred on its point of the ring; nothing about
   it is drawn. The core stack reads a size up from the outer ring, and every
   mark on one ring is the same size. */
.to-node {
  position: absolute;
  transform: translate(-50%, -50%);
  display: grid; place-items: center;
  aspect-ratio: 1;
}
/* Three depths. Size and strength fall with the band's cos(θ): the front band
   is the core stack, full size and full strength; the rim is small and dim. */
.to-t1 { width: clamp(46px, 7vw, 66px); --depth: 1; }
.to-t2 { width: clamp(36px, 5.2vw, 50px); --depth: .78; }
.to-t3 { width: clamp(26px, 3.6vw, 36px); --depth: .5; }

/* The marks are SVG masks, so their colour comes from here: --tint carries the
   brand colour (set per node in app.js). Marks whose brand colour is black
   are tinted white in the script, so every mark clears the dark ground bare.
   Held a touch under full strength; hover brings one up to full. */
.to-ico {
  width: 70%; height: 70%;
  background-color: var(--tint, var(--page-text-2));
  opacity: var(--depth, .9);
  -webkit-mask: var(--ico) center / contain no-repeat;
  mask: var(--ico) center / contain no-repeat;
  transition: opacity .3s var(--ease), transform .3s var(--ease);
}
.to-node:hover { z-index: 3; }
.to-node:hover .to-ico { opacity: 1; transform: scale(1.12); }

.to-name {
  position: absolute; bottom: calc(100% + var(--s-2)); left: 50%; transform: translateX(-50%);
  white-space: nowrap; pointer-events: none;
  font-family: var(--font-mono); font-size: var(--t-3xs); letter-spacing: var(--ls-mono); text-transform: uppercase;
  color: #fff; background: rgba(12, 12, 15, 0.92); border: 1px solid var(--d-line);
  border-radius: var(--r-sm); padding: 5px 10px;
  opacity: 0; transition: opacity .22s ease, transform .22s ease;
}
.to-node:hover .to-name { opacity: 1; transform: translateX(-50%) translateY(-2px); }

/* ---------------- Services — startup stages ----------------
   A progression, not three cards. The stages ARE sequential (seed → growth →
   expansion), so they get a timeline: one rule across the top, a node per
   stage, text hanging beneath. No boxes — which is what keeps this page from
   having two near-identical card rows. */
.stage-track {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(var(--s-6), 3vw, var(--s-12));
  position: relative;
}
.stage-track::before {
  content: ""; position: absolute; left: 0; right: 0; top: 6px; height: 1px;
  background: linear-gradient(to right, var(--line), var(--line) 78%, transparent);
  transform-origin: left center;   /* data-motion="track" draws it from here */
}
.stage { position: relative; padding-top: var(--s-8); }
.stage::before {
  content: ""; position: absolute; left: 0; top: 0; width: 13px; height: 13px;
  border-radius: 50%; background: var(--bg); border: 1px solid var(--ink-4);
  transition: border-color .3s var(--ease), box-shadow .3s var(--ease), background .3s var(--ease);
}
.stage:hover::before { background: var(--accent); border-color: var(--accent); box-shadow: 0 0 0 5px var(--accent-soft); }
.stage-tag {
  font-family: var(--font-mono); font-size: var(--t-2xs); letter-spacing: var(--ls-mono);
  text-transform: uppercase; color: var(--accent);
}
/* One rank, one size. A stage, a process step and an engagement model are all
   the same thing structurally — a named item inside a section — and they were
   set at --t-h4 (22px), --t-lg (17px) and --t-xl (19px) respectively, so three
   identical ranks read as three different ones. --t-xl / -0.02em is what
   .wwd h3 uses on the homepage for exactly this, and all three now match it. */
.stage h3 { margin-top: var(--s-3); font-size: var(--t-xl); font-weight: 700; letter-spacing: -0.02em; text-wrap: balance; }
.stage p { margin-top: var(--s-3); font-size: var(--t-sm); line-height: 1.55; color: var(--ink-2); max-width: 40ch; }

/* ---------------- Services — process layout ----------------
   From the S1 mockup: the head and four working principles on the left, the
   rail on the right. The rail is the taller column by a long way, so the lead
   is sticky rather than centred — centring it would leave the heading floating
   opposite step 3 with nothing above it. */
.proc-layout {
  display: grid;
  grid-template-columns: minmax(0, 0.82fr) minmax(0, 1.18fr);
  gap: clamp(var(--s-7), 4vw, var(--s-14));
  align-items: start;
}
.proc-lead { position: sticky; top: calc(var(--nav-h, 76px) + var(--s-8)); }
.proc-lead .section-head { margin-bottom: clamp(var(--s-7), 3vw, var(--s-10)); }
.proc-lead .section-head p { font-size: var(--t-lg); }

.proc-vals { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-5); }
.pv { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--s-4); align-items: start; }
.pv-ico {
  width: 36px; height: 36px; display: grid; place-items: center;
  border-radius: var(--r-sm); color: var(--accent);
  background: var(--accent-soft);
  background: color-mix(in srgb, var(--accent) 11%, var(--bg));
}
.pv-ico svg { width: 19px; height: 19px; display: block; }
.pv-body h3 { font-size: var(--t-md); font-weight: 700; letter-spacing: -0.01em; line-height: 1.3; }
.pv-body p { margin-top: 2px; font-size: var(--t-sm); line-height: 1.5; color: var(--ink-2); }

/* ---------------- Services — process rail ----------------
   A connected rail, not a ruled table. One spine down the left, a node per
   step sitting ON it, and a bordered card beside each node — which is the one
   place on this page where the accent stops being a colour for type and
   becomes a surface: each card opens with a tinted accent tile.

   What it replaces was six rows inside one frame, divided by 1px gaps. That
   read as a specification table, and the section is a sequence.

   --node is the diameter of the node AND the width of the column it sits in,
   so the spine, the ring and the glyph are all positioned off one number and
   a breakpoint only has to move that one. It resolves to a spacing token at
   every size. */
.rail {
  --node: var(--s-12);
  list-style: none; margin: 0; padding: 0;
  position: relative;
  display: grid; gap: var(--s-4);
}
/* The spine. Anchored to the FIRST node's centre and run to the foot of the
   list, where a gradient takes it out: the last node's centre is not a length
   this element can express, and a line that dissolves past the final branch
   says "and on" better than one that stops dead. --accent-line, not --line —
   this is the one rule on the page that is the process itself. */
.rail::before {
  content: ""; position: absolute;
  left: calc(var(--node) / 2); margin-left: -0.5px; width: 1px;
  top: calc(var(--node) / 2); bottom: 0;
  background: linear-gradient(to bottom, var(--accent-line), var(--accent-line) 86%, transparent);
  transform-origin: top center;
}
/* data-motion="track" draws its rule across; this one runs down. Overriding
   the axis on the component rather than in styles.css is what .stage-track
   already does when it stacks below 860px — the component owns where its rule
   sits and therefore which way it grows. Ties with the [data-motion="track"]
   rules on specificity and wins on order. */
.rail:not(.is-in)::before { transform: scaleY(0); }
.rail.is-in::before { animation-name: motion-drop; }

.rail-step {
  position: relative;
  display: grid; grid-template-columns: var(--node) minmax(0, 1fr);
  column-gap: var(--s-5);
}
/* The node ring. It is the li's ::before because that is the element
   data-motion="track" pops as the draw reaches it. var(--bg), not #fff: the
   ring has to be opaque to cut the spine, and it is the one thing here that
   could not follow if the ground token ever moved. */
.rail-step::before {
  content: ""; position: absolute; left: 0; top: 0;
  width: var(--node); height: var(--node);
  border-radius: 50%;
  background: var(--bg); border: 1px solid var(--accent-line);
  transition: border-color .4s var(--ease), box-shadow .4s var(--ease);
}
.rail-step:hover::before { border-color: var(--accent); box-shadow: 0 0 0 5px var(--accent-soft); }
/* The number sits over the ring rather than inside it: track animates the ring
   (::before) and the number (a child) on two different curves, so they cannot
   be the same box. text-indent gives back the trailing space --ls-mono hangs
   off the last glyph, which otherwise pushes "01" left of centre. */
.rail-n {
  position: absolute; left: 0; top: 0;
  width: var(--node); height: var(--node);
  display: grid; place-items: center;
  font-family: var(--font-mono); font-size: var(--t-2xs);
  letter-spacing: var(--ls-mono); text-indent: var(--ls-mono);
  color: var(--accent);
}
.rail-n svg { width: var(--s-5); height: var(--s-5); display: block; text-indent: 0; }

/* grid-column is explicit because .rail-n is absolutely positioned and so takes
   no grid slot — auto-placement would drop the card into the node's column.
   --line-soft and .4s on hover are the .wwd:hover pair, so a row of steps and a
   row of services answer the cursor identically. */
.rail-card {
  grid-column: 2;
  background: var(--bg);
  border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: var(--s-5) var(--s-6);
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  column-gap: var(--s-5); align-items: start;
  transition: background .4s var(--ease), border-color .4s var(--ease);
}
.rail-step:hover .rail-card { background: var(--line-soft); border-color: var(--accent-line); }

/* The tinted tile. The tint is DERIVED from --accent, not a second red picked
   by eye: --accent-soft is the token pair's own 12% wash and carries browsers
   without color-mix; the color-mix above it mixes --accent into the ground
   token, so the tile follows both the accent and the page if either moves.
   Mixed with var(--bg) rather than `transparent` on purpose — mixing into
   transparent premultiplies alpha, which is the same trap the theme tween
   avoids by lerping channels in calc(). --r-lg: this is a surface, not a chip. */
.rail-ico {
  width: var(--s-12); height: var(--s-12);
  display: grid; place-items: center;
  border-radius: var(--r-lg);
  color: var(--accent);
  background: var(--accent-soft);
  background: color-mix(in srgb, var(--accent) 11%, var(--bg));
  transition: background .4s var(--ease);
}
.rail-step:hover .rail-ico { background: color-mix(in srgb, var(--accent) 18%, var(--bg)); }
.rail-ico svg { width: var(--s-6); height: var(--s-6); display: block; }

.rail-copy h3 { font-size: var(--t-xl); font-weight: 700; letter-spacing: -0.02em; }
.rail-copy p { margin-top: var(--s-2); font-size: var(--t-base); line-height: 1.6; color: var(--ink-2); max-width: 62ch; }

/* The phase label, hung at the card's right edge. Plain tracked type, no chip:
   the card already has a frame and a tile, and a third enclosed shape inside
   it is one too many. */
.rail-cat {
  justify-self: end; padding-top: var(--s-1);
  font-family: var(--font-mono); font-size: var(--t-3xs);
  letter-spacing: var(--ls-mono); text-transform: uppercase; font-weight: 500;
  color: var(--ink-4); white-space: nowrap;
}

/* The fork: the first four steps are shared, these two are alternatives to
   each other rather than the next thing that happens. So their nodes carry a
   accent ring, frame and tag, and the EITHER / OR tags say they are
   alternatives rather than steps five and six.

   The nodes stay numbered. They carried glyphs for a while - a line the arrow
   came to rest on, and the infinity mark - but the first of those read as a
   download icon, and the two filled rings broke a column that is otherwise six
   identical outlines. The owner's mockup numbers every node and keeps a single
   glyph for the branch that loops, which is what this does now. */
.rail-fork::before { border-color: var(--accent-line); }
.rail-fork .rail-card { border-color: var(--accent-line); }
.rail-fork .rail-cat { color: var(--accent); }
.rail-fork h3 em { font-style: normal; font-weight: 500; color: var(--ink-4); font-size: var(--t-sm); letter-spacing: 0; }

/* ---------------- Work — case studies ----------------
   Same content type as the homepage's Selected work, so the cards read with
   its card treatment rather than a second one: .work-name's display family at
   600 and -0.012em, .work-desc's --t-sm/1.55 in --ink-2, a mono label on the
   site-wide --ls-mono rather than a one-off .08em, and the accent-on-hover the
   homepage gives .work-name. Nothing is borrowed from the homepage's LAYOUT —
   this stays a nine-up even grid, because the asymmetric 1.41fr arrangement
   only works with four cards hand-placed into named slots. */
/* .case-filter / .cf-btn / .case-empty are NOT work-only despite the names —
   blog.html's topic filter reuses all three, so they are left as-is here. */
/* SHARED by work.html and blog.html despite the case- prefix — both use this
   row for their filter. Neither page's owner may change it alone.

   The chips already wear .btn-ghost's shell (transparent, --line, --r-sm), so
   putting their TYPE on .btn's removes the site's only second control idiom.
   --font-mono and --font-display both resolve to Space Grotesk, so the real
   delta is one pixel of size, slightly tighter tracking, and weight 500. */
.case-filter { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-bottom: var(--s-10); }
.cf-btn {
  font-family: var(--font-display); font-size: var(--t-xs); font-weight: 500;
  letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-2); background: transparent; border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: var(--s-2) var(--s-4); cursor: pointer; transition: color .2s, border-color .2s, background .2s;
}
.cf-btn:hover { color: var(--ink); border-color: var(--ink); }
.cf-btn.is-on { color: #fff; background: var(--ink); border-color: var(--ink); }

/* Split rather than the homepage's flat 40px, because the shapes differ: three
   columns here to its two, so columns take less (32px) to avoid starving the
   cards of width. Rows take more (56px) because a card here ends in three
   stacked lines of meta, not the homepage's single flex row — an equal gap
   would leave less air under the paragraph than the homepage leaves under
   its one-liner. */
.case-grid { display: grid; grid-template-columns: repeat(3, 1fr); column-gap: var(--s-8); row-gap: var(--s-14); }
.case { display: flex; flex-direction: column; }
.case[hidden] { display: none; }
.case-shot {
  border-radius: var(--r-lg); overflow: hidden; border: 1px solid var(--line);
  background: var(--bg-muted); aspect-ratio: 1 / 1;
}
/* The homepage scales its whole .scene 1.04 over .8s; the same gesture, applied
   to the image because these frames are full-bleed crops with no scene behind
   them. 1.045/.6s was a touch faster and larger than anything else on the site. */
.case-shot img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .8s cubic-bezier(.2,.7,.2,1); }
.case:hover .case-shot img { transform: scale(1.04); }
.case-meta { margin-top: var(--s-5); }
.case-cat { font-family: var(--font-mono); font-size: var(--t-2xs); letter-spacing: var(--ls-mono); text-transform: uppercase; color: var(--ink-4); }
/* One step down the scale from the homepage's --t-h2s, not a match: three cards
   share a row here where the homepage gives one card 58% of the width, so the
   same 40px cap would land on a ~370px card and wrap names like "Conversation
   platform". --t-h3 keeps the ratio of name to card width roughly where the
   homepage puts it. */
.case-meta h3 {
  margin-top: var(--s-2); font-family: var(--font-display); font-size: var(--t-h3);
  font-weight: 600; line-height: 1.08; letter-spacing: -0.012em;
  transition: color .25s var(--ease);
}
.case:hover .case-meta h3 { color: var(--accent); }
.case-meta p { margin-top: var(--s-2); font-size: var(--t-sm); line-height: 1.55; color: var(--ink-2); }

/* ---------------- Home — real screenshots inside the existing work cards ----
   The homepage cards keep their original `.work-thumb > .scene` treatment
   (tinted ground, 38px inset, scale-on-hover). This just lets a real
   screenshot sit where the CSS mockups used to, framed the same way
   `.mock` frames them in sections.css. */
.work-thumb .scene .shot {
  max-width: min(520px, 100%);
  max-height: 100%;
  width: auto; height: auto;
  object-fit: contain;
  display: block;
  background: rgba(255, 255, 255, 0.035);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
}
.work-thumb .scene.dark .shot { border-color: rgba(255,255,255,.08); }
/* The tall card stacks two shots in a column, so each takes the full width and
   lets its own aspect ratio set its height — max-height would fight the stack. */
.wk-tall .work-thumb .scene .shot { max-width: 100%; max-height: none; width: 100%; height: auto; min-height: 0; }
/* the full-width band closes the section, so its shot runs edge to edge
   rather than floating small in the middle of a 21:8 frame */
.wk-wide .work-thumb .scene { padding: 0; }
.wk-wide .work-thumb .shot {
  max-width: 100%; max-height: none; width: 100%; height: 100%;
  object-fit: cover; object-position: center 30%;
  border: 0; border-radius: 0; box-shadow: none;
}
/* A square card image with no screenshot inside it (Siemplify, Seraphic) fills
   its frame instead of floating framed on a tint: a floating 740 square in a
   3/2 slot is a small tile with two empty margins. */
.work-thumb .scene.fill { padding: 0; }
.work-thumb .scene.fill .shot {
  max-width: 100%; max-height: none; width: 100%; height: 100%;
  object-fit: cover; object-position: center;
  border: 0; border-radius: 0; box-shadow: none;
}
/* .case-meta p moved up to its siblings in the case-studies block above —
   it had drifted in here behind the homepage screenshot rules. */
.case-empty { margin-top: 28px; color: var(--ink-4); font-size: var(--t-base); }

/* ---------------- Case study detail ---------------- */
.cs-back {
  display: flex; width: fit-content; align-items: center; gap: 7px; margin-bottom: 26px;
  font-family: var(--font-mono); font-size: var(--t-2xs); letter-spacing: .06em; text-transform: uppercase;
  color: var(--page-text-3); transition: color .2s, gap .2s;
}
.cs-back:hover { color: var(--page-text); gap: 11px; }

/* No box-shadow. --shadow-lg has resolved to `none` since the site went flat,
   so the declaration was claiming a lift this frame never had - the homepage
   frames the same screenshots (.pm-media img) on border + --bg-muted alone.
   Margin is .post-hero-shot's clamp rather than a flat 64px: case studies and
   articles now open with the same 72px hero, and the drop from the opening shot
   to the first heading should breathe the same way. 64px was right at 1240 and
   far too much on a phone. */
/* ONE frame across all thirty. The hero shots were never a set: the nine
   original gallery images run from 2.61 (lava, ultra-wide) to 0.48 (remy, a
   tall phone screenshot), and the twenty-one from atrax.al are all square. With
   width:100% / height:auto that ratio WAS the layout - remy opened on 1382px of
   screenshot in a 664px column, lava on a 254px letterbox, and no two case
   studies began the same way.

   3/2 because four of the nine originals already sit within 0.05 of it.
   `contain`, not `cover`: the set mixes centred logos with wide screenshots and
   one portrait, and any crop tight enough to suit the screenshots beheads the
   logos. Contained on the muted ground the frame reads as a plate, which is
   what the border and the radius were already drawing. */
.cs-hero-shot {
  border-radius: var(--r-lg); overflow: hidden; border: 1px solid var(--page-line);
  background: var(--page-surface); margin-bottom: clamp(var(--s-8), 4vw, var(--s-14));
  aspect-ratio: 3 / 2;
}
.cs-hero-shot img { display: block; width: 100%; height: 100%; object-fit: contain; }

.cs-layout { display: grid; grid-template-columns: 1fr 300px; gap: clamp(32px, 5vw, 72px); align-items: start; }
/* Measured, not asserted: this renders 664px at 17px, i.e. 64 characters a
   line. The articles' own column is 630px / 61 characters. Two templates three
   characters apart, both well inside the comfortable band - so they are left
   different rather than forced together. (An earlier pass estimated this at 81
   characters and called it too wide; that assumed a wider `ch` than Manrope
   actually has. 62ch was also tried, and is a lede width, not a body width.) */
.cs-main { max-width: 68ch; }

.cs-block + .cs-block { margin-top: var(--s-12); }
/* Was a mono 12px uppercase micro-label in --ink-4. That works for "The goal"
   and falls apart on "Modular, and independently scalable" - six of the nine
   headings are sentences, not labels, and tracked uppercase cannot carry one.
   This is the site's display-heading formula (--font-display / 600 / -0.012em,
   as .section-head h2 sets it) one step down the scale, identical to .prose h2
   so the two long-form templates read as one room. The homepage modal's
   .pm-block h3 (--t-h4 / 700) was the other candidate and was rejected: its
   column is roughly half the width of this one. */
.cs-block h2 {
  font-family: var(--font-display); font-size: var(--t-h3);
  font-weight: 600; letter-spacing: -0.012em; line-height: 1.2; color: var(--page-text);
}
/* 1.72 is .prose p's leading and --s-4 its h2 + p gap. The paragraph GAP is
   deliberately not shared: .prose p + p is --s-7, because an article runs ten
   unbroken paragraphs while every .cs-block here holds exactly one. The old
   1.68/14px was a near-miss off the scale, not a considered difference. */
.cs-block p { margin-top: var(--s-4); font-size: var(--t-lg); line-height: 1.72; color: var(--page-text-2); }

/* --s-6 between shots and a 12px caption offset are what .pm-media uses for
   these very screenshots on the homepage; 28px and 11px were off the scale. */
.cs-gallery { margin-top: var(--s-12); display: grid; gap: var(--s-6); }
.cs-fig { margin: 0; }
.cs-fig img {
  display: block; width: 100%; height: auto;
  border-radius: var(--r-lg); border: 1px solid var(--page-line); background: var(--page-surface);
}
.cs-fig figcaption {
  margin-top: var(--s-3); font-family: var(--font-mono); font-size: var(--t-2xs);
  letter-spacing: .06em; text-transform: uppercase; color: var(--page-text-3);
}

.cs-aside { display: grid; gap: 20px; position: sticky; top: 96px; }
.cs-stack { margin-top: 16px; display: grid; gap: 14px; }
.cs-stk dt { font-size: var(--t-xs); font-weight: 600; color: var(--page-text-3); }
.cs-stk dd { margin: 4px 0 0; font-size: var(--t-sm); line-height: 1.5; color: var(--page-text-2); }
.cs-aside .bs-link { margin-top: 20px; display: inline-flex; }

.cs-nav { padding-top: 0; }
/* SHARED by the nine case studies and the eleven articles, so it belongs to
   neither. Both page owners flagged the same three faults and correctly left
   them rather than make the two templates diverge on a common component.

   .cs-nav-name had NO font-family, so it inherited Manrope from body — the only
   title-like element on the site not set in Space Grotesk, and the one real
   theme defect in this block. It also carried the 700 / -0.02em card pair; the
   site's display formula is 600 / -0.012em.

   The hover box-shadow was dead: --shadow-md has resolved to `none` since the
   site went flat, so it claimed a lift this frame never had. */
.cs-nav-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-5); border-top: 1px solid var(--page-line); padding-top: var(--s-10); }
.cs-nav-item {
  display: flex; flex-direction: column; gap: var(--s-2); padding: var(--s-6) var(--s-7);
  border: 1px solid var(--page-line); border-radius: var(--r-lg);
  transition: border-color .3s, transform .3s;
}
.cs-nav-item:hover { border-color: var(--page-text); transform: translateY(-3px); }
.cs-nav-next { text-align: right; align-items: flex-end; }
.cs-nav-lbl { font-family: var(--font-mono); font-size: var(--t-2xs); letter-spacing: var(--ls-mono); text-transform: uppercase; color: var(--page-text-3); }
.cs-nav-name { font-family: var(--font-display); font-size: var(--t-h4); font-weight: 600; letter-spacing: -0.012em; color: var(--page-text); }
.cs-nav-item:hover .cs-nav-name { color: var(--accent); }

/* ---------------- Contact ----------------
   The 36-field brief form that lived here was dropped on 2026-09-23 (see the
   comment in contact.html). What is left is the address, set large enough to
   read across a room, and a copy button for the visitors a mailto: link does
   nothing for. --page-* tokens because this page ends in the dark CTA. */
.brief-layout { display: grid; grid-template-columns: 1fr 320px; gap: clamp(28px, 4vw, 64px); align-items: start; }

.ct-book { padding-bottom: var(--s-10); margin-bottom: var(--s-10); border-bottom: 1px solid var(--page-line); }
.ct-book-t {
  margin: var(--s-3) 0 var(--s-6); font-family: var(--font-display); font-size: var(--t-xl);
  font-weight: 600; letter-spacing: -0.02em; line-height: 1.25; color: var(--page-text);
}
.ct-lbl {
  display: block; font-family: var(--font-mono); font-size: var(--t-xs); font-weight: 500;
  letter-spacing: var(--ls-mono); text-transform: uppercase; color: var(--page-text-3);
}
/* The display face at the h2 step: it is the page's one piece of content.
   overflow-wrap so the address breaks rather than widening a phone. */
.ct-mail {
  display: inline-block; margin-top: var(--s-3); font-family: var(--font-display);
  font-size: var(--t-h2); font-weight: 600; letter-spacing: -0.03em; line-height: 1.1;
  color: var(--page-text); border-bottom: 1px solid var(--page-line); overflow-wrap: anywhere;
  transition: color .2s, border-color .2s;
}
.ct-mail:hover { color: var(--accent); border-color: var(--accent); }
.ct-acts { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3); margin-top: var(--s-8); }
.ct-copied { font-size: var(--t-sm); color: var(--page-text-2); }
.ct-hint { margin-top: var(--s-8); max-width: 52ch; font-size: var(--t-md); line-height: 1.6; color: var(--page-text-2); }

.brief-aside { display: grid; gap: var(--s-5); position: sticky; top: 96px; }
.ba-card { border: 1px solid var(--page-line); border-radius: var(--r-lg); padding: 24px 22px 26px; background: var(--page-bg); }
.ba-card h3 { font-size: var(--t-s); font-family: var(--font-mono); letter-spacing: .1em; text-transform: uppercase; color: var(--page-text-3); font-weight: 500; }
/* .wwd li's metrics exactly — same object, a bulleted list inside a bordered
   card. The dot was 6px accent, four of them stacked; the homepage keeps its
   dots at --ink-4 and spends the accent on hover only, and this card has no
   hover to spend it on. */
.ba-list { list-style: none; margin-top: var(--s-4); display: grid; gap: var(--s-3); }
.ba-list li { position: relative; padding-left: 16px; font-size: var(--t-s); line-height: 1.45; color: var(--ink-2); }
.ba-list li::before { content: ""; position: absolute; left: 0; top: 7px; width: 5px; height: 5px; border-radius: 50%; background: var(--ink-4); }
.ba-loc { margin-top: var(--s-3); font-size: var(--t-sm); line-height: 1.6; color: var(--ink-4); }

/* ---------------- Footer plain-text lines ---------------- */
.foot-col span { display: block; font-size: var(--t-sm); color: rgba(255,255,255,0.55); padding: 5px 0; }

/* ---------------- Responsive ---------------- */
@media (max-width: 960px) {
  /* stacked, a sticky lead would pin the heading over the rail as it scrolls */
  .proc-layout { grid-template-columns: 1fr; }
  .proc-lead { position: static; }
  .proc-vals { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .proc-vals { grid-template-columns: 1fr; }
}
@media (max-width: 1080px) {
  .case-grid, .ins-feature { grid-template-columns: repeat(2, 1fr); }
  .stage-track { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-5); }
  /* .stack-rail is 3 across from the start now, so the old 1080 step is gone
     from here and the ladder is 3 → 2 (860) → 1 (660). */
  .val-grid { gap: var(--s-4); }
  .brief-layout, .cs-layout { grid-template-columns: 1fr; }
  .brief-aside { position: static; grid-template-columns: 1fr 1fr; }
  .cs-aside { position: static; grid-template-columns: 1fr 1fr; margin-top: 44px; }
  /* .cs-main uncaps so the gallery can use the full stacked width, but that
     also uncapped the prose: at 1080 the paragraphs ran the whole 1016px wrap,
     about 120 characters a line, which is the worst measure anywhere on the
     site and it appeared exactly where the layout was supposed to get easier to
     read. Carrying the 68ch down onto .cs-block keeps the text at its own
     measure and still lets the shots span the wider column. */
  .cs-main { max-width: none; }
  .cs-block { max-width: 68ch; }
}
@media (max-width: 860px) {
  /* the timeline rule only reads as a sequence horizontally — stack it */
  .stage-track { grid-template-columns: 1fr; gap: 0; }
  .stage-track::before { top: 6px; bottom: 6px; left: 6px; right: auto; width: 1px; height: auto;
    background: linear-gradient(to bottom, var(--line), var(--line) 78%, transparent); }
  /* Stacked, the rule runs top-to-bottom — so must the draw. Overriding the
     axis here rather than in styles.css keeps the effect generic: the component
     owns where its rule sits, and therefore which way it grows. These tie on
     specificity with the [data-motion="track"] rules and win on order. */
  .stage-track::before { transform-origin: top center; }
  .stage-track:not(.is-in)::before { transform: scaleY(0); }
  .stage-track.is-in::before { animation-name: motion-drop; }
  .stage { padding: 0 0 var(--s-7) var(--s-8); }
  .stage:last-child { padding-bottom: 0; }
  .page-hero { padding: 128px 0 64px; }
  .story-grid { grid-template-columns: 1fr; }
  .story-photo { order: -1; }
  .svc-block { grid-template-columns: 1fr; gap: 14px; }
  .svc-n { padding-top: 0; }
  /* The rail keeps its spine on a tablet — it is the whole idea of the
     section. What gives way is the card's third column: the phase tag moves
     off the right edge and sits above the heading, so the copy gets the full
     remaining width instead of splitting it with a label. */
  .rail-card { grid-template-columns: auto minmax(0, 1fr); row-gap: var(--s-2); }
  .rail-ico { grid-row: 1 / span 2; }
  .rail-cat { grid-column: 2; grid-row: 1; justify-self: start; padding-top: 0; }
  .rail-copy { grid-column: 2; grid-row: 2; }
  /* Stack band: 3 → 2. The vertical rule moves to every even segment and the
     horizontal rule to every row after the first. */
  .stack-rail { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .rail-seg:nth-child(3n+1)::before { display: block; }
  .rail-seg:nth-child(odd)::before { display: none; }
  .rail-seg:nth-child(n+3) { border-top: 1px solid var(--d-line); }
}
@media (max-width: 660px) {
  .case-grid, .ins-feature { grid-template-columns: 1fr; }
  .lead-row { grid-template-columns: 1fr; gap: var(--s-7); }
  .val-grid { grid-template-columns: 1fr; }
  .val-result { grid-template-columns: 1fr; }
  /* One segment per row on a phone. The descendant selector is what makes the
     ::before go away: it outranks the two :nth-child rules the 860 block sets,
     which a bare .rail-seg::before would not. */
  .stack-rail { grid-template-columns: minmax(0, 1fr); }
  .stack-rail .rail-seg::before { display: none; }
  .rail-seg:nth-child(n+2) { border-top: 1px solid var(--d-line); }
  .rail-desc { max-width: none; }
  /* Process rail on a phone: one number moves the node, the spine and the
     glyph together. The copy drops to its own full-width row under the tile,
     because at 335px of wrap a 40px tile beside it leaves a 20-character
     measure — the same failure the case studies had at 1080. */
  .rail { --node: var(--s-10); gap: var(--s-3); }
  .rail-step { column-gap: var(--s-3); }
  .rail-n { font-size: var(--t-3xs); }
  .rail-n svg { width: var(--s-4); height: var(--s-4); }
  .rail-card { padding: var(--s-4); grid-template-columns: auto minmax(0, 1fr); row-gap: var(--s-3); }
  .rail-ico { width: var(--s-10); height: var(--s-10); grid-row: 1; }
  .rail-ico svg { width: var(--s-5); height: var(--s-5); }
  .rail-cat { grid-column: 2; grid-row: 1; justify-self: end; align-self: center; }
  .rail-copy { grid-column: 1 / -1; grid-row: 2; }
  /* The sphere on a phone too (owner, 2026-09-24, over the flat grid the
     earlier pass used). Same three bands, same positions - they are
     percentages of a square, so it simply scales to the column. At 375 the
     square is ~335px: the bands sit ~91/138/154px out and every mark has
     ~80px of arc to itself, so the sizes step down to 44/34/24 (less below
     ~370px) and the bands use slightly different latitudes (app.js). The mark
     at the centre drops to 104px to stay clear of the front band. Names
     stay visually hidden (there is no hover on a phone) but in the tree. */
  .tech-orbit { margin-bottom: var(--s-10); }
  .to-t1 { width: min(44px, 12vw); }
  .to-t2 { width: min(34px, 9.4vw); }
  .to-t3 { width: min(24px, 6.6vw); }
  .to-logo { width: min(104px, 27vw); }
  .to-name {
    left: 0; top: 0; bottom: auto; transform: none; opacity: 1;
    width: 1px; height: 1px; padding: 0; border: 0; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%);
  }
  .brief-aside, .cs-aside { grid-template-columns: 1fr; }
  .cs-nav-grid { grid-template-columns: 1fr; }
  .cs-nav-next { text-align: left; align-items: flex-start; }
  .ins-list a { grid-template-columns: 44px 1fr; row-gap: 4px; }
  .ins-list img { width: 44px; height: 34px; }
  .il-d { grid-column: 2; }
}

/* ---------------- Journal — index ----------------
   Layout borrowed from atrax-website.vercel.app/blog: a topic filter, one
   featured post across two columns, then a grid of the rest. Their cards use
   28px radii; these use the site's own square corners, because a rounded
   blog bolted onto a squared site reads as a different website. */

/* Micro-label spec, same as .eyebrow and .work-year: uppercase Space Grotesk
   at --ls-mono. It was --font-display at .06em, the one tracking nothing else
   on the site uses, which left it reading as a stray type system directly
   under a hero that had just been matched to the homepage. */
.blog-count {
  margin-top: var(--s-5);
  font-family: var(--font-mono); font-size: var(--t-xs); font-weight: 500;
  letter-spacing: var(--ls-mono); text-transform: uppercase; color: var(--ink-4);
  /* Tracking hangs a trailing space off the last glyph, so inside .ph-centre
     the glyphs sit half a step (0.07em) left of true centre. A centred line
     splits its indent, so indenting by the full --ls-mono shifts it back by
     exactly that half. Needed only because this label is centred — .work-year
     and .post-topic are flush left and carry no correction. */
  text-indent: var(--ls-mono);
}
.blog-count b { color: var(--ink); font-weight: 600; }
.blog-count i { font-style: normal; padding: 0 var(--s-1); opacity: .5; }

/* The filter row IS this section's head — there is no h2 above it — so it
   takes .section-head's 56px of clearance rather than the 34px .case-filter
   gets on work.html, where an h2 has already opened the section. Scoped, so
   work.html is untouched. */
.blog-index .case-filter { margin-bottom: var(--s-14); }

/* Wider than the 40px inside .post-grid, so the featured post reads as its own
   band rather than as a first row that happens to be bigger. */
.post-list { display: grid; gap: clamp(var(--s-10), 4vw, var(--s-16)); }

.post { display: block; }
.post[hidden] { display: none; }
/* 3:2 is the native ratio of every file in assets/blog (1000x667), so `cover`
   now crops nothing, and it is the ratio the paired homepage work cards
   (.wk-a/.wk-b) already use. 16:10 was shaving a band off each image for
   nothing; 4:3 on the featured card was cutting a sixth off it. */
.post-shot {
  overflow: hidden; border: 1px solid var(--line); background: var(--bg-muted);
  border-radius: var(--r-lg); aspect-ratio: 3 / 2;
}
/* No grayscale. Every other image on this site — work thumbs, client logos,
   testimonial portraits — is shown in its own colour, and desaturating only
   the journal is most of why it read as a separate, staler property. The 1.04
   scale stays: it is the homepage work card's hover, to the decimal. */
.post-shot img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform .6s var(--ease);
}
.post:hover .post-shot img { transform: scale(1.04); }

/* 20px off the shot, matching .work-meta. The label used to sit flush on the
   image edge, which is the one spacing bug you could see from across a room. */
.post-meta { margin-top: var(--s-5); }
.post-topic { font-family: var(--font-mono); font-size: var(--t-2xs); letter-spacing: var(--ls-mono); text-transform: uppercase; color: var(--ink-4); }
.post-topic i { font-style: normal; padding: 0 var(--s-1); opacity: .6; }
.post-meta h2, .post-meta h3 { margin-top: var(--s-3); line-height: 1.12; transition: color .25s var(--ease); }
/* --t-xl / 700 / -0.02em is .wwd h3, the homepage's card heading, exactly */
.post-meta h3 { font-size: var(--t-xl); font-weight: 700; letter-spacing: -0.02em; text-wrap: balance; }
/* and the lead title is .work-name: --t-h2s / 600 / -0.012em. Leading stays at
   1.12 rather than .work-name's 1.08 because these titles wrap and those are
   single words. */
.post-meta h2 { font-family: var(--font-display); font-size: var(--t-h2s); font-weight: 600; letter-spacing: -0.012em; }
.post:hover .post-meta h2, .post:hover .post-meta h3 { color: var(--accent); }
/* 14px/1.55 is what .work-desc and .wwd > p set for card body everywhere else.
   At 15px/1.62 these were the only cards on the site running large, which read
   as a blog template rather than as this page. */
.post-meta p { margin-top: var(--s-3); font-size: var(--t-sm); line-height: 1.55; color: var(--ink-2); max-width: 56ch; }

/* the one featured post runs across two columns */
.post-featured { display: grid; grid-template-columns: 1.1fr 1fr; gap: clamp(var(--s-6), 3vw, var(--s-12)); align-items: center; }
/* side by side the meta is a column, not something sitting under a shot */
.post-featured .post-meta { margin-top: 0; }
/* the only excerpt long enough to want the body size back */
.post-featured .post-meta p { font-size: var(--t-base); line-height: 1.62; }
/* Not a filled chip. This site spends accent on hairlines and single words —
   .wwd-n over a card heading, .eng .badge — and a solid red block
   was the only filled badge anywhere outside the buttons. Tracking and radius
   come from .cf-btn in the filter row right above it, so the two chips on this
   page are cut to one pattern; padding from .eng .badge. */
.post-flag {
  display: inline-block; margin-right: var(--s-3);
  font-family: var(--font-mono); font-size: var(--t-2xs); letter-spacing: .05em; text-transform: uppercase;
  color: var(--accent); border: 1px solid var(--accent-line); border-radius: var(--r-sm); padding: 4px 9px;
}

/* 28px columns from .case-grid, 40px rows from the homepage .work grid. Split
   rather than uniform because each card carries three lines of text under its
   shot, so the vertical gap has to clear a caption the square work cards do
   not have — uniform 28px let one card's dek crowd the next card's image. */
.post-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-10) var(--s-7); }

/* ---------------- Journal — article — hero ----------------
   .post-head is on the <section> of all eleven articles and nowhere else. It
   exists because the two other ways to reach these heroes both fail:
   [data-page="blog"] also catches blog.html, and .ph-tight also catches the
   nine case studies. Everything the shared .page-hero rule sets — the wash, the
   weight, the 1.02 leading, the -0.045em — is left alone, because that part is
   the homepage's and is exactly what these pages were missing. */

/* Article titles are sentences, not statements: the longest here is ten words,
   where About and Services carry two or three. At the shared 72px/15ch it set
   four lines and ~290px of headline — the same block as the homepage's entire
   hero, on a page where the article is supposed to start immediately.

   The measure is what actually fixes it, not the size: 15ch scales with the
   type, so shrinking the font alone leaves the line count where it was. 22ch at
   52px is ~690px, close enough to the body column below that headline and text
   read as one block, and the longest title lands on three lines. 52px is
   --t-h1, the step the scale already had; the 72px step stays for the five
   landing pages, which is the hierarchy the right way round. */
.page-hero.post-head h1 { font-size: var(--t-h1); max-width: 22ch; }

/* The site's last eyebrow, and it is metadata now, not a section label — so it
   is set as the same category+date line the reader just clicked on the journal
   index (.post-topic), rather than keeping the leading 18px hairline that the
   removed section labels wore. Font, colour and case already matched; the rule,
   the 11px/12px step and the tracking did not. The <i> separator came through
   from the scrape unstyled and was rendering the middot in italic. */
.post-head .eyebrow { font-size: var(--t-2xs); letter-spacing: .08em; }
.post-head .eyebrow::before { display: none; }
.post-head .eyebrow i { font-style: normal; padding: 0 var(--s-1); opacity: .6; }

/* ---------------- Journal — article — body ----------------
   One left edge the whole way down. These heroes deliberately skip .ph-centre,
   so a reading column centred in the 1240px wrap started ~240px to the right of
   its own title — the exact mismatch .ph-centre's comment is about. Figure,
   prose and source line now all hang off the h1's left edge.

   --post-measure is that column, set once so the three cannot drift. It is in
   px, not ch, on purpose: the three elements are set at 17px, 16px and 14px, so
   one ch value would have given them three different widths.

   630px is about 74 characters of 17px Manrope — the top of the band, a
   correction rather than a squeeze. .cs-main keeps its 68ch (~690px, ~81
   characters) and should: it sits in a 1fr/300px grid with an aside beside it,
   in short blocks broken by headings and figures. An article is ten unbroken
   paragraphs alone in the wrap, where the return sweep is what tires the eye.
   That is the one place the two long-form templates deliberately part. */
.post-body { --post-measure: 630px; }

/* The hero and the article are one unit; .section's 120px on top of the hero's
   own 60px read as a break between a title and its own first sentence. 40px is
   what the figure needs to clear the headline and no more. */
.post-body { padding-top: var(--s-10); }

/* Full-bleed was tried and rejected: at wrap width these 3:2 shots render ~780px
   tall, so the first thing under the title was a grey slab taller than a laptop
   viewport, with its right edge 550px past the text it introduces. */
.post-hero-shot {
  max-width: var(--post-measure);
  border: 1px solid var(--line); border-radius: var(--r-lg); overflow: hidden;
  background: var(--bg-muted); margin: 0 0 clamp(var(--s-8), 4vw, var(--s-14));
}
.post-hero-shot img { display: block; width: 100%; height: auto; }

.prose { max-width: var(--post-measure); }
.prose > :first-child { margin-top: 0; }

/* --ink, not --ink-2. Everywhere else on the site --ink-2 is supporting copy
   sitting beside a black heading; here the paragraphs are the page, and 800
   words of #5b5b61 reads as a caption that never ends. --ink-2 and --ink-4 stay
   for the things that really are secondary — the source line below. */
.prose p { font-size: var(--t-lg); line-height: 1.72; color: var(--ink); }

/* 28px, not 20px. The line box here is ~29px, so a 20px gap is two-thirds of a
   line and the eye reads the break as loose leading rather than a new
   paragraph. One line of air is the smallest gap that actually separates. */
.prose p + p { margin-top: var(--s-7); }

/* 56 above / 16 below, so the heading binds to the text it introduces and the
   space above it is twice the paragraph gap. At 48px it was only 1.7x the new
   gap and had stopped registering as a break at all. */
.prose h2 {
  font-family: var(--font-display); font-size: var(--t-h3); font-weight: 600;
  letter-spacing: -0.012em; line-height: 1.2; color: var(--ink);
  margin-top: var(--s-14);
}
.prose h2 + p { margin-top: var(--s-4); }

/* No article carries an inline link yet, but `a { color: inherit }` is
   site-wide, so the first one written would be invisible in a wall of body
   text. Same hairline-underline as .post-source so the two agree on the page. */
.prose a { color: var(--ink); border-bottom: 1px solid var(--accent-line); }
.prose a:hover { color: var(--accent); border-color: var(--accent); }

/* Markers in --ink-4: the site draws its rules and ticks in the grey, and a
   column of full-black discs beside 17px body is louder than the sentences it
   belongs to. An accent dot (the .ba-list idiom) was tried and rejected — right
   for four bullets in an aside, a row of red flecks down an article. 24px above
   rather than the 28px paragraph gap, so a list binds to its own lead-in. */
.post-ul { margin: var(--s-6) 0 0; padding-left: var(--s-5); }
.post-ul li { font-size: var(--t-lg); line-height: 1.72; color: var(--ink); }
.post-ul li::marker { color: var(--ink-4); }
.post-ul li + li { margin-top: var(--s-2); }

.post-source {
  max-width: var(--post-measure); margin: clamp(var(--s-10), 4vw, var(--s-14)) 0 0;
  padding-top: var(--s-5); border-top: 1px solid var(--line);
  font-size: var(--t-sm); color: var(--ink-4);
}
.post-source a { color: var(--ink-2); border-bottom: 1px solid var(--line); }
.post-source a:hover { color: var(--accent); border-color: var(--accent); }

@media (max-width: 1080px) {
  .post-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 860px) {
  .post-featured { grid-template-columns: 1fr; }
  /* stacked, the meta is under a shot again and wants .work-meta's 20px back */
  .post-featured .post-meta { margin-top: var(--s-5); }
}
@media (max-width: 660px) {
  .post-grid { grid-template-columns: 1fr; }
}


/* The accent word in a section heading, for the whole site.
   sections.css scopes this as `.services .section-head h2 em` - to the
   homepage's own section rather than to the pattern - so on every other page an
   <em> in a heading fell back to plain italics, which is the opposite effect.
   Promoted here once About needed it too and it stopped being an AI-page quirk.
   Checked before widening: the only other <em>s on the site are the two in
   services.html's .rail-fork h3, which are subtitles with their own rule at
   .rail-fork h3 em and a different selector, so they are untouched. */
.section-head h2 em { font-style: normal; color: var(--accent); }

/* ---------------- AI page ----------------
   Almost everything here is borrowed, not built. The three entry points reuse
   .wwd-row / .wwd from the homepage outright, and the Land -> Build -> Scale
   stages reuse .stage-track / .stage from Services. Only two components are
   genuinely new, and both are small. A new page should cost the design system
   as little as possible, or it stops being a system. */

/* The hero standfirst, one step up the scale. The review comment on it was the
   single word "larger". Scoped to this page rather than widening .ph-sub: that
   rule is the hero sub-line on fourteen pages, and only this one was asked to
   change. --t-lede -> --t-h4 is the next step up and keeps the fluid clamp; the
   measure widens with it so the line count does not grow. */
[data-page="ai"] .ph-sub {
  max-width: 680px;
  font-size: var(--t-h4);
  line-height: 1.5;
}

/* Three cards in the homepage's four-column frame. Written as min-width so the
   existing .wwd-row breakpoints (2-up at 960, 1-up at 660) still apply below it
   untouched - an unguarded .wwd-row.ai-three would outrank them on specificity
   at every width and pin the row at three columns down to a phone. */
@media (min-width: 861px) {
  .wwd-row.ai-three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* the 2-up rules at 1080 (sections.css) re-rule cells for two columns; with
     three they left no divider between 2 and 3 and a stray top border on 3 */
  .wwd-row.ai-three .wwd { border-left: 1px solid var(--line); border-top: 0; }
  .wwd-row.ai-three .wwd:first-child { border-left: 0; }
}
/* Below 861 three cards stack. 2-up left the third one alone on its row with
   a hairline stopping half way across. */
@media (max-width: 860px) {
  .wwd-row.ai-three { grid-template-columns: minmax(0, 1fr); }
  .wwd-row.ai-three .wwd { border-left: 0; }
  .wwd-row.ai-three .wwd + .wwd { border-top: 1px solid var(--line); }
}
/* Land / Build / Scale (AI page) and Build / Scale / Transform (homepage) name
   the tier, so they are set as words rather than as an 11px mono index. The
   owner could not see them at that size (2026-09-23). Both rows are
   .wwd-row.ai-three and both get it. */
.wwd-row.ai-three .wwd-n {
  font-family: var(--font-display); font-size: var(--t-xl); font-weight: 700;
  letter-spacing: -0.01em; line-height: 1.2;
}

/* The alternative propositions. Rebuilt from a mockup the owner supplied: an
   icon tile, the proposition, what it contains, and the outcome it ends in.
   The old version was a two-column ruled list with none of that - the comment
   was "maybe with a pic is better", and the mockup attached to it turned out to
   be a fuller section rather than the same list with an image dropped in.

   Icons are inline SVG on currentColor, like the footer's social marks. No icon
   font and no sprite sheet: four glyphs do not justify a dependency on a site
   that has none, and inline means they inherit the accent colour for free. */
.ai-alt { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--line); }
.ai-alt-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1.02fr) minmax(0, 1.08fr) minmax(0, 0.84fr);
  gap: clamp(var(--s-5), 2.4vw, var(--s-8));
  align-items: start;
  padding: var(--s-8) 0;
  border-bottom: 1px solid var(--line);
}

.ai-alt-icon {
  width: 56px; height: 56px; flex: none;
  display: grid; place-items: center;
  border-radius: var(--r-lg);
  background: var(--accent-soft); color: var(--accent);
}
.ai-alt-icon svg { width: 24px; height: 24px; display: block; }

.ai-alt-role {
  display: block;
  font-family: var(--font-mono); font-size: var(--t-2xs); letter-spacing: var(--ls-mono);
  text-transform: uppercase; color: var(--ink-4);
}
.ai-alt-lead h3 {
  margin-top: var(--s-3);
  font-family: var(--font-display); font-size: var(--t-xl); font-weight: 700;
  letter-spacing: -0.02em; color: var(--ink); text-wrap: balance;
}
.ai-alt-lead p {
  margin-top: var(--s-3); font-size: var(--t-sm); line-height: 1.55; color: var(--ink-2);
}

/* The checklist. Its rule is on the LEFT edge rather than around the whole
   column: the row already has hairlines above and below it, and a fourth box
   inside them would be one frame too many. */
.ai-alt-list {
  list-style: none; margin: 0; padding: 0 0 0 clamp(var(--s-5), 2vw, var(--s-8));
  border-left: 1px solid var(--line);
  display: grid; gap: var(--s-3); align-content: start;
}
.ai-alt-list li {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--s-3);
  align-items: start;
  font-size: var(--t-sm); line-height: 1.5; color: var(--ink-2);
}
.ai-alt-list svg { width: 17px; height: 17px; flex: none; color: var(--ink-4); margin-top: 1px; }

.ai-alt-out { padding-left: clamp(var(--s-5), 2vw, var(--s-8)); border-left: 1px solid var(--line); }
.ai-alt-outlbl {
  display: block;
  font-family: var(--font-mono); font-size: var(--t-2xs); letter-spacing: var(--ls-mono);
  text-transform: uppercase; color: var(--ink-4);
}
.ai-alt-out p {
  margin-top: var(--s-3);
  font-family: var(--font-display); font-size: var(--t-base); font-weight: 600;
  letter-spacing: -0.012em; line-height: 1.35; color: var(--ink);
}

/* Each row used to end in an accent arrow. It linked nowhere, and the
   reviewer asked for it gone (2026-09-23): an arrow promises a destination. */

@media (max-width: 1180px) {
  .ai-alt-row { grid-template-columns: auto minmax(0, 1fr) minmax(0, 1fr); }
  .ai-alt-out { grid-column: 2 / -1; padding-left: 0; border-left: 0; padding-top: var(--s-5); }
}
@media (max-width: 860px) {
  .ai-alt-row { grid-template-columns: auto minmax(0, 1fr); row-gap: var(--s-5); }
  .ai-alt-list { grid-column: 1 / -1; padding-left: 0; border-left: 0; }
  .ai-alt-out { grid-column: 1 / -1; padding-top: 0; }
}
@media (max-width: 660px) {
  /* The tile stops holding a column of its own - at 56px it was taking a sixth
     of a phone screen to sit beside two words of label. */
  .ai-alt-icon { width: 44px; height: 44px; }
  .ai-alt-icon svg { width: 20px; height: 20px; }
}

@media (max-width: 660px) {
  /* The role label stops being a column and becomes a kicker above the name. At
     210px it was taking a third of a phone screen to hold two words. */
  .ai-alt-row { grid-template-columns: minmax(0, 1fr); gap: var(--s-3); padding: var(--s-6) 0; }
  .ai-alt-role { padding-top: 0; }
}

/* ============================================================
   Phone pass (2026-09-23)
   ============================================================ */
@media (max-width: 660px) {
  /* About's "The result": three columns at 360px are ~72px each and the
     labels ("experience", "delivered") could not break, so they ran into the
     next column. One stat per row, number beside its label. */
  .vr-stats { grid-template-columns: minmax(0, 1fr); gap: var(--s-4); }
  .vr-stat, .vr-stat:first-child {
    display: flex; align-items: baseline; gap: var(--s-4);
    padding-left: 0; border-left: 0;
  }
  .vr-stat + .vr-stat { border-top: 1px solid var(--d-line-soft); padding-top: var(--s-4); }
  .vr-l { margin: 0; }
}

/* Tap targets. Anything a thumb is meant to hit gets 44px, the smallest size
   both platforms' guidelines accept. The small text links get an invisible
   hit area rather than padding, so nothing on screen moves. Keyed on the
   pointer as well as the width: a phone held sideways is 900px+ wide and
   gets the desktop layout, but it is still a thumb. */
@media (max-width: 860px), (pointer: coarse) {
  .nav-cta-link { min-height: 44px; }
  .nav-burger { width: 44px; height: 44px; }
  .nav-burger span { left: 14px; right: 14px; }
  .nav-burger span:nth-child(1) { top: 18px; }
  .nav-burger span:nth-child(2) { top: 24px; }
  .nav-burger span:nth-child(3) { top: 30px; }
  .foot-soc a { width: 44px; height: 44px; }
  .foot-col a { padding: 11px 0; }
  .cf-btn, .cred { min-height: 44px; }
  /* It is a labelled pill ("Close" + x), not an icon button: a fixed 44px
     width cut the label off. Height is what the tap target needs. */
  .pm-close { min-height: 44px; padding-top: 0; padding-bottom: 0; }
  .work-all, .foot-top, .cs-back, .bs-link, .ct-mail, .ba-loc a, .ind-go, .cap-go { position: relative; }
  .work-all::after, .foot-top::after, .cs-back::after, .bs-link::after, .ct-mail::after, .ba-loc a::after, .ind-go::after, .cap-go::after {
    content: ""; position: absolute; left: -6px; right: -6px; top: 50%;
    height: 44px; transform: translateY(-50%);
  }
}

/* ---------- Compact grids on phones (owner's choice: compact, not carousels)
   Measured at 375 before this: Portfolio 17,600px tall (30 cards, one per
   row), the homepage's Industries 1,580 and Services grid 2,090, and the CTA
   band 950 on every page. Everything stays visible; it just stops taking a
   screen per item. */
@media (max-width: 660px) {
  /* Portfolio: two per row. The square shot holds up at ~160px; the name
     drops to 16px and the line under it is held to two lines. */
  .case-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--s-3); row-gap: var(--s-8); }
  .case-meta { margin-top: var(--s-3); }
  .case-cat { font-size: 10px; letter-spacing: .08em; }
  .case-meta h3 { font-size: var(--t-md); line-height: 1.2; }
  .case-meta p {
    font-size: var(--t-s); line-height: 1.45;
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden;
  }

  /* Industries: a two-up index of sectors, icon over name, on both pages.
     Services' #industries used to opt out and keep its three lines each, as
     the full reference; one per row that ran 1,790px of a 12,000px page, and
     the owner still found phones too long to scroll (2026-09-24). Desktop
     keeps the lines. */
  .ind-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--s-4); }
  .ind { grid-template-columns: minmax(0, 1fr); gap: var(--s-3); padding: var(--s-5) 0; }
  .ind h3 { font-size: var(--t-md); }
  .ind-list { display: none; }
  .ind-go { margin-top: 0; font-size: var(--t-s); }

  /* Services tiles: two-up, tighter, the line under each title held to three. */
  .cap-grid, .caps--four .cap-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cap { padding: var(--s-6) var(--s-4) var(--s-7); }
  .cap--strip { padding-bottom: calc(var(--s-7) + 44px); }
  .cap-ico svg { width: 26px; height: 26px; }
  .cap h3 { margin-top: var(--s-4); font-size: var(--t-md); line-height: 1.2; }
  .cap h3 br { display: none; }
  .cap p {
    margin-top: var(--s-2); font-size: var(--t-s); line-height: 1.45;
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden;
  }
  .cap-strip { height: 44px; padding: 0 var(--s-4); font-size: var(--t-s); }
  .cap-go { padding-top: var(--s-4); font-size: var(--t-s); }


  /* CTA band, under one screen: the three points become one row of icon +
     label (the grey second line goes), the ticks one wrapped line, and less
     air top and bottom. */
  .cta .wrap { padding-top: 72px; padding-bottom: 40px; }
  .cta p { margin-top: var(--s-4); font-size: var(--t-md); }
  .cta-points {
    flex-direction: row; justify-content: space-between; align-items: flex-start;
    width: 100%; gap: var(--s-3); margin-top: var(--s-8);
  }
  .cta-points li { flex: 1 1 0; flex-direction: column; align-items: center; gap: var(--s-2); padding: 0; text-align: center; }
  .cta-points svg { width: 24px; height: 24px; }
  .cta-points span { font-size: 0; }
  .cta-points b { font-size: var(--t-s); line-height: 1.25; }
  .cta-btns { margin-top: var(--s-8); }
  .cta-checks { flex-direction: row; justify-content: center; gap: var(--s-2) var(--s-6); margin-top: var(--s-6); }
  .cta-checks li { font-size: var(--t-s); padding-left: 20px; }
  .cta .wrap.cta-foot { padding-bottom: 40px; }
}

/* ============================================================
   Phone pass 2 (2026-09-24)
   Owner: phones are "still too long to scroll". Measured at 375 before
   this: Services 12,020px, homepage 9,590, About 7,930, Portfolio 6,950,
   AI 6,580, with a 690px CTA band and a 1,020px footer on every page.
   The rule this pass follows: a phone gets the index, desktop keeps the
   detail. Nothing is removed from the page, only from the phone layout.
   ============================================================ */
@media (max-width: 660px) {
  /* Rhythm. 64px a side was a desktop section's air at half the width. */
  .section { padding: 48px 0; }
  .section.clients { padding: 36px 0; }
  .section-head { margin-bottom: var(--s-8); }
  .page-hero { padding: 112px 0 40px; }
  .page-hero.ph-tight { padding-bottom: 32px; }
  .stats { padding-top: 56px; padding-bottom: 56px; }
  .story { padding-top: 56px; }
  .svc-detail { padding-top: 56px; }

  /* Homepage hero on a phone: the whole first screen, with the copy CENTRED in
     it (owner, 2026-09-24: "I don't want to see the globe when I first load").
     Content-height put the globe section ~400px down, inside the first view.
     The earlier objection to a full-screen hero was a headline floating at
     the top of empty page; centring answers that. The globe section loses its
     30px upward pull here so it starts exactly at the fold. */
  .hero {
    --hero-pad-top: 96px; padding-bottom: 32px;
    min-height: 100vh; min-height: 100svh; box-sizing: border-box;
    align-items: center;
  }
  .hero-copy { min-height: 0; width: 100%; }
  .section.reach { margin-top: 0; }

  /* Selected projects, two-up like the Portfolio grid, square shots (a 3:2
     frame at 160px left the screenshots postage-stamp sized), name and year
     only. Retention AI stays the full-width band under them. */
  .work-head { grid-template-columns: minmax(0, 1fr); margin-bottom: var(--s-8); }
  .work-head > .work-all { grid-column: 1; grid-row: auto; justify-self: start; }
  .work { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--s-3); row-gap: var(--s-8); }
  .wk-a, .wk-b, .wk-c, .wk-d, .wk-e, .wk-f { grid-column: auto; grid-row: auto; }
  .wk-wide { grid-column: 1 / -1; }
  .wk-a .work-thumb, .wk-b .work-thumb, .wk-c .work-thumb,
  .wk-d .work-thumb, .wk-e .work-thumb, .wk-f .work-thumb { aspect-ratio: 1 / 1; }
  .wk-wide .work-thumb { aspect-ratio: 16 / 10; }
  .work-thumb .scene { padding: 12px; }
  .work-meta { margin-top: var(--s-3); row-gap: 2px; }
  .work-name { font-size: var(--t-md); line-height: 1.2; }
  .work-year { font-size: var(--t-3xs); }
  .work-item:not(.wk-wide) .work-desc { display: none; }

  /* Build / Scale / Transform (homepage) and the AI page's three entry
     points: the bullets two to a row, the card a step tighter. */
  .wwd { padding: var(--s-6) var(--s-5); }
  .wwd h3 { margin-top: var(--s-3); }
  .wwd ul { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-2) var(--s-4); padding-top: var(--s-4); }
  .wwd ul::before { grid-column: 1 / -1; margin-bottom: var(--s-2); }

  /* Services tiles: the line under each title held to two, not three. */
  .cap p { -webkit-line-clamp: 2; line-clamp: 2; }

  /* Services hero: all seven jump chips visible. A single sideways-scrolling
     row was tried first and showed three - nothing said it scrolled (owner,
     2026-09-24). Compact chips that wrap instead: about three rows. */
  body[data-page="services"] .ph-jump { gap: var(--s-2); }
  body[data-page="services"] .ph-jump a {
    gap: var(--s-2); padding: 0 var(--s-3); min-height: 40px;
    font-size: var(--t-s);
  }
  body[data-page="services"] .ph-jump .pj-ico svg { width: 15px; height: 15px; }

  /* Services blocks (MVP / Product Development): body a step down, chips
     closer together. */
  .svc-body > p { font-size: var(--t-md); line-height: 1.6; }
  .svc-points { margin-top: var(--s-4); gap: var(--s-2); }
  .svc-points li { padding: 6px var(--s-3); font-size: var(--t-s); }

  /* Stack band: the chips name the tools, so the sentence above them goes,
     and the segments go back to two a row (the 860 ladder's rules, with the
     vertical hairline on every second one). */
  .rail-desc { display: none; }
  .stack-rail { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stack-rail .rail-seg:nth-child(even)::before { display: block; }
  .rail-seg:nth-child(2) { border-top: 0; }
  .rail-seg { padding: var(--s-5) var(--s-4); }
  .rail-tags { margin-top: var(--s-3); gap: 6px; }
  .rail-tags li { font-size: var(--t-2xs); padding: 3px var(--s-2); }

  /* AI alternatives: the tile moves beside the name instead of taking a row
     of its own above it. */
  .ai-alt-row { grid-template-columns: auto minmax(0, 1fr); column-gap: var(--s-4); }
  .ai-alt-icon { width: 40px; height: 40px; }
  .ai-alt-out { grid-column: 2; }

  /* Process rail: the numbered node already marks each step, so the icon
     tile goes and the phase tag sits above the heading. */
  .rail-ico { display: none; }
  .rail-card { grid-template-columns: minmax(0, 1fr); row-gap: var(--s-1); }
  .rail-cat { grid-column: 1; grid-row: 1; justify-self: start; }
  .rail-copy { grid-column: 1; grid-row: 2; }
  .rail-copy h3 { font-size: var(--t-lg); }
  .rail-copy p { font-size: var(--t-sm); line-height: 1.55; }

  /* About, values: number and icon share the heading's line, and the card
     sheds a step of padding. */
  .val-card {
    grid-template-columns: auto minmax(0, 1fr); grid-template-areas: "ico t" "b b";
    gap: var(--s-3) var(--s-4); padding: var(--s-5);
  }
  .val-n { display: none; }
  .val-ico { width: 36px; height: 36px; }
  .val-ico svg { width: 18px; height: 18px; }
  .val-t { font-size: var(--t-lg); align-self: center; }
  .val-b { font-size: var(--t-sm); line-height: 1.55; }

  /* About, leadership: one full portrait per row on a phone, as before this
     pass (owner, 2026-09-24: keep them "like it was" on mobile), with the CEO
     first. Only the phone order changes; the markup and the desktop row
     stay Alda, Gerti, Fation. */
  .lead-row .lead-fig:nth-child(2) { order: -1; }

  /* AI alternatives: name, one line of what it is, and the outcome. The
     four-point list under each is the desktop's detail. */
  .ai-alt-list { display: none; }

  /* Portfolio: category and name under each square, as an index. */
  .case-meta p { display: none; }

  /* CTA band: the two buttons side by side. */
  .cta-btns { flex-wrap: nowrap; gap: var(--s-3); }
  .cta-btns .btn { flex: 1 1 0; padding-left: var(--s-3); padding-right: var(--s-3); }

  /* Footer on a phone (owner, 2026-09-24: "too tall" - it was 932px at 375).
     Same links, re-laid:
     - the brand block is the logo, then the social icons and the Clutch badge
       on ONE row; the one-line company description is the desktop's detail;
     - Services and Company side by side as before, their links on 32px rows
       with the hit area kept at 40px by an ::after (rows are 32px apart, so
       neighbours' areas meet rather than stack);
     - Contact runs full width, its four items flowing two to a line;
     - the copyright and Back to top share one line. */
  .footer { padding: 40px 0 24px; }
  .foot-grid { gap: 28px 20px; }
  .foot-brand {
    display: grid; grid-template-columns: auto 1fr; align-items: center;
    column-gap: var(--s-3); row-gap: var(--s-5);
  }
  .foot-brand .brand { grid-column: 1 / -1; margin-bottom: 0; }
  /* The links first, the brand block (logo, socials, Clutch) closing the
     footer just above the copyright line (owner, 2026-09-24). */
  .foot-brand { order: 1; padding-top: var(--s-6); border-top: 1px solid var(--d-line-soft); }
  .foot-brand p { display: none; }
  .foot-soc { margin-top: 0; gap: var(--s-2); }
  .foot-soc a { width: 40px; height: 40px; }
  .foot-brand .clutch-badge { margin-top: 0; justify-self: end; padding: 8px 10px; gap: var(--s-3); }
  .foot-brand .cb-w { font-size: 18px; }
  .foot-brand .cb-s { font-size: 12px; }
  .foot-col h4 { margin-bottom: var(--s-2); }
  .foot-col a { position: relative; padding: 6px 0; }
  .foot-col a::after { content: ""; position: absolute; left: 0; right: 0; top: 50%; height: 40px; transform: translateY(-50%); }
  .foot-grid > .foot-col:last-child {
    grid-column: 1 / -1;
    display: grid; grid-template-columns: 1fr 1fr; column-gap: 20px;
  }
  .foot-grid > .foot-col:last-child h4 { grid-column: 1 / -1; }
  .foot-col span { padding: 6px 0; }
  .foot-bottom { margin-top: 28px; padding-top: 16px; flex-wrap: nowrap; font-size: 11px; letter-spacing: 0; gap: var(--s-3); }

  /* The bar: the lockup a step smaller so Book a call fits beside it and
     the burger down to 360px wide. */
  .brand-logo { height: 32px; }
  .nav-cta-link { padding: 0 12px; }
}
/* Below 360 the tagline cannot fit next to the button as well: the brand
   clips to mark + wordmark (the SVG's first 1539 of 2412 units). */
@media (max-width: 359px) {
  .nav .brand { width: 101px; overflow: hidden; }
  .nav-cta-link .arr { display: none; }
}
