/* ==========================================================================
   Proventarc — one-page site
   Token source: brand/DESIGN.md (colour + type). Component tokens decided
   here (spacing, radii, elevation, states) fold back into DESIGN.md.
   ========================================================================== */

:root {
  /* --- colour (from DESIGN.md, 1:1) --- */
  --ink: #1A2321;
  /* Achromatic (C=0), not cream. The warm near-white band is the saturated
     generative default, and cream mismatches white slides and white paper —
     decks and print are the reason this is neutral. */
  --paper: #F0F0F0;
  --teal: #2E5952;
  --ochre: #B4552F;
  --teal-support: #4E6E68;

  /* --- derived surfaces & states (new, fold back) --- */
  --teal-deep: #24463F;        /* hover/active on teal, hero depth */
  --paper-raised: #FAFAFA;     /* raised surface on paper */
  --paper-tint: #E5E5E5;       /* tinted section band on paper */
  --paper-bright: #FFFFFF;     /* hover lift on teal surfaces, brighter than --paper */
  --ochre-bright: #E39A6F;     /* ochre for TEXT/GRAPHIC accents on dark surfaces (3.4:1 on teal) */
  --mark-counter-dark: #EC845B; /* logo counter on dark ONLY — matches brand/proventarc-*-on-dark.svg.
                                   Not a general accent, never on paper. 3.01:1 teal / 6.14:1 ink.
                                   More chroma than ochre-bright, which read washed out in the mark. */
  --ink-muted: color-mix(in oklab, var(--ink) 76%, var(--paper));   /* secondary text on paper */
  --paper-muted: color-mix(in oklab, var(--paper) 84%, var(--teal)); /* secondary text on teal */
  --hairline: color-mix(in oklab, var(--ink) 14%, var(--paper));
  --hairline-inverse: color-mix(in oklab, var(--paper) 26%, var(--teal));

  /* --- spacing (4px base) --- */
  --s-2: 0.5rem;  --s-3: 0.75rem; --s-4: 1rem;
  --s-6: 1.5rem;  --s-8: 2rem;    --s-12: 3rem;
  --s-24: 6rem;
  /* Tightened 2026-08-19. Was 2.75->4.5rem and 1.75->2.5rem, which put 144px of
     dead band between every pair of sections at desktop and 40px between a
     heading and its own content. Across eight sections on the home page that
     alone was most of a viewport of scrolling. */
  --section-pad: clamp(2rem, 1.5rem + 2vw, 2.75rem);
  --block-gap: clamp(1.25rem, 1rem + 1vw, 1.75rem); /* heading -> its content */

  /* --- radii (restrained, structural) --- */
  --r-sm: 3px;    /* buttons, small controls */
  --r-md: 8px;    /* panels, cards */

  /* --- motion --- */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --t-fast: 150ms;
  --t-med: 260ms;

  /* --- type --- */
  --font-display: "Mona Sans", "Helvetica Neue", Arial, sans-serif;
  --font-body: "Schibsted Grotesk", "Helvetica Neue", Arial, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", monospace;

  --mark-legs: var(--teal);
  --mark-bar: var(--ochre);
}

/* ==== base ==== */

*, *::before, *::after { box-sizing: border-box; }

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

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.65;
  color: var(--ink);
  background: var(--paper);
  font-kerning: normal;
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* A page shorter than the viewport (/booked, /404) put the footer partway down
     the screen with a band of bare paper under it, which reads as the page having
     failed to finish rather than as a short page. main takes the slack, so the
     footer sits at the fold or below it and never above it with nothing beneath.
     flex rather than a 3-row grid because the child count varies: /privacy and
     /404 override the nav block, and a fixed template would have to be right
     about how many rows each page produces. */
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}
/* 0 shrink: without it a long page's main is allowed to compress below its
   content height in a column flex container. */
body > main { flex: 1 0 auto; }

h1, h2, h3, h4 { margin: 0; text-wrap: balance; }
p { margin: 0; text-wrap: pretty; }

.container {
  max-width: 71.25rem;
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 4vw, 2.5rem);
}

/* semantic z-scale */
:root { --z-nav: 10; }

/* Sticky nav overlaps anchored content; keep any anchor target clear of it.
   Every id inside main, not a hand-listed set: the old list named #method, which
   exists in no template, and omitted #signals plus every anchor on
   /revenue-motion, /about and /faq. .legal pages carry a static .nav-plain, so
   they need no offset and are excluded. */
/* 5.5rem, not 5rem: the bar measures 81px at desktop (36px padding + a 44px CTA
   + a 1px border), so 80px left the target's top pixel under the bar on every
   anchor jump. 88px clears it with a little air, and the bar is shorter than
   that at every narrower width. */
:where(main:not(.legal), main:not(.legal) [id]) { scroll-margin-top: 5.5rem; }

::selection { background: var(--teal); color: var(--paper); }
/* On dark surfaces the default teal-on-paper highlight is teal text on a teal
   band = invisible. Invert it: paper highlight, ink text.
   .nav:not(.nav-plain), for the same reason :focus-visible is scoped that way
   below: .nav-plain is transparent over paper, so the inverted rule handed the
   /privacy and /404 header a paper highlight on a paper ground, which is the
   invisible-selection bug this block exists to prevent, from the other end. */
.hero ::selection,
.closing ::selection,
.footer ::selection,
.nav:not(.nav-plain) ::selection { background: var(--paper); color: var(--ink); }

.skip-link {
  position: absolute;
  left: var(--s-4);
  top: -100%;
  z-index: 100;
  background: var(--paper);
  color: var(--ink);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-sm);
  font-weight: 600;
  text-decoration: none;
}
.skip-link:focus { top: var(--s-4); }

/* Visible content stays out; assistive tech still gets it (e.g. the
   "opens in a new tab" hint on booking CTAs). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* focus */
:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 3px;
  border-radius: 2px;
}
/* Dark surfaces only. .nav-plain is excluded because it sets a transparent
   background over paper, so it inherited a paper ring on paper: 1.00:1, no ring
   at all on /privacy and the 404. .footer is here because it is the site's other
   dark band and was missing: teal on #1A2321 measures 2.04:1. */
.hero :focus-visible,
.closing :focus-visible,
.footer :focus-visible,
.nav:not(.nav-plain) :focus-visible {
  outline-color: var(--paper);
}

/* ==== buttons & links ==== */

.btn {
  display: inline-block;
  padding: 0.8125rem 1.5rem;
  border-radius: var(--r-sm);
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  border: 1px solid transparent;
  /* opacity is in the list for [data-cal-pending] below: without it the dim
     arrives and leaves in a single frame while everything else eases, which is
     the same split response the .text-link comment describes. */
  transition: background-color var(--t-fast) var(--ease-out),
              border-color var(--t-fast) var(--ease-out),
              color var(--t-fast) var(--ease-out),
              opacity var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out);
}
/* 1px, and no scale. A 1.02-1.05 hover scale is the consumer-app gesture; at this
   register the button should acknowledge the pointer, not perform for it. Source
   order matters: :active follows :hover so the press still wins on equal
   specificity and the button travels 2px between the two states.
   Gated on (hover: hover) for the same reason .q-anchor is: a touch tap applies
   :hover and holds it until the next tap elsewhere, so an ungated lift leaves the
   CTA stuck 1px up under the modal and still up when focus returns to it on close.
   :active stays outside the query, because touch is exactly where the press
   acknowledgement matters. */
@media (hover: hover) {
  .btn:hover { transform: translateY(-1px); }
}
.btn:active { transform: translateY(1px); }

/* Booking trigger waiting on cal.com's embed to paint its modal. Set and cleared
   by static/js/cal-embed.js, which is also what decides when the wait has failed.
   Deliberately not a spinner and not a label swap: the wait is normally under a
   second, a label swap would put copy in a stylesheet, and a spinner would be the
   only moving decoration on the site. Dimming plus a wait cursor is enough to say
   "heard you". Applies to .picker-card too, which is the same trigger in a box. */
[data-cal-pending] { opacity: 0.6; cursor: wait; }
[data-cal-pending]:hover { transform: none; }

/* primary on teal surfaces */
.btn-inverse {
  background: var(--paper);
  color: var(--ink);
}
.btn-inverse:hover { background: var(--paper-bright); }

/* quiet on teal surfaces */
.btn-ghost-inverse {
  background: transparent;
  color: var(--paper);
  /* 55%, not 45%: at 45% this measured 2.73:1 against --teal, below the WCAG
     1.4.11 non-text-contrast floor of 3:1 (only --teal-deep cleared it, at
     3.18:1). 55% clears both (3.30:1 / 3.96:1), verified by rendering the
     composited pixel, not by computing the oklab math by hand. */
  border-color: color-mix(in oklab, var(--paper) 55%, transparent);
}
.btn-ghost-inverse:hover { border-color: var(--paper); }

.text-link {
  color: var(--teal);
  font-weight: 600;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  /* The whole hover response now eases. It used to deepen the colour over 150ms
     while the underline jumped 1px to 2px in a single frame, so half the response
     arrived late.

     The obvious fix, transitioning text-decoration-thickness, does not work:
     Chromium computes it but does not interpolate it (probed at 2000ms linear,
     1px to 6px, it reads 6px from the first sample). Declaring the transition
     would have shipped an inert rule and left the pop exactly where it was. The
     underline instead tightens toward the text, which is a property that does
     interpolate, and it keeps text-decoration-skip-ink doing its job. Rebuilding
     the underline as a background-image gradient would animate but loses the
     descender skipping, which is not a trade this typography should make. */
  transition: color var(--t-fast) var(--ease-out),
              text-underline-offset var(--t-fast) var(--ease-out);
}
.text-link:hover { color: var(--teal-deep); text-underline-offset: 2px; }

/* WCAG 2.5.8. A link that is the entire paragraph is a standalone target, so the
   "inline in a sentence" exception does not apply and a 21px line box is short.
   .standalone-link marks that explicitly. It used to be inferred from
   `p:has(> .text-link:only-child)`, which also matched any ordinary sentence
   containing exactly one link — :only-child counts anchor children, not
   paragraph shape — and padded the /privacy mailto (one link inside a full
   sentence) by mistake. .page-links is the 404's route row: four links and
   separators in one paragraph, so it gets its own selector too. */
.standalone-link > .text-link,
.page-links .text-link {
  display: inline-block;
  padding-block: 0.75rem;
}
/* The route row used to carry literal "&nbsp;·&nbsp;" separators between the
   links. A non-breaking space does not stop a line breaking beside an
   inline-block, so at 390px the second line began with an orphaned middot before
   its link. Measured on both /booked and /404.
   Separated by a gap instead, which is what .footer-meta already does with the
   same shape (five links and labels in a row, no middots between them). The
   orphan cannot happen because there is nothing left to orphan, and a screen
   reader stops announcing a decorative character between every pair.

   column-gap only, and the zero row-gap is deliberate rather than a default:
   .footer-meta sets `gap: 0 var(--s-6)` on the identical shape, so wrapped rows
   are already edge-adjacent there by an explicit decision. Measured at 390px the
   targets are 52px tall and 0.1px apart, which clears WCAG 2.5.8 on size alone,
   and the 24px the eye reads between rows comes from each link's own
   padding-block. A mis-tap lands on a neighbouring link rather than on nothing;
   that is the same trade the footer already makes. */
.page-links {
  display: flex;
  flex-wrap: wrap;
  column-gap: clamp(1.25rem, 3vw, 2rem);
}

/* ==== nav ==== */

.nav {
  position: sticky;
  top: 0;
  inset-inline: 0;
  z-index: var(--z-nav);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-6);
  padding: clamp(0.75rem, 2vw, 1.125rem) clamp(1.25rem, 4vw, 2.5rem);
  /* teal-deep bar: reads as a header over the teal hero and stays legible over paper */
  background: var(--teal-deep);
  border-bottom: 1px solid var(--hairline-inverse);
  --mark-legs: var(--paper);
  --mark-bar: var(--mark-counter-dark);
}
.nav-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  text-decoration: none;
  margin-right: auto;
}
.nav-wordmark {
  font-family: var(--font-display);
  font-weight: 650;
  font-size: 1.25rem;
  letter-spacing: -0.01em;
  color: var(--paper);
}
.nav-links {
  display: flex;
  gap: var(--s-6);
  font-size: 0.9375rem;
}
.nav-links a {
  display: inline-flex;
  align-items: center;
  min-height: 44px; /* tap target */
  color: var(--paper-muted);
  text-decoration: none;
  transition: color var(--t-fast) var(--ease-out);
}
.nav-links a:hover { color: var(--paper); }
/* Current page. Underline plus full-strength paper, so it is not carried by
   colour alone. aria-current is the hook: the marker cannot be styled without
   also being announced. */
.nav-links a[aria-current="page"] {
  color: var(--paper);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 7px;
}
.nav-brand { min-height: 44px; }

/* The mark's third path is the StarterLine, the `arc` in proventus + arc, and it
   is the one part of the logo drawn as a stroke rather than a fill, so it is the
   one part that can dash. Hovering the brand redraws it from the foot up to the
   crest: the name performing itself, on the element that is the name. Nothing
   announces it and nothing depends on finding it.

   3060 is the path's measured length in its own user space (3052.5, rounded up).
   dasharray longer than the path means the whole stroke sits inside the first
   dash, so the resting rule renders identically to no dasharray at all. That is
   the point: the visible state is the default and the animation is the departure
   from it, never the other way round (same rule as .line-compound). Verified at
   4x scale, 0 differing pixels of 31,200 against dasharray:none.

   The offset runs from NEGATIVE length, not positive: a positive offset draws
   from the path's start, which is the crest, so the arc would grow downwards.
   From -3060 it draws from the end, along the foot and up the riser, which is
   the direction the mark argues.

   Hover is gated on (hover: hover) for the same reason .btn's lift is: a touch
   tap applies :hover and holds it until the next tap elsewhere. :focus-visible
   is deliberately outside the query, so a keyboard reaches it on any device. */
.mark .starterline {
  stroke-dasharray: 3060;
  stroke-dashoffset: 0;
}
@media (hover: hover) {
  .nav-brand:hover .starterline { animation: draw-starterline 700ms var(--ease-out); }
}
.nav-brand:focus-visible .starterline { animation: draw-starterline 700ms var(--ease-out); }
@keyframes draw-starterline {
  from { stroke-dashoffset: -3060; }
  to   { stroke-dashoffset: 0; }
}

/* no-JS disclosure menu (shown only where .nav-links is hidden) */
.nav-menu { display: none; position: relative; }
.nav-menu summary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  cursor: pointer;
  list-style: none;
  border-radius: var(--r-sm);
  border: 1px solid color-mix(in oklab, var(--paper) 45%, transparent);
}
.nav-menu summary::-webkit-details-marker { display: none; }
.nav-menu-icon,
.nav-menu-icon::before,
.nav-menu-icon::after {
  display: block;
  width: 17px;
  height: 1.5px;
  background: var(--paper);
  content: "";
}
.nav-menu-icon { position: relative; }
.nav-menu-icon::before { position: absolute; top: -5.5px; }
.nav-menu-icon::after  { position: absolute; top: 5.5px; }
.nav-menu[open] .nav-menu-icon { background: transparent; }
.nav-menu[open] .nav-menu-icon::before { top: 0; transform: rotate(45deg); }
.nav-menu[open] .nav-menu-icon::after  { top: 0; transform: rotate(-45deg); }
/* Resting state is the open one: grid-template-rows: 1fr, full opacity. The
   collapsed state lives only in the keyframe's `from`, so a browser that
   suppresses animations gets a menu that simply appears, never one stuck at
   zero height. Closing stays instant: <details> drops the panel out of the
   render tree the moment `open` is removed, and the only ways to hold it there
   long enough to collapse it are JavaScript or a statically-hidden resting
   state, which is the bug this rule is written to avoid. */
.nav-menu-panel {
  position: absolute;
  right: 0;
  top: calc(100% + 0.5rem);
  min-width: 12rem;
  display: grid;
  grid-template-rows: 1fr;
  background: var(--teal-deep);
  border: 1px solid var(--hairline-inverse);
  border-radius: var(--r-md);
  padding: var(--s-2);
}
.nav-menu-list {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}
@media (scripting: enabled) {
  /* 280ms, near the top of the sensible band, because --ease-out is ease-out-quint
     and it front-loads hard: measured at 220ms the panel was 86% open by 80ms, so
     the smoothing barely registered. The duration is the lever here, not the curve.
     One curve is a brand decision and a second one for a single component is not
     worth what it costs. */
  .nav-menu[open] .nav-menu-panel { animation: menu-open 280ms var(--ease-out) backwards; }
}
@keyframes menu-open {
  from { grid-template-rows: 0fr; opacity: 0; }
  to   { grid-template-rows: 1fr; opacity: 1; }
}
.nav-menu-panel a {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding-inline: var(--s-3);
  color: var(--paper);
  text-decoration: none;
  font-size: 0.9375rem;
  border-radius: var(--r-sm);
}
.nav-menu-panel a:hover { background: color-mix(in oklab, var(--paper) 12%, transparent); }
.nav-menu-panel a[aria-current="page"] {
  background: color-mix(in oklab, var(--paper) 12%, transparent);
  font-weight: 650;
}
.nav .btn {
  font-size: 0.9375rem;
  padding: 0.5625rem 1.125rem;
  display: inline-flex;
  align-items: center;
  min-height: 44px; /* tap target */
}

/* ==== hero ==== */

.hero {
  position: relative;
  background:
    radial-gradient(120% 90% at 80% 0%, color-mix(in oklab, var(--teal) 86%, var(--ink)) 0%, transparent 55%),
    var(--teal);
  color: var(--paper);
  padding-block: clamp(2.75rem, 3vw + 1.5rem, 4.5rem) clamp(3rem, 5vw, 5rem);
}

/* /about and /faq put nothing beside the h1, so the full hero padding leaves the
   heading floating in ~300px of empty teal. Tightened, the same emptiness reads
   as a deliberate band. Pages with a figure in the second column keep the full
   padding, because there the space is occupied. */
.hero-compact {
  padding-block: clamp(2.25rem, 1.5rem + 1.5vw, 3rem) clamp(2.5rem, 3vw, 3.25rem);
}
/* The 33rem cap on .hero-lead is sized for the home page, where the lead sits in
   a 7fr column beside the chart. On a compact hero there is no second column, so
   the same cap left the sub-heading wrapping inside a third of a 1140px band. */
.hero-compact .hero-lead { max-width: 46rem; }

.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  column-gap: clamp(2.5rem, 6vw, 5.5rem);
  align-items: end;
}
/* Explicit line placement rather than `grid-template-areas`, because the figure
   spans both rows and a named area cannot overlap the headline's full-width one.
   The headline keeps that full-width row: at 88px "Where revenue" needs 609px and
   the 7fr copy column gives 568px, so inside the column it broke after "Where".
   The chart used to sit only in the lower-right quadrant, leaving a dead band of
   teal beside the headline. Spanning both rows costs nothing measured: hero height
   is identical at 662px and .hero-lead is unchanged at 528px over 4 lines.
   Rejected on measurement: 6fr/6fr columns. It takes the chart 405 to 486px but
   costs the lead 42px, dropping it to 55 characters over 5 lines and pushing the
   hero 31px taller. */
.hero-grid > h1 { grid-column: 1 / -1; grid-row: 1; }
.hero-grid > .hero-copy { grid-column: 1; grid-row: 2; }
.hero-grid > .hero-figure { grid-column: 2; grid-row: 1 / 3; align-self: center; }

/* The model diagram wants more width than the hero chart and reads best optically
   centred against a two-line h1, not baseline-aligned to it. */
.hero-grid-model {
  grid-template-columns: minmax(0, 6fr) minmax(0, 6fr);
  align-items: center;
}
/* It also stacks a lot earlier than the hero chart does (56rem). The diagram has
   a hard minimum: entry label + three stage labels + six connectors on one line.
   Between roughly 900 and 1150px a 6fr column is narrower than that and the last
   stage was being clipped. Stacking early costs nothing, since the figure then
   has the full container and the copy above it is short. */
@media (max-width: 72rem) {
  .hero-grid-model { grid-template-columns: minmax(0, 1fr); align-items: start; }
  .hero-grid-model .model-figure { max-width: 36rem; }
}

.hero h1 {
  font-family: var(--font-display);
  font-weight: 640;
  font-size: clamp(2.875rem, 2rem + 4.2vw, 5.5rem);
  line-height: 1.02;
  letter-spacing: -0.025em;
  /* Wide enough for "Where revenue" on one line, narrow enough that the whole
     phrase never fits on one: at full container width it would, by 22px, which is
     too little slack to hold across viewports. Two lines is the intended shape. */
  max-width: 15ch;
  margin-bottom: var(--s-6);
}

/* one accent moment per section: the marked word on dark surfaces */
.accent-word { color: var(--ochre-bright); }

.hero-lead {
  margin-top: var(--s-6);
  max-width: 33rem;
  font-size: 1.125rem;
  line-height: 1.7;
  letter-spacing: 0.01em;
  color: var(--paper-muted);
}

.hero-actions {
  margin-top: var(--s-6);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
}

.hero-qualifier {
  margin-top: var(--s-6);
  /* hero body copy. Mono is scoped by element ROLE (caption / attribution / label /
     metric), never by how sentence-like the text is: see DESIGN.md. */
  font-family: var(--font-body);
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--paper-muted);
  /* 36rem is .hero-lead's 33rem plus 3rem, and that relationship is the reason for
     the cap: the qualifier's right edge sits a fixed 48px proud of the lead's, so
     the two read as one block with a slightly wider foot. The old 34rem was only
     16px proud, which reads as a mis-set edge rather than a deliberate one.
     It is inert where the column is narrower: at 1512 the copy track is 567px and
     the qualifier takes all of it, 39px past the lead; at 1024 and below both fill
     the column and the edges go flush. It binds in the stacked layout, where the
     column is 706px and uncapping put the qualifier 178px past the lead, which is
     no longer a foot, it is a second column. */
  max-width: 36rem;
}

/* hero figure */
.hero-figure { margin: 0; }
.hero-figure svg { display: block; width: 100%; height: auto; }
.hero-figure .axis {
  stroke: color-mix(in oklab, var(--paper) 30%, transparent);
  stroke-width: 1;
}
.line-adhoc {
  fill: none;
  stroke: color-mix(in oklab, var(--paper) 52%, var(--teal));
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.line-compound {
  fill: none;
  stroke: var(--ochre-bright);
  stroke-width: 2.5;
  stroke-linecap: round;
}
.dot-compound { fill: var(--ochre-bright); }
.hero-figure figcaption {
  margin-top: var(--s-4);
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  letter-spacing: 0.02em;
  color: var(--paper-muted);
}

/* ==== /booked: the arrival ====
   The one page on the site where something has actually been completed, and it
   was dressed as a policy page: paper ground, transparent header, no mark, no
   accent. Every route into it is teal at the moment of the click.

   The gesture is the home hero's compounding curve, cropped to its rise and
   landing on the dot: same shape, same ochre, same draw-then-arrive cadence.
   It is not decoration. The argument the whole site makes is that a line either
   flattens or compounds from a fixed starting point, and booking the call is the
   moment a starting point exists. A tick would have said "form submitted"; this
   says what the practice says. Small (232px) on purpose, because a confirmation
   should acknowledge at this register, not celebrate.

   No flat foil beside it. The comparison is the home page's job; here there is
   one line because there is one thing that happened. */
.hero-booked h1 { margin-bottom: var(--s-6); }
.booked-arc {
  display: block;
  width: 232px;
  max-width: 100%;
  height: auto;
}
.booked-line {
  fill: none;
  /* --ochre-bright, not --ochre: true Red Ochre is ~1.6:1 on teal. Same token
     and the same 2.25 stroke weight as .line-compound renders at on the home
     page, so the two read as one gesture at two sizes. */
  stroke: var(--ochre-bright);
  stroke-width: 2.25;
  stroke-linecap: round;
}
.booked-dot { fill: var(--ochre-bright); }

/* The slot itself, filled by /js/booked.js. No `display` declared, so the
   `hidden` attribute still hides it when there is nothing to say; margin is left
   to `.prose p`, which outranks this selector anyway. */
.booked-slot {
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 2.2vw, 1.5rem);
  line-height: 1.3;
  color: var(--teal);
}

@media (scripting: enabled) {
  /* One orchestrated entrance, on the one surface that earns it: headline, then
     the line draws, then the dot lands as it finishes. 225 is the measured path
     length (223.31, rounded up).
     Resting state is the drawn line and the visible dot, with the undrawn state
     living in the keyframe `from`, the same rule .line-compound is written to,
     so a browser that suppresses animation gets a finished figure rather than a
     blank one. Verified under prefers-reduced-motion and under a forced
     animation:none: dashoffset 0, dot opacity 1, in both. */
  .hero-booked h1 { animation: rise 700ms var(--ease-out) both; }
  .booked-line {
    stroke-dasharray: 225;
    stroke-dashoffset: 0;
    animation: draw-booked 800ms var(--ease-out) 200ms both;
  }
  .booked-dot {
    transform-box: fill-box;
    transform-origin: center;
    animation: dot-arrive 420ms var(--ease-out) 880ms backwards;
  }
}
@keyframes draw-booked { from { stroke-dashoffset: 225; } to { stroke-dashoffset: 0; } }

/* ==== Revenue Motion model (/revenue-motion hero) ====
   A two-dimensional diagram, not a list. The geometry states the relationships
   so the labels do not have to: go-to-market design feeds the spine from the
   left; the motion runs across it; compensation spans it from above and drops
   onto all three stages; forecasting and analytics spans it from below and
   rises to all three. Take a layer away and the arms it owned are gone, which
   is the "one system" argument made visually.

   Ochre is on the spine only. It is the motion, which is the thing the practice
   is named for. Ochre-bright, not ochre, because the ground is teal (true ochre
   is 1.61:1 there). Everything else is a paper-toned hairline at 3.3:1.

   Alignment mechanism worth keeping: the three nodes sit in three equal 1fr
   tracks, and each node is a flex row of [link][label][link] with both links at
   flex:1. That centres every label in its own track (so the arm grids, also
   3x1fr, land dead on the labels) AND makes the two links either side of a
   track boundary meet, so the spine draws as one continuous rule without any
   element needing to know a label's width.

   Hairline weights match the hero chart. No boxes, no radii, no icons, no fills.
   The entrance is at the foot of this block; every part of the figure is fully
   legible with animation disabled, which is the constraint it is written to. */

.model-figure { margin: 0; }

.model {
  /* --arm is the ROW height. The arm row butts straight onto the nodes row, and
     the chevron is translated half its own height past the line's end, so with
     no gap the arrowhead landed inside the text's line box and touched the
     glyphs. --arm-gap is padded off the text end of each arm, so the visible
     connector is --arm minus --arm-gap. Bump the two together. */
  --arm: 2.625rem;
  --arm-gap: 0.625rem;
  /* One weight for every structural line: arms, entry arrow, entry elbow. The
     elbow's position is derived from it, so a literal here silently steps the
     elbow off the spine. Ladder: ochre 2px > structure > rails 1px. */
  --structure-w: 1.5px;
  /* Two weights, two jobs. The rails are context: they say a layer spans the
     whole motion, and a reader takes that from their length, not their weight.
     The arms are the claim — the layer touching every stage — so they carry
     more ink. One value for both made the diagram read flat. */
  --model-hair: color-mix(in oklab, var(--paper) 32%, transparent);
  --model-arm:  color-mix(in oklab, var(--paper) 68%, transparent);
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto var(--arm) auto var(--arm) auto;
  grid-template-areas:
    ".     layer-top"
    ".     arms-down"
    "entry nodes"
    ".     arms-up"
    ".     layer-bottom";
  align-items: center;
}
.model-layer-top { grid-area: layer-top; }
.model-arms-down { grid-area: arms-down; }
.model-entry { grid-area: entry; }
.model-nodes { grid-area: nodes; }
.model-arms-up { grid-area: arms-up; }
.model-layer-bottom { grid-area: layer-bottom; }

/* The two layers. The rule is the span: it reaches across all three stages,
   which is what makes the arms below it read as "acts on every one of these".
   Mono, because these ARE labels and that is the role DESIGN.md scopes mono to.
   The axis has to be family, not weight: they sit directly under .hero-lead, and
   at 16px sans in --paper-muted they matched the lead's own size, colour and
   register, so they read as another line of body copy rather than as parts of a
   diagram. Weight cannot fix that — going bolder was tried and it outweighed the
   spine, which is what put the eye on "Strategy and insights" first. Small
   uppercase mono is distinct from prose without being louder than a stage. */
.model-entry,
.model-layer {
  margin: 0;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1.3;
  /* Muted, level with the figcaption: mono meta runs at one tier in this figure.
     Full paper put them at 6.91:1, the same strength as the h1 and the three
     stages, so "OPERATIONS AND PROCESSES" competed with the node row for first
     read. Colour was only carrying the separation from body copy before the
     family changed; family, case and size carry it now, so this is free. */
  color: var(--paper-muted);
}
.model-layer { text-align: center; }
.model-layer-top { border-bottom: 1px solid var(--model-hair); padding-bottom: var(--s-2); }
.model-layer-bottom { border-top: 1px solid var(--model-hair); padding-top: var(--s-2); }
/* Room between the entry label and the connector that leaves it. */
.model-entry { padding-right: var(--s-3); }
/* Only used once the entry label moves above the spine; see the 40rem block. */
.model-entry-drop { display: none; }

/* Arms: one per stage, so a layer visibly touches every stage rather than
   sitting near them. Chevrons carry the direction (compensation acts downward,
   analytics observes upward), which is the part a caption used to have to say. */
.model-arms {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-self: stretch;
  height: 100%;
}
/* 1.5px, not 1px: the arms have to outweigh the rules, and matching their value
   alone left both at one device pixel on a 1x display, where the distinction
   collapses to colour. Weight ladder is ochre 2px > structure 1.5px > rails 1px. */
.model-arms > span {
  position: relative;
  justify-self: center;
  width: var(--structure-w);
  height: 100%;
  background: var(--model-arm);
}
.model-arms > span::after {
  content: "";
  position: absolute;
  left: 50%;
  width: 7px;
  height: 7px;
  border-right: 1.5px solid var(--model-arm);
  border-bottom: 1.5px solid var(--model-arm);
}
/* The gap goes at the text end only: the rule end stays flush, the arrowhead
   stops short. box-sizing is border-box, so the span's height:100% shortens. */
.model-arms-down { padding-bottom: var(--arm-gap); }
.model-arms-up { padding-top: var(--arm-gap); }
.model-arms-down > span::after { bottom: 0; transform: translate(-50%, 50%) rotate(45deg); }
.model-arms-up > span::after { top: 0; transform: translate(-50%, -50%) rotate(-135deg); }

/* The spine. */
.model-nodes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.model-nodes li {
  display: flex;
  align-items: center;
  min-width: 0;
}
.model-node {
  flex: 0 0 auto;
  padding-inline: 0.5rem;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.4375rem;
  letter-spacing: -0.01em;
  line-height: 1.2;
  color: var(--paper);
}
/* The only element in the figure carrying real weight. At 1px it was the same
   ink as the rails and the ochre read as a tint rather than as the one spark. */
.model-link {
  flex: 1 1 0;
  min-width: 0.625rem;
  height: 2px;
  background: var(--ochre-bright);
  position: relative;
}
/* Every link that arrives at a stage gets the arrowhead; nothing leaves Grown. */
.model-link:first-child::after {
  content: "";
  position: absolute;
  right: 0;
  top: 50%;
  width: 8px;
  height: 8px;
  border-top: 2px solid var(--ochre-bright);
  border-right: 2px solid var(--ochre-bright);
  transform: translateY(-50%) rotate(45deg);
}
.model-nodes li:last-child .model-link:last-child { visibility: hidden; }
/* The entry connector is not part of the motion, so it is not ochre. It is
   still structure rather than context, so it takes the arm weight, not the
   rail's — at 2px the rail value read as a muddy smear. It sits one step below
   ochre so the motion keeps the heaviest line in the figure to itself. */
.model-nodes li:first-child .model-link:first-child {
  background: var(--model-arm);
  height: var(--structure-w);
}
.model-nodes li:first-child .model-link:first-child::after {
  border-top-color: var(--model-arm);
  border-right-color: var(--model-arm);
}

.model-figure figcaption {
  margin-top: var(--s-6);
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  line-height: 1.55;
  letter-spacing: 0.02em;
  color: var(--paper-muted);
}

/* The /revenue-motion entrance. Order is the argument, not decoration: go-to-market
   arrives from the left, the motion then runs left to right through Won, Kept and
   Grown, and only once the spine exists do the two layers appear and reach down and
   up to touch every stage. Watched once, it builds the same claim the geometry makes
   standing still. 1,120ms end to end.

   Mechanism is clip-path and transform throughout, so nothing reflows and nothing
   that carries meaning has its shape distorted: scaling an arm would have squashed
   its chevron, and the chevron is what says which way a layer acts. Every keyframe
   ends where the static rules above already put the element, so the whole figure is
   present with animations suppressed. Every inset stays in one unit across a pair of
   keyframes; the negative values hold the clip clear of the chevrons, which sit
   outside their arm's border box.

   Fill mode is `backwards`, not `both`. Backwards is the half that matters: it
   holds the undrawn state through the delay. Forwards would pin the `to` values
   after the run, and a held `transform: none` computes to an identity matrix and
   a held clip-path stays a real clip, both of which push the element down a
   different rasterisation path than the static rules do. Measured: with `both`
   the finished page and the animation-suppressed page differ by a few hundred
   antialiased pixels; with `backwards` the rendered result is byte-identical,
   because at rest the static rules are the only thing in effect. Every `to`
   already equals the static value, so nothing snaps at the end. */
@media (scripting: enabled) {
  .model-entry,
  .model-entry-drop   { animation: model-entry-in 400ms var(--ease-out) backwards; }
  .model-nodes        { animation: model-spine 620ms var(--ease-out) 180ms backwards; }
  .model-layer-top    { animation: model-layer-down 340ms var(--ease-out) 640ms backwards; }
  .model-arms-down    { animation: model-arms-down 300ms var(--ease-out) 740ms backwards; }
  .model-layer-bottom { animation: model-layer-up 340ms var(--ease-out) 720ms backwards; }
  .model-arms-up      { animation: model-arms-up 300ms var(--ease-out) 820ms backwards; }
}
@keyframes model-entry-in {
  from { opacity: 0; transform: translateX(-8px); }
  to   { opacity: 1; transform: none; }
}
@keyframes model-spine {
  from { clip-path: inset(-0.5rem 100% -0.5rem 0); }
  to   { clip-path: inset(-0.5rem 0% -0.5rem 0); }
}
/* The layers settle toward the spine, not away from it. */
@keyframes model-layer-down {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}
@keyframes model-layer-up {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
@keyframes model-arms-down {
  from { clip-path: inset(0% -10% 100% -10%); }
  to   { clip-path: inset(0% -10% -25% -10%); }
}
@keyframes model-arms-up {
  from { clip-path: inset(100% -10% 0% -10%); }
  to   { clip-path: inset(-25% -10% 0% -10%); }
}

/* Hero entrance. Gated on (scripting: enabled) rather than a JS-set class, so the
   site ships zero JavaScript. Browsers without the query never match and get the
   static hero, which is the correct fallback either way. */
@media (scripting: enabled) {
  /* The h1 left .hero-copy for its own grid row, so it is named here to keep the
     original cadence: headline first, then lead, actions, qualifier at 90ms steps. */
  .hero-grid > h1,
  .hero-copy > * { animation: rise 700ms var(--ease-out) both; }
  .hero-copy > :nth-child(1) { animation-delay: 90ms; }
  .hero-copy > :nth-child(2) { animation-delay: 180ms; }
  .hero-copy > :nth-child(3) { animation-delay: 270ms; }
  .hero-figure { animation: fade 900ms var(--ease-out) 250ms both; }
  /* The resting state of all three is VISIBLE, and the undrawn state lives in the
     keyframe's `from` with fill-mode `both` holding it through the delay. Written
     the other way round (a static stroke-dashoffset: 620 plus a `to` keyframe) the
     chart is blank for anything that suppresses animations without setting
     prefers-reduced-motion: animation-blocking extensions, some render pipelines.
     A reveal must enhance an already-visible default, never gate visibility. */
  /* Both dasharrays MUST stay longer than their own path, or the resting stroke
     breaks into a dash and a gap and the tail of the line is simply missing. The
     compound path is 709.4 units since it was redrawn off the logo arc (it was
     ~577), so 620 clipped the last 89 units, which is the crest and the payoff. */
  .line-compound {
    stroke-dasharray: 720;
    stroke-dashoffset: 0;
    animation: draw-compound 1100ms var(--ease-out) 400ms both;
  }
  .line-adhoc {
    stroke-dasharray: 480;
    stroke-dashoffset: 0;
    animation: draw-adhoc 900ms var(--ease-out) 350ms both;
  }
  /* The dot is where the compounding line ends, so it is the payoff of the one
     argument the home page is making. A fade alone had it materialise; arriving at
     full size off a small scale reads as the line landing on it. transform-box is
     what makes `center` mean the circle's own centre rather than the SVG's.
     `backwards`, not `both`, for the reason set out in the model entrance above:
     a held `transform: none` computes to an identity matrix, which re-rasterised
     the 7px dot's antialiasing and made the finished page differ from the
     animation-suppressed one by 243 pixels for no visible gain. */
  .dot-compound {
    transform-box: fill-box;
    transform-origin: center;
    animation: dot-arrive 420ms var(--ease-out) 1350ms backwards;
  }
}

@keyframes rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes draw-compound { from { stroke-dashoffset: 720; } to { stroke-dashoffset: 0; } }
@keyframes draw-adhoc    { from { stroke-dashoffset: 480; } to { stroke-dashoffset: 0; } }
@keyframes dot-arrive    { from { opacity: 0; transform: scale(0.4); } to { opacity: 1; transform: none; } }

/* ==== sections ==== */

.section { padding-block: var(--section-pad); }

/* Two same-background sections in a row stack their padding into a dead zone that
   reads as a mistake at desktop widths. A hairline turns it into a deliberate break. */
.section-ruled { padding-block-start: 0; }
.section-ruled > .container { border-top: 1px solid var(--hairline); padding-block-start: var(--section-pad); }

/* Tinted band. Keeps the paper/tint alternation working on the inner pages. */
.section-tint { background: var(--paper-tint); }

/* Display face (Mona Sans) on section headings, not just the hero. It was reaching
   only .nav-wordmark and .hero h1, so the brand voice died above the fold.
   DESIGN.md permits display at large/bold and these are 28-42px at 700. */
.section h2, .closing h2 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.75rem, 1.35rem + 1.8vw, 2.625rem);
  line-height: 1.12;
  letter-spacing: -0.015em;
}

.prose { max-width: 65ch; }
.prose h2 { margin-bottom: var(--s-4); }
.prose p { margin-bottom: var(--s-4); }
.prose p:last-child { margin-bottom: 0; }

.split-grid {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: var(--s-6) clamp(2.5rem, 6vw, 5rem);
  align-items: start;
}
.split-grid h2 { max-width: 16ch; }

.section-intro {
  margin-top: var(--s-3);
  color: var(--ink-muted);
  /* no measure cap: these are one-line intros and the 60ch cap wrapped them early */
}

/* ==== work list ==== */

.work-list { margin: var(--block-gap) 0 0; }
.work-row {
  display: grid;
  /* 21rem, not 17rem. Measured with Range.getClientRects, not the dt's box (the
     dt stretches to the row height, so a box measurement reports a wrap that is
     not there). The four terms on /revenue-motion need 247, 275, 310 and 325px;
     272px wrapped three of them and 320px still wrapped the longest by 5px. */
  grid-template-columns: minmax(11rem, 21rem) minmax(0, 1fr);
  gap: var(--s-4) var(--s-8);
  padding-block: 0.875rem;
  border-top: 1px solid var(--hairline);
}
.work-row:last-child { border-bottom: 1px solid var(--hairline); }
.work-row dt {
  font-weight: 650;
  font-size: 1.125rem;
  letter-spacing: -0.005em;
}
.work-row dd {
  margin: 0;
  color: var(--ink-muted);
  /* fills the description column; the 56ch cap forced a wrap the grid didn't need */
}
/* The contents of one area, as a middot list. Meta by role, which is what earns
   the mono face here: same treatment as .proof-context, which is the same shape
   of string. teal-support is 4.91:1 on paper and this only ever renders on
   /#work, a plain .section; it would fail on a .section-tint band (4.44:1). */
.work-covers {
  display: block;
  margin-top: 0.4rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1.65;
  letter-spacing: 0.02em;
  color: var(--teal-support);
  /* balance, not pretty. These are 1-3 line meta strips, well inside Chrome's
     6-line balance cap. `pretty` only adjusts the last line, which cleared the
     dangling middots but still left "· sequencing" alone at 12.9% of a 692px
     measure on the post-acquisition row; balance splits the whole block. */
  text-wrap: balance;
}

/* ==== routes ====
   Four ways in, differentiated by commitment. Deliberately rows, not a 3-up card
   grid. No bar: see .route-duration. */
/* No max-width. It carried 52ch and it was the last cap on the site acting
   entirely alone, holding a full-width block to 52% of its parent with 505px
   spare. Removing it moves nothing: the line is 36 characters and was never
   near the cap, so the BOX grows and the text does not. Removed anyway, per
   brand/DESIGN.md "Measure and width" — a cap with no reason a reader can see
   is what the next person mistakes for a decision. */
.routes-default {
  margin-top: var(--s-3);
  font-weight: 650;
}
.routes {
  list-style: none;
  margin: var(--s-6) 0 0;
  padding: 0;
  display: grid;
  gap: var(--s-4);
}
.route {
  display: grid;
  /* 20rem, not the 15rem the bar needed: the labelled duration runs to 310px on
     "Typical duration · Scoped to the decision", and at 240px the value wrapped
     flush under the label, where it read as a second label. The body column has
     the slack, since it is capped at 52ch well inside the container. */
  grid-template-columns: minmax(0, 20rem) minmax(0, 1fr);
  gap: var(--s-2) var(--s-8);
  align-items: baseline;
  padding-top: var(--s-4);
  border-top: 1px solid var(--hairline);
}
.route-head { display: flex; flex-direction: column; gap: 0.15rem; }
/* Body face, not display: these are 17px in-row labels, the tier below a section
   heading. The display face at this size reads as noise rather than voice. */
.route h3,
.route h4 {
  font-family: var(--font-body);
  font-size: 1.0625rem;
  font-weight: 650;
  letter-spacing: -0.005em;
  color: var(--teal);
}
/* The duration is the only commitment signal now, so it carries a label. The bar
   it replaces drew a quantity that did not exist: unlabelled, no axis, no unit,
   and the longest fill belonged to the route its own text calls light touch. */
.route-duration {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.03em;
  /* --teal, not --teal-support: support is 4.44:1 on the tinted band, just under
     AA. Hierarchy against the heading comes from size and weight instead. */
  color: var(--teal);
}
.route-duration-label {
  color: var(--ink-muted);
}
.route-duration-label::after { content: " · "; }
/* Direct child only: .route-duration is a descendant too, and this rule's colour
   and size would win on specificity. */
.route > p {
  grid-column: 2;
  grid-row: 1;
  color: var(--ink-muted);
  font-size: 1rem;
  /* 74ch, not 52ch. The body column is ~788px at desktop and the 52ch cap was
     holding the text to ~470px of it, so every route wrapped a line or two
     earlier than the grid required. 74ch is the top of the readable measure. */
  max-width: 74ch;
}

/* ==== /schedule: the two booking options ====
   The site's default is hairline rules, not cards (see .work-row). These are
   boxes because the whole box is the click target: a bordered surface is what
   says "one thing to click", where a rule only groups. Colour is inherited
   rather than left to the UA, so no default link blue reaches anything inside. */

.picker {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-4);
}
/* The li is the grid item and stretches; the anchor has to be told to fill it,
   or two cards with different body lengths end up different heights. */
.picker li { display: grid; }
.picker-card {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding: var(--s-6);
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  background: var(--paper-raised);
  color: inherit;
  text-decoration: none;
  /* opacity, and the (hover: hover) gate below, for the reasons given on .btn:
     this is the same booking trigger in a box, so it takes [data-cal-pending]
     and it holds a tapped hover state the same way. */
  transition: border-color var(--t-fast) var(--ease-out),
              background-color var(--t-fast) var(--ease-out),
              opacity var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out);
}
@media (hover: hover) {
  .picker-card:hover {
    border-color: var(--teal);
    background: var(--paper-bright);
    transform: translateY(-1px);
  }
}
.picker-card:active { transform: translateY(1px); }
.picker-card h2 {
  margin: 0;
  font-family: var(--font-body);
  font-size: 1.0625rem;
  font-weight: 650;
  letter-spacing: -0.005em;
  color: var(--teal);
}
.picker-card p {
  margin: 0;
  color: var(--ink-muted);
  font-size: 1rem;
}
/* auto margin, not a fixed gap: it pins the cue to the bottom of whichever card
   is taller, so both cues sit on one line however long the body copy runs. */
.picker-go {
  margin-top: auto;
  padding-top: var(--s-4);
  font-weight: 650;
  color: var(--teal);
}
.picker-go::after { content: " \2192"; }
/* The disclosure that lets the booking skip a consent step. Quiet, but on the
   page rather than behind a link, because it describes what the boxes above it
   do. Width is a percentage of the container, not a measure in ch, so it holds
   the same proportion of the page as the boxes above rather than tracking its
   own font size; it is reset to full width on narrow viewports, where 75% of a
   350px column would run this to seven lines. */
.picker-note {
  margin-top: var(--s-6);
  max-width: 75%;
  color: var(--ink-muted);
  font-size: 0.8125rem;
  line-height: 1.6;
}

/* ==== proof ==== */

.proof-list {
  margin: var(--block-gap) 0 0;
  display: grid;
  gap: var(--s-6);
}
.proof-list:first-child {
  /* The top margin spaces the list from the heading above it. On /faq the list
     opens its section (the H1 sits in the hero band), so there is nothing to
     space from and the gap renders as dead air. */
  margin-top: 0;
}
/* Bar and sector label share a full-width row above the heading/body row, so the
   body's first line starts level with its own h3 instead of 50-70px above it. */
.proof-meta { grid-column: 1 / -1; }
.proof-bar {
  /* the logo crossbar at small scale — the section's ochre marker */
  display: block;
  width: 22px;
  height: 6px;
  background: var(--ochre);
  margin-bottom: var(--s-4);
}
/* Full container width, split heading | body (same shape as .work-row), so the
   text stops wrapping short inside a 2/3-width column. */
/* 7fr 5fr, not 5fr 7fr. Auto-placement puts the heading and the body in column
   one and the stat and tags in column two, so the old ratio gave all the prose
   the narrow track and left 74-130px of dead space under the stat. */
.proof-item {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  gap: var(--s-2) clamp(2rem, 4vw, 3.5rem);
  align-items: start;
}
/* Explicit placement: the heading and the prose are the left column, the stat
   block is the right one spanning both their rows. Without this, .proof-side
   would auto-place beside the heading and push the body into column two. */
.proof-item > h3,
.proof-item > .proof-body { grid-column: 1; }
.proof-side { grid-column: 2; grid-row: 2 / span 2; }
.proof-context {
  font-family: var(--font-mono); /* sector/size label = meta, legitimate mono use */
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  color: var(--teal-support);
  /* No margin-bottom: the grid row gap now does that job, and doubling them
     pushed the meta row away from the heading it belongs to. */
}
/* 21px at 700 is large-bold, so the display face applies here too. It also keeps
   the track-record headings on / and the questions on /faq in one voice: they are
   the same tier, they just sit at different heading levels per page structure. */
.proof-item h3 {
  font-family: var(--font-display);
  font-size: 1.3125rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  text-wrap: balance;
}
/* FAQ questions follow the page h1 directly, so they are h2 by document
   structure. Size them as proof headings, not as section headings. */
.proof-item h2 {
  font-size: 1.3125rem;
  font-weight: 700;
  line-height: 1.35;
  letter-spacing: -0.01em;
}
.proof-body { color: var(--ink-muted); }

/* /faq reuses .proof-item, but an FAQ entry has only two children, so
   auto-placement INVERTS the rule above: the one-line question takes the 7fr
   track and the answer drops into the 5fr one. The longest answer ran 48
   characters a line over 17 lines inside 418px, with 586px of empty container
   beside it. Scoped to h2, which is the FAQ marker per the rule at .proof-item h2.
   74ch is the measure already used for the body column elsewhere in this file. */
.proof-item > h2 { grid-column: 1 / -1; }
.proof-item > h2 ~ .proof-body { grid-column: 1 / -1; max-width: 74ch; }

/* FAQ question anchors: a reader can send a URL that lands on one answer.
   Revealed on hover where hover exists, always present where it does not, and
   always keyboard reachable. It sits inline inside the heading text, so WCAG
   2.5.8's inline exception covers the target size. */
.q-anchor {
  /* padding, not margin: it is transparent, so the gap looks the same and the
     hit area clears 24px wide without the 2.5.8 inline exception being needed. */
  padding-inline: 0.4em;
  color: var(--teal);
  font-weight: 400;
  text-decoration: none;
}
.q-anchor:hover { text-decoration: underline; text-underline-offset: 3px; }
@media (hover: hover) {
  .q-anchor { opacity: 0; transition: opacity var(--t-fast) var(--ease-out); }
  .proof-item:hover .q-anchor,
  .q-anchor:focus-visible { opacity: 1; }
}

/* ==== issues, mechanism and outcomes ====
   The buyer's words on the left, how it actually shows up in the middle, what
   changes on the right. Same rhythm as .work-row: hairline rules, no icons, no
   card grid. */

.issues {
  width: 100%;
  margin-top: var(--block-gap);
  border-collapse: collapse;
  text-align: left;
}
.issues th {
  padding: 0 var(--s-8) var(--s-3) 0;
  font-family: var(--font-mono); /* column label = meta, legitimate mono use */
  font-size: 0.75rem;
  font-weight: 400;
  letter-spacing: 0.03em;
  /* --teal, not --teal-support: support is 4.44:1 on the tinted band, under the
     4.5:1 floor at this size. Same call as .route-duration. */
  color: var(--teal);
}
/* The complaint is a short quoted phrase; the mechanism and the outcome are
   both full sentences and split the rest of the row evenly between them. */
.issues th:nth-child(1) { width: 26%; }
.issues th:nth-child(2) { width: 37%; }
.issues th:nth-child(3) { width: 37%; }
.issues td {
  padding: 1.125rem var(--s-8) 1.125rem 0;
  border-top: 1px solid var(--hairline);
  vertical-align: top;
}
.issues tr:last-child td { border-bottom: 1px solid var(--hairline); }
/* The remedy carries the weight, not the complaint. It was the other way round:
   the buyer's words at 650 in full ink against the outcome at 400 in muted ink,
   so the problem outranked the answer on the one element that is meant to show
   diagnosis. The mechanism sits at the same weight as the outcome and full ink
   (no override needed): it is a plain statement of fact, not the buyer's quoted
   words and not the payoff, so it takes neither the muted nor the bold treatment.
   All three columns stay above 4.5:1 on the tinted band. */
.issues .issue { color: var(--ink-muted); }
.issues .outcome { font-weight: 650; letter-spacing: -0.005em; }

/* ==== about ==== */

/* 8fr/4fr, up from 7fr/5fr. The prose was 572px of a 1060px grid, 54%, with an
   inert .prose 65ch cap sitting 104px ABOVE the track: the cap was never the
   constraint here, the ratio was, which is why widening caps had stopped
   working. 8fr lands the prose at 653px, identical to .split-grid's prose track,
   so there is now one prose measure across the site rather than two.
   Measured against 9fr/3fr and two fixed-aside variants. 9fr was rejected on the
   portrait, not the text: 245px stops reading as the editorial portrait the
   About section exists to carry and becomes a byline thumbnail. 327px holds.
   81 characters a line, and that is fine rather than tolerated: when this grid
   stacks below 56rem the same prose already runs 96-101 characters on a 791px
   track, so the site ships far wider than this today. See brand/DESIGN.md
   "Measure and width" — the character count is not the test.
   Side effect worth having: on /about the two columns ended 84px apart and now
   end 10px apart. */
.about-grid {
  display: grid;
  /* 17.5rem floor on the aside, not 0. 4fr alone put the portrait at 258px in
     the 897-980px band, which is still two columns because the stack point is
     896px, and 258 is where it starts reading as a thumbnail (it was 322px at
     5fr, so this ratio change caused it). The floor only binds in that band; at
     1024 and up 4fr already exceeds it, so nothing above changes. A half-screen
     window on a 1920 monitor is 960px, which is why the band is worth holding. */
  grid-template-columns: minmax(0, 8fr) minmax(17.5rem, 4fr);
  gap: clamp(2.5rem, 6vw, 5rem);
  align-items: start;
}
/* Unconditional, not just once stacked. The track governs the measure at every
   width; leaving the cap in place above a narrower track is what made the next
   ratio change silently do nothing. */
.about-grid > .prose { max-width: none; }

.about-aside {
  display: flex;
  flex-direction: column;
  gap: var(--s-6);
}
/* Portrait: paper-toned rectangle aligned to the aside column, not an avatar circle.
   ponytail: no duotone filter until the real photo lands and can be tuned to it. */
.portrait { margin: 0; }
.portrait img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--r-md);
  border: 1px solid var(--hairline);
}
.portrait figcaption {
  margin-top: var(--s-3);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1.5;
  letter-spacing: 0.02em;
  color: var(--teal-support);
}

/* Full width of the aside. It was 66%, pushed right, so it would read as a
   quotation set against the column rather than another paragraph in it. That
   bought 139px of indent and cost a 269px line box: six lines of three or four
   words, which reads as a stack of fragments, not a quotation. The crossbar
   above it already does the signalling the indent was there for. Text stays
   left-aligned: ragged-left display type is hard to read. */
.pull-quote {
  /* margin:0 is load-bearing, not tidying. Chrome's UA sheet gives <blockquote>
     margin-inline: 40px. The old rule zeroed it as a side effect of
     `margin: 0 0 0 auto`; dropping that for `width: 100%` alone let the 40px
     back in, which pushed the quote off the aside's left spine and ran it 40px
     past the container's right edge. */
  margin: 0;
  width: 100%;
  padding-top: var(--s-2);
}
.crossbar {
  display: block;
  width: 46px;
  height: 10px;
  background: var(--ochre);
  margin-bottom: var(--s-4);
}
/* Was a fourth face, a serif carried purely for this one paragraph: 131KB to set
   two routes, and one of the generative-default serifs the brand rejects.
   Italic, so it reads as a quotation rather than a stray large paragraph, and
   Schibsted Grotesk rather than Mona Sans because only Schibsted ships a real
   italic here. Mona Sans is upright-only in this build, so `font-style: italic`
   on the display face gets a synthesised oblique, which is visibly wrong at
   24-32px. The weight drop and the size still do the separating. */
.pull-quote p {
  font-family: var(--font-body);
  font-style: italic;
  font-weight: 300;
  /* One step down from 1.75rem. At 66% of a 408px aside the line box is 269px,
     and 28px type put three words on it over six lines. 24px holds four to five
     and stops the quote reading as a stack of fragments. */
  font-size: clamp(1.25rem, 1rem + 0.85vw, 1.5rem);
  line-height: 1.42;
  letter-spacing: -0.008em;
}

/* ==== career path (/about aside) ====
   The functions the practice came out of, as a sequence rather than a sentence.
   A single hairline spine with a tick into each label: no cards, no boxes. The
   final tick is ochre for recency, not arrival. It listed "Revenue leadership"
   as a fifth entry until 2026-08-21, which made four functions read as a climb
   to a title and claimed leadership in pre-sales and sales, which is untrue.
   Leadership is Credentials' job, where it can name the employer and the scope
   and be checked. */

.path { margin: 0; }
.path-label {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  color: var(--teal-support);
}
.path-list {
  list-style: none;
  margin: var(--s-4) 0 0;
  padding: 0 0 0 var(--s-6);
  border-left: 1px solid var(--hairline);
}
.path-list li {
  position: relative;
  padding-block: 0.4375rem;
  font-size: 1.0625rem;
  line-height: 1.35;
}
.path-list li:last-child { padding-bottom: 0; font-weight: 650; }
.path-list li::before {
  content: "";
  position: absolute;
  left: calc(-1 * var(--s-6));
  top: calc(0.4375rem + 0.66em);
  width: 0.875rem;
  height: 1px;
  background: var(--hairline);
}
/* 4.3:1 on paper: graphic use only, which is what this is. */
.path-list li:last-child::before {
  width: 1.25rem;
  height: 2px;
  background: var(--ochre);
}
/* .pull-quote cite removed with the element it styled: it duplicated the
   portrait figcaption verbatim once the caption's second line came off. */

/* ==== closing ==== */

.closing {
  background:
    radial-gradient(110% 100% at 20% 100%, color-mix(in oklab, var(--teal) 86%, var(--ink)) 0%, transparent 55%),
    var(--teal);
  color: var(--paper);
  padding-block: var(--section-pad);
}
/* NO max-width on the children. The container is the measure: .closing-inner is
   also a .container, so 1060px at desktop, and the block already starts on the
   site's left spine without a cap because .container is not centring its
   children.

   History, because this is the block the house rule came out of. The cap went
   44rem -> 37rem -> 42rem -> 52rem, four values, every one of them chasing the
   65-75ch book rule and every one of them still reading as a narrow column in a
   wide empty band. At 42rem it also orphaned "it is." onto a third line. See
   brand/DESIGN.md "Measure and width": this is a two-paragraph call to action
   read in a few seconds on the way to a button, not prose anyone settles into,
   so the character count is not the test. Whether a reader can see the emptiness
   is the test, and they could.

   Uncapped, the first paragraph runs on ONE line at desktop, roughly 110
   characters. Deliberate. text-wrap: balance below keeps the second honest, and
   below about a 1060px viewport the container narrows and both wrap normally. */
.closing p {
  margin-top: var(--s-6);
  font-size: 1.125rem;
  line-height: 1.7;
  letter-spacing: 0.01em;
  color: var(--paper-muted);
  /* Overrides the global `p { text-wrap: pretty }` above, and that global is the
     whole point: these paragraphs were ALREADY on pretty and it was not enough.
     Measured across four caps, pretty left "have already tried." alone at 19% of
     the line, and matched the no-property case in 5 of 6 pairs for the obvious
     reason that on a <p> they are the same computed value. Balance clears it at
     every cap.
     Second block on the site where balance beat pretty (see .work-covers, which
     is a <span> so the global never reached it at all). The rule: pretty only
     adjusts the last line, so it is for prose; balance splits the whole block,
     so it is for short ones. Two paragraphs of 110 and 190 characters sit well
     inside Chrome's 6-line balance limit.
     It also makes the rag self-correcting, which matters because this copy is a
     CMS field. With balance on, 52 / 56 / 60rem render byte-identical, so the
     cap is no longer what holds the block together and the next copy edit cannot
     silently reintroduce an orphan. */
  text-wrap: balance;
}
.closing-actions {
  margin-top: var(--s-6);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
}

/* ==== footer ==== */

.footer {
  background: var(--ink);
  color: color-mix(in oklab, var(--paper) 80%, var(--ink));
  padding-block: var(--s-12);
  font-size: 0.9375rem;
  --mark-legs: var(--paper);
  --mark-bar: var(--mark-counter-dark);
}
.footer-inner {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--s-8);
}
.footer-brand { display: flex; gap: var(--s-4); align-items: flex-start; }
.footer-brand p { line-height: 1.6; }
.footer-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 var(--s-6);
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  letter-spacing: 0.02em;
}
.footer-meta a {
  display: inline-flex;
  align-items: center;
  min-height: 44px; /* tap target */
}
.footer a {
  color: color-mix(in oklab, var(--paper) 88%, var(--ink));
  text-decoration: none;
  transition: color var(--t-fast) var(--ease-out);
}
.footer a:hover { color: var(--paper-bright); text-decoration: underline; text-underline-offset: 3px; }

/* ==== plain pages (privacy) ==== */

.nav-plain {
  position: static;
  background: transparent;
  border-bottom: 1px solid var(--hairline);
  --mark-legs: var(--teal);
  --mark-bar: var(--ochre);
}
.nav-plain .nav-wordmark { color: var(--ink); }
/* Matches .nav-brand's 44px: "Back to the site" is a bar-level control, not a
   link in a sentence, and a 21px line box is under the 2.5.8 floor. */
.nav-plain .text-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

.legal { padding-block: var(--s-8) var(--s-24); }
.legal-updated {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted); /* was --greige at 3.24:1, below the 4.5:1 floor for 13px */
  margin-bottom: var(--s-3);
}
.legal h1 {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: clamp(1.75rem, 1.35rem + 1.8vw, 2.375rem);
  letter-spacing: -0.015em;
  margin-bottom: var(--s-8);
}
.legal h2 {
  font-size: 1.25rem;
  font-weight: 700;
  margin: var(--s-8) 0 var(--s-3);
}

/* ==== responsive ==== */

/* Jump-links crowd the bar before the CTA does; drop them first, keep the CTA. */
@media (max-width: 52rem) {
  .nav-links { display: none; }
  .nav-menu { display: block; }
}

@media (max-width: 56rem) {
  /* .proof-side carries an explicit column and a two-row span at desktop; both
     have to go with the second column or it lands outside the grid. */
  .proof-item { grid-template-columns: 1fr; gap: var(--s-3); }
  .proof-side { grid-column: 1; grid-row: auto; }
  .route { grid-template-columns: 1fr; gap: var(--s-2); }
  .route > p { grid-column: 1; grid-row: auto; }
  .picker { grid-template-columns: 1fr; }
  .picker-note { max-width: none; }
  /* `grid-template-columns: 1fr` alone does NOT collapse this grid: the children
     carry explicit `grid-column` / `grid-row` from the unscoped rule, and a child
     placed in column 2 recreates the second track as an implicit one. They have to
     be reset with the container. `grid-area: auto` is the shorthand that clears all
     four placement longhands at once. Same bug, same fix, as `.hero-grid-model`
     below; that one was written first and this one was missed, which left the phone
     hero at two columns down to 320px (a 14ch measure and a three-line CTA label).
     Historic note: both rules once fought `grid-template-areas` instead, which is
     gone from the base rule as of the two-row figure span. */
  .hero-grid { grid-template-columns: 1fr; align-items: start; row-gap: var(--s-8); }
  .hero-grid > h1,
  .hero-grid > .hero-copy,
  .hero-grid > .hero-figure { grid-area: auto; }
  .split-grid { grid-template-columns: 1fr; gap: var(--s-6); }
  .split-grid h2 { max-width: none; }
  .hero-figure { max-width: 30rem; }
  .about-grid { grid-template-columns: 1fr; }
  /* No `.about-grid > .prose { max-width: none }` here any more: it is on the
     base rule now and applies at every width. It was added here because stacked,
     .prose kept its 65ch cap while the aside beneath it filled the track, so the
     two blocks shared a left edge and ended 31px apart at 768px, a visible
     ragged edge bought for three characters of measure. */
  /* Once stacked there is no second column to set the quote against, so it takes
     a hairline instead. The 66% right-push this used to undo is gone from the
     base rule; only the rule and its air are still needed here. */
  .pull-quote { border-top: 1px solid var(--hairline); padding-top: var(--s-8); }
}

@media (max-width: 40rem) {
  .work-row { grid-template-columns: 1fr; gap: var(--s-2); padding-block: 1.25rem; }

  /* The model diagram keeps its composition and moves one piece: there is no
     room for the entry label beside the spine at 320px (the three stage tracks
     need the full width), so it goes above. Its connector then runs down a clear
     left channel, OUTSIDE the two layer rules, and turns right into the first
     stage. Routing it through the middle would have it crossing the compensation
     band and reading as though it pointed at that instead.

     The nodes row is given an explicit height here for one reason: it lets the
     turn be placed at exactly half that height, so the corner lands on the spine
     without any element needing to measure the label. */
  .model {
    --arm: 1.875rem;                    /* 1.25rem visible + --arm-gap clearance */
    --node-h: 2rem;
    grid-template-columns: 1.5rem minmax(0, 1fr);
    grid-template-rows: auto auto var(--arm) var(--node-h) var(--arm) auto;
    grid-template-areas:
      "entry entry"
      "drop  layer-top"
      "drop  arms-down"
      "drop  nodes"
      ".     arms-up"
      ".     layer-bottom";
  }
  .model-entry { padding-right: 0; padding-bottom: var(--s-2); }
  .model-entry-drop {
    display: block;
    grid-area: drop;
    align-self: stretch;
    position: relative;
    height: 100%;
  }
  /* One connector, one weight, one value along its whole path. This elbow feeds
     the horizontal entry arrow, so it takes the structure weight rather than the
     rail's — at 1px in the rail value the path visibly changed identity halfway
     along and read as two unrelated lines. */
  .model-entry-drop::before,
  .model-entry-drop::after {
    content: "";
    position: absolute;
    left: 0.375rem;
    /* Half the line's own weight is subtracted, and it is load-bearing. Plain
       node-h/2 puts the leg's BOTTOM EDGE on the row's centre line, so the leg
       rides half its thickness high and visibly steps off the entry arrow,
       which the flex row centres. This lands both centres on one line. */
    bottom: calc(var(--node-h) / 2 - var(--structure-w) / 2);
    background: var(--model-arm);
  }
  .model-entry-drop::before { top: 0; width: var(--structure-w); }
  .model-entry-drop::after { right: 0; height: var(--structure-w); }
  .model-node { font-size: 1.0625rem; padding-inline: 0.375rem; }
  .model-link { min-width: 0.5rem; }
  .model-layer, .model-entry { font-size: 0.6875rem; letter-spacing: 0.06em; }
}

/* Three columns of prose want more room than two did before they stack, so
   this breaks earlier (48rem) than the block above (40rem) rather than
   sharing it. Column labels drop, since the quote marks and the
   mechanism/outcome pairing already make them redundant. */
@media (max-width: 48rem) {
  .issues, .issues tbody, .issues tr, .issues td { display: block; width: auto; }
  .issues thead { display: none; }
  .issues tr { border-top: 1px solid var(--hairline); padding-block: 1.25rem; }
  .issues tr:last-child { border-bottom: 1px solid var(--hairline); }
  .issues td { padding: 0; border: 0; }
  .issues .issue, .issues .mechanism { margin-bottom: var(--s-2); }
}

/* 416px. The bar carries mark + wordmark + menu + CTA and wants
   159 + 44 + 117 + two 24px gaps + 40px padding = 408px. Below that the CTA is
   the element that gives, and its label breaks mid-word: measured 99x56 at 390,
   84x56 at 375, 75x74 at 360, against 117x44 at 412 and above. That band holds
   nearly every phone in portrait, so the tightening below starts here. It used
   to start at 22rem, which is why 320px looked correct while every real handset
   did not. Tightened, the bar wants 159 + 44 + 109 + 24 + 24 = 360px. */
@media (max-width: 26rem) {
  .nav {
    gap: var(--s-3);
    padding-inline: var(--s-3);
  }
  .nav .btn { padding-inline: 0.875rem; }
}

/* 320px. Tightened, the bar still needs 360px, so below that the wordmark goes:
   the mark is the logo at this size and .nav-brand keeps its aria-label, so the
   accessible name survives. The CTA keeps its full label and its 44px tap
   height. Scoped away from .nav-plain, which carries no CTA and has the room. */
@media (max-width: 22rem) {
  .nav:not(.nav-plain) .nav-wordmark { display: none; }
  /* The panel hangs off the menu button's right edge. At 12rem it starts 4.6px
     left of the viewport, so opening the menu clips its first characters. */
  .nav-menu-panel { min-width: 10.5rem; }
}

/* ==== reduced motion ==== */

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


/* ==================================================================
   BENCHMARK PROPOSAL: new components only. Existing tokens throughout,
   no new colours, no new type roles. Mono is used only where the element
   IS a label or a metric, per the DESIGN.md role rule.
   ================================================================== */

/* ------------------------------------------------------------------
   LAYOUT FIXES. Not part of the benchmark; these are pre-existing rules
   tuned for the home page that misfit /revenue-motion.
   ------------------------------------------------------------------ */

/* The model hero becomes a single column at every width.
   Why, measured rather than assumed: the copy/diagram row has ~1010px usable at
   1440. The lead wants 640+ and the diagram stops overflowing at ~490. That row
   is zero-sum, and the current 6fr/6fr split already overflows the diagram by 2px
   to hold the lead at 568. So no ratio satisfies both, and the diagram has to
   leave the row.
   Everything that follows falls out of that: the h1 gets the full container and
   holds 88px on one line (it was boxed into a 568px column while needing ~740px,
   which is what wrapped it, not the 15ch cap), the lead reaches its own cap
   instead of being clipped by a column, and the diagram gets 768px instead of
   487px, where its arms and labels actually read.
   The children carry explicit `grid-column` / `grid-row` from the unscoped
   .hero-grid rules; left in place they recreate a second, implicit column and the
   grid grows a phantom 216px track with the title and lead on top of each other.
   They have to be reset with the container. */
.hero-grid-model {
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  row-gap: clamp(2rem, 4vw, 3.5rem);
}
.hero-grid-model > h1,
.hero-grid-model > .hero-copy,
.hero-grid-model > .model-figure { grid-area: auto; }

.hero-grid-model h1 { max-width: none; }
/* 48rem, up from 42rem: this hero is single column at every width, so the lead
   had a 1140px band and was using 59% of it. */
.hero-grid-model .hero-lead { max-width: 48rem; }
/* Matches .hero-lead's 48rem deliberately: it is an ALIGNMENT decision, not a
   conservative cap, and the two right edges land flush at the same x.
   Uncapping it to the full 1060px track was tried and measured worse. The word
   ink is byte-identical at both widths, so all 292px went into gaps: ink density
   across the spine fell 36.7% to 24.7%, the bare rule trailing "Grown" went from
   0.64x that word's width to 1.19x, and the centred labels dropped under a fifth
   of their own rule. If the figure needs more presence the lever is the 23px
   node type, not the container - widening without scaling the ink is what
   produced the sparseness. */
.hero-grid-model .model-figure { max-width: 48rem; }

/* `.split-grid` gave the heading 5 of 12 columns at up to 2.625rem, so a short
   heading became a slab and the prose beside it got 58% and read dense. The
   heading column drops to 4 of 12 and the type down a step. Applies to every
   split-grid section, the home page problem block included. */
.split-grid h2 {
  font-size: clamp(1.5rem, 1.25rem + 1.1vw, 2.125rem);
  line-height: 1.15;
}
@media (min-width: 56.01rem) {
  .split-grid { grid-template-columns: minmax(0, 4fr) minmax(0, 8fr); }
  .split-grid h2 { max-width: 15ch; }
}

/* --- proof: the number, pulled out of the paragraph (rec 7) --- */
.proof-stat {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 0.75rem;
  margin: 0 0 var(--s-3);
}
.proof-stat-figure {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.5rem, 1.2rem + 1vw, 2rem);
  line-height: 1;
  color: var(--teal);
  letter-spacing: -0.01em;
}
.proof-stat-label {
  font-size: 0.9375rem;
  color: var(--ink-muted);
  max-width: 34ch;
}

/* --- proof: engagement shape + duration (rec 3) ---
   Hairline rule and a middot, not outlined pills. Two bordered rounded chips
   were a card device in a system that carries structure on 1px rules
   everywhere else, and the pair was visibly lopsided: a four-letter chip beside
   a full-width one. */
.proof-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0;
  margin: var(--s-3) 0 0;
  padding-top: var(--s-3);
  border-top: 1px solid var(--hairline);
}
.proof-tags span {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--teal-support);
}
/* --ink-muted, not --hairline: a colour picked for a rule carries no contrast
   obligation, and this one is type. */
.proof-tags span + span::before {
  content: "·";
  margin: 0 0.5rem;
  color: var(--ink-muted);
}

/* --- routes: self-selection strip (rec 5) --- */
.starts-heading {
  margin: var(--s-6) 0 var(--s-3);
  font-size: 1.0625rem;
}
.starts {
  list-style: none;
  margin: 0 0 var(--s-8);
  padding: 0;
  display: grid;
  gap: 1px;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  background: var(--hairline);
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  overflow: hidden;
}
.start {
  background: var(--paper);
  padding: var(--s-6);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--s-4);
}
.start-situation {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.45;
  color: var(--ink);
}
.start-route {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--teal);
}

/* --- diagnostic: the 1-5 maturity progression ---
   Was a numbered table stacked under the eight dimensions, which drew a list
   where the argument is a direction of travel. Left to right, rising, so the
   shape carries the meaning before the words do. The bars encode a real
   quantity, the level itself, which is what separates this from the route bars
   that were cut for drawing nothing. Ochre lands on level 5 only: it is the
   section's one accent, and "Compounding" is the word the whole site is named
   for. */
.scale-heading {
  margin: var(--s-8) 0 var(--s-6);
  font-size: 1.0625rem;
}
.scale {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--s-6) var(--s-4);
}
.scale-step { display: flex; flex-direction: column; gap: var(--s-2); }
.scale-track {
  display: flex;
  align-items: flex-end;
  height: 4.875rem;
  border-bottom: 1px solid var(--hairline);
}
/* One set of numbers, two orientations: --level is the quantity, and the base
   rule spends it on height while the mobile block spends it on width. Stated
   twice as literals, the two ramps drift the moment either is tuned. */
.scale-step:nth-child(1) { --level: 18%; }
.scale-step:nth-child(2) { --level: 38%; }
.scale-step:nth-child(3) { --level: 58%; }
.scale-step:nth-child(4) { --level: 78%; }
.scale-step:nth-child(5) { --level: 100%; }
.scale-fill { display: block; width: 100%; height: var(--level); background: var(--teal-support); }
.scale-step:nth-child(5) .scale-fill { background: var(--ochre); }
.scale-level {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  color: var(--teal-support);
}
.scale-label { font-weight: 650; font-size: 1rem; letter-spacing: -0.005em; }
.scale-detail { font-size: 0.875rem; line-height: 1.45; color: var(--ink-muted); }

/* Below 44rem this goes straight to one column, never to three or two: five does
   not divide into either, so an intermediate grid left level 5 alone in a row
   beside an empty cell and turned the section's one ochre accent into a stray
   coloured block. Stacked, the ramp still reads top to bottom.

   CORRECTION 2026-08-20: this comment used to claim five columns "hold down to
   about 700px, where each track is still ~140px and clears the widest label".
   Measured, a track is 129px at a 768px viewport, not 140px, and the label was
   the wrong thing to check. .scale-detail is what wraps: 11 characters a line
   over 8 lines at 768px, 12 over 7 at 1512px. Left as is by decision, because
   this is a bar ramp with captions rather than prose and five across is the
   comparison it exists to make, but do not read the old claim as evidence the
   text is comfortable at any width. It is not.

   Stacked, the ramp also turns on its side. A vertical bar in a full-width
   track encodes the level in the one dimension the reader is no longer
   comparing across, so at 1.5rem tall level 1 came out 4px and all five read as
   the same hairline: the ramp was there in the markup and invisible on the
   page. Here --level is width instead, the container edge is the axis, and the
   number and label move above the bar so each step reads as label, quantity,
   caption. */
@media (max-width: 44rem) {
  .scale { grid-template-columns: minmax(0, 1fr); gap: var(--s-6); }
  .scale-step {
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-areas: "level label" "track track" "detail detail";
    align-items: baseline;
    gap: var(--s-3) var(--s-2);
  }
  .scale-level  { grid-area: level; }
  .scale-label  { grid-area: label; }
  .scale-detail { grid-area: detail; }
  .scale-track {
    grid-area: track;
    align-items: stretch;
    height: 0.5rem;
    border-bottom: 0;
  }
  .scale-fill { width: var(--level); height: auto; }
}
