/* ==========================================================================
   Abstraction Advisors 2026
   Single stylesheet for every marketing surface. Article bodies are styled
   separately by blog-legacy.css, which loads only on post views.

   Contents
     1  Tokens
     2  Fonts
     3  Reset and base
     4  Typography
     5  Layout primitives
     6  Buttons and links
     7  Site chrome: nav, footer
     8  Components
     9  Page compositions
    10  Motion
    11  Responsive
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens

   Colour carries forward from the previous brand. Three values changed, each
   for a measured contrast failure rather than for taste:

     --text-muted   #7a95c0 -> #93accf   6.37:1 -> 8.36:1 on --bg
                    This is the body colour for most of the site. The old value
                    passed AA and missed AAA; the new one clears AAA on --bg and
                    7.25:1 on --surface.
     --line-strong  new                  3.42:1, for boundaries that carry
                    meaning (inputs, focus targets). WCAG 1.4.11 wants 3:1 for
                    non-text UI; --line at 1.45:1 is decorative only and stays
                    decorative only.
     --accent used as text  banned       #2563EB is 3.75:1 on --bg and fails for
                    text. It is a fill colour. Text uses --accent-text (7.63:1).

   Shape rule, applied everywhere without exception:
     --r        panels, cards, media
     --r-ctl    buttons, inputs, tags
     --r-pill   the credential badge, and nothing else
   -------------------------------------------------------------------------- */
:root {
  /* surface */
  --bg:           #060d1c;
  --bg-raised:    #0b1628;
  --surface:      #0f1d35;
  --surface-2:    #13264a;

  /* line */
  --line:         #1c2f50;   /* decorative hairline, 1.45:1 */
  --line-strong:  #44689c;   /* meaningful boundary, 3.42:1 */

  /* brand */
  --accent:       #2563EB;   /* fill only */
  --accent-hover: #1d4ed8;
  --accent-text:  #60a5fa;   /* 7.63:1 on --bg, 6.62:1 on --surface */
  --accent-deep:  #1a3a6e;

  /* Text drawn ON --accent. It is white in both themes and that is the point:
     --text-strong flips to near black under a light theme, and every place that
     paints text on the accent fill was reading --text-strong. White on --accent
     is 5.17:1 dark and 6.70:1 light, so both clear AA. */
  --on-accent:    #ffffff;

  /* Surfaces that were hardcoded dark before the light theme existed. */
  --nav-bg:          rgba(6, 13, 28, 0.9);
  --nav-bg-scrolled: rgba(6, 13, 28, 0.98);
  --panel-a:         #12233f;
  --panel-b:         #0d1a30;
  --panel-line:      #24406d;

  /* Error text. 11.50:1 on --bg here, 6.31:1 on the light --bg. */
  --danger-text:  #ffb4b4;
  --danger-line:  #7d3b3b;
  --danger-bg:    rgba(180, 60, 60, 0.1);

  /* ink */
  --text:         #e8f0ff;   /* 16.95:1 */
  --text-muted:   #93accf;   /*  8.36:1 */
  --text-strong:  #ffffff;   /* 19.41:1 */

  /* type */
  --display: 'Newsreader', Georgia, 'Times New Roman', serif;
  --sans:    'DM Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  /* The record register. Anything a reader could check is set in it: figures,
     scores, dates, prices, slot times, sources. Claims stay in --display.
     Tabular figures are the point, so the numerals in a column line up. */
  --mono:    ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;

  /* scale */
  --step--1: 0.8125rem;
  --step-0:  1rem;
  --step-1:  1.125rem;
  --step-2:  clamp(1.35rem, 1.1rem + 0.9vw, 1.6rem);
  --step-3:  clamp(1.7rem, 1.3rem + 1.7vw, 2.4rem);
  --step-4:  clamp(1.95rem, 1.4rem + 2.6vw, 3.15rem);
  --step-5:  clamp(2.3rem, 1.5rem + 3.6vw, 3.85rem);

  /* space */
  --s-1: 0.25rem; --s-2: 0.5rem;  --s-3: 0.75rem; --s-4: 1rem;
  --s-5: 1.5rem;  --s-6: 2rem;    --s-7: 3rem;    --s-8: 4rem;
  --s-9: 6rem;    --s-10: 8rem;

  /* shape */
  --r:      10px;
  --r-ctl:  6px;
  --r-pill: 999px;

  /* motion */
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --dur:  240ms;
  --dur-fast: 160ms;

  /* structure */
  --shell:   1200px;
  --measure: 68ch;
  --nav-h:   72px;
}

/* --------------------------------------------------------------------------
   1b. Light theme, added 2026-08-18.

   The site stays dark by default and by brand. This block answers a reader
   whose device asks for light, which on the reference pages (the rubric,
   /data-residency/, /data-handling/) is most of them: those are documents an
   operations executive forwards, prints, or reads beside a spreadsheet.

   Every value below was measured, not picked, on the same basis as the dark
   set. Ratios are against --bg unless noted:

     --text         #101B2E  16.65:1
     --text-muted   #4C5F7C   6.27:1   (5.93:1 on --bg-raised, still AA)
     --text-strong  #05090F  19.27:1
     --accent-text  #1747BE   7.56:1   text only, never a fill
     --accent       #1D4ED8   fill only; white on it is 6.70:1
     --line-strong  #748EB1   3.25:1 on --bg, 3.36:1 on --surface, clearing
                              1.4.11's 3:1 on every ground it is drawn against
     --line         decorative hairline only, as in the dark set

   blog-legacy.css carries 115 var() references and no literal colour, so the
   62 post bodies follow this flip without a rule of their own.
   -------------------------------------------------------------------------- */
@media (prefers-color-scheme: light) {
  :root {
    --bg:           #FAFBFD;
    --bg-raised:    #F1F5FA;
    --surface:      #FFFFFF;
    --surface-2:    #E9EFF8;

    --line:         #DCE4EF;
    --line-strong:  #748EB1;

    --accent:       #1D4ED8;
    --accent-hover: #1739A8;
    --accent-text:  #1747BE;
    --accent-deep:  #DCE7FB;

    --text:         #101B2E;
    --text-muted:   #4C5F7C;
    --text-strong:  #05090F;

    --on-accent:    #ffffff;

    --nav-bg:          rgba(250, 251, 253, 0.9);
    --nav-bg-scrolled: rgba(250, 251, 253, 0.98);
    --panel-a:         #FFFFFF;
    --panel-b:         #F1F5FA;
    --panel-line:      #DCE4EF;

    --danger-text:  #B3261E;
    --danger-line:  #E4A9A4;
    --danger-bg:    rgba(179, 38, 30, 0.07);
  }

  /* The client marks are flattened to a single light tone for the dark ground,
     which on a light one would be invisible. Inverting a single-tone mark gives
     a dark mark of the same shape, so no second set of files is needed. See
     rebuild/client-logos.md before changing the generator. */
  .logos img,
  .trust__logos img { filter: invert(1) brightness(0.42); }
}

/* --------------------------------------------------------------------------
   2. Fonts — self-hosted, latin subset.
   Removes a render-blocking third-party request and the GDPR question that
   loading Google Fonts from the browser raises.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('../fonts/dmsans-300-700.woff2') format('woff2');
}
/* Newsreader replaced Cormorant Garamond on 2026-08-18. Cormorant is a
   high-contrast display serif: its hairlines approach one device pixel at
   subhead sizes, and a light-on-dark ground thins strokes optically further, so
   the face was fighting the background it was given. Worse, cormorant-600.woff2
   was a variable file whose default instance is Light 300, so the weight asked
   for was never the weight served.

   These two are static instances cut from the Newsreader variable font at
   opsz=36, subset to latin. 41KB for the pair against 98KB for the three
   Cormorant files it replaces, so the change is 57KB lighter. */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../fonts/newsreader-600.woff2') format('woff2');
}
@font-face {
  font-family: 'Newsreader';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/newsreader-400-italic.woff2') format('woff2');
}

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

html {
  scroll-behavior: smooth;
  background: var(--bg);
  -webkit-text-size-adjust: 100%;
}
/* The previous theme set html { opacity: 0 } and waited for JS to add a class.
   That blanks the page until a script runs and shows nothing at all if it
   fails. Deliberately not carried forward. */

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: var(--step-0);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

img, svg, video { max-width: 100%; height: auto; display: block; }
ul, ol { list-style: none; }

:focus-visible {
  outline: 2px solid var(--text-strong);
  outline-offset: 3px;
  border-radius: 2px;
}
::selection { background: var(--accent); color: var(--on-accent); }

.skip-link {
  position: absolute;
  left: var(--s-4);
  top: -100px;
  z-index: 200;
  background: var(--accent);
  color: var(--on-accent);
  padding: var(--s-3) var(--s-5);
  border-radius: var(--r-ctl);
  font-size: var(--step--1);
  font-weight: 600;
  text-decoration: none;
  transition: top var(--dur-fast) var(--ease);
}
.skip-link:focus { top: var(--s-4); }

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
}

/* --------------------------------------------------------------------------
   4. Typography
   -------------------------------------------------------------------------- */
h1, h2, h3, .display {
  font-family: var(--display);
  font-weight: 600;
  /* 1.12 and -0.005em were tuned to Cormorant's small x-height. Newsreader
     sets larger on the same body, so it wants a little more leading and a
     little more negative tracking to hold the same colour on the page. */
  line-height: 1.16;
  letter-spacing: -0.014em;
  color: var(--text-strong);
  text-wrap: balance;
}
h1 { font-size: var(--step-5); }
h2 { font-size: var(--step-4); }
h3 { font-size: var(--step-2); }
h4, h5, h6 { font-family: var(--sans); font-weight: 600; line-height: 1.3; color: var(--text-strong); }

/* Emphasis inside display type stays in the same family, as italic.
   Descender clearance is reserved so the y/g/j/p/q tails are not clipped. */
h1 em, h2 em, .display em {
  font-style: italic;
  font-weight: 400;
  color: var(--accent-text);
  line-height: 1.14;
  padding-bottom: 0.06em;
  display: inline-block;
}

p { text-wrap: pretty; }

.lead {
  font-size: var(--step-1);
  color: var(--text-muted);
  line-height: 1.7;
  max-width: 58ch;
}

.eyebrow {
  display: inline-block;
  font-family: var(--sans);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-text);
  margin-bottom: var(--s-4);
}

/* THE RECORD REGISTER, added 2026-08-18.

   A figure set in the same face as the sentence around it reads as part of the
   argument. Set in mono with tabular figures it reads as a value that came from
   somewhere, which is the whole distinction this site is built on. Applied to
   figures, scores, prices, dates and slot times, and to nothing else: a label
   is not a value and does not belong here. */
.proof__figure,
.rubric__score,
.day__n,
.slot__time,
.booking__done-when,
.timeline__cost b,
.record {
  font-family: var(--mono);
  font-variant-numeric: lining-nums tabular-nums;
  letter-spacing: -0.01em;
}

.muted { color: var(--text-muted); }
.prose > * + * { margin-top: var(--s-4); }
.prose p { color: var(--text-muted); max-width: var(--measure); }
.prose strong { color: var(--text); font-weight: 600; }

/* PROSE IN TWO COLUMNS, added 2026-08-06. Opt in, never automatic.

   `--measure` is 68ch, which resolves to 746px, and the shell is 1200px. A
   prose block on its own therefore leaves 454px of empty shell beside every
   line, and a page built mostly from headings over prose reads as a narrow
   ragged column pinned to one edge of a very wide field. That is what /arabic-ai/
   and /data-residency/ were both audited for.

   The answer is not a longer line. 68ch is correct and widening it trades a
   layout complaint for a reading one. The answer is to give the empty column a
   job, so two paragraphs sit beside each other and each column becomes the
   measure: 576px at the 1200px cap, about 52 characters, inside a comfortable
   line rather than outside it.

   ONLY put this on a block with two or more children. With one paragraph it
   does the opposite of its job and empties half the row, which is the defect it
   exists to remove.

   1100px, and the number is arithmetic. `.section` pads 64px a side, so the
   shell is `min(1200, vw - 128)`. At 1100 that leaves 972px and two columns of
   462px, about 42 characters, which is the last width where a column is still a
   column. Below it the shell is close enough to the measure that there is no
   dead band to remove and one column is right. */
@media (min-width: 1100px) {
  .prose--columns {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--s-7);
    /* What `.prose > * + *` already spends between two stacked paragraphs. The
       horizontal arrangement changes here. The vertical rhythm does not. */
    row-gap: var(--s-4);
    align-items: start;
  }
  /* The stacked margin and the row gap would otherwise both apply and the
     second column would sit a paragraph's margin lower than the first. */
  .prose--columns > * + * { margin-top: 0; }

  /* A closing link is not a third paragraph and must not read as the foot of
     the left column. It takes its own full-width row under both. Where `:has()`
     is unavailable it falls back into column one, which is untidy rather than
     broken. */
  .prose--columns p:has(> .jump) { grid-column: 1 / -1; }
}

/* A closing note and the action it implies, on one line rather than two rows
   of a section. Wraps rather than crushes: below the wrap the button drops
   under the sentence and the row is what it used to be. */
.note-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4) var(--s-6);
  flex-wrap: wrap;
}
.note-row p { color: var(--text-muted); max-width: var(--measure); }
/* BASE LINK COLOUR, added 2026-08-06.

   There was none. Every anchor that was not inside a component with its own
   colour rule fell through to the user-agent default, #0000ee, which on this
   background measures 2.06:1 and fails WCAG AA outright. It had gone unnoticed
   because every link on the site at cutover happened to sit inside a styled
   component; the first two that did not, a lead paragraph on the homepage and
   the reference list on /resources/, were both written on the same day and both
   shipped blue.

   This is the floor, not a component style. Cards and card-like blocks set
   `color: inherit` deliberately and still win on specificity, the nav, footer
   and buttons all set their own, and nothing else changes.

   Underline is left to the component, because a link inside running prose wants
   one and a link that is a whole card does not. */
a { color: var(--accent-text); }

/* `.lead` joined `.prose` here on 2026-08-06. A lead paragraph had never
   carried an inline link until the framework sentence on the homepage did, so
   an anchor inside one fell through to the user-agent default: #0000ee on
   #060d1c, which measures 2.06:1 and was the first axe-core failure this site
   has had since cutover. Styling the two together is the fix rather than a
   one-off class, because the next lead paragraph with a link in it will be
   written by somebody who does not know this happened. */
.prose a,
.lead a { color: var(--accent-text); text-decoration: underline; text-underline-offset: 3px; }
.prose a:hover,
.lead a:hover { color: var(--text-strong); }

/* --------------------------------------------------------------------------
   5. Layout primitives
   -------------------------------------------------------------------------- */
/* VERTICAL RHYTHM, retuned 2026-08-06.

   These were --s-9 (96px) top and bottom, so two adjacent sections put 192px of
   nothing between the last line of one and the first of the next. Measured
   across eleven desktop pages, that made 43% to 65% of every page blank, with a
   repeating ~200px dead band that read as a rendering fault rather than as
   space. /facts/ was 65% empty.

   The offer page had already reached this conclusion on its own and fixed it
   locally with `.offer-sec { padding: var(--s-7) ... }` on 2026-08-04, measured
   then at 230px of dead band down to 134px. This is that same decision applied
   where it belonged in the first place, which also lets .offer-sec stop being a
   special case.

   Horizontal padding is untouched. The complaint was vertical and the shell is
   already centred and capped at 1200px. */
.section { padding: var(--s-7) var(--s-8); }
.section--tight { padding: var(--s-6) var(--s-8); }
.section--raised { background: var(--bg-raised); }
.section--edge { border-top: 1px solid var(--line); }

.shell { max-width: var(--shell); margin-inline: auto; width: 100%; }
.shell--narrow { max-width: 800px; }

/* Section head. Stacked, never a big headline with a small paragraph floating
   in the opposite corner. */
/* --s-7 until 2026-08-06. Stacked on top of the section's own padding this was
   48px of gap on top of 48px, before a reader reached the content the heading
   introduces. */
/* MEASURE, corrected 2026-08-06 evening. This was `62ch`, and the number lied.
   `ch` resolves against the element it is written on, and .s-head inherits the
   16px sans, so 62ch measured 682px. The h2 inside it sets at 54px Cormorant,
   where 682px is roughly 26 characters. Measured across every section heading
   on the site, 12 of 26 needed between 700px and 920px to sit on one line and
   were being folded into two short lines instead, which is what put a 400px
   heading column against a 1200px shell and left the rest of the row empty.

   920px is the widest heading that still wants one line. The two headings that
   need more (1139px and 1267px) wrap to two balanced lines of ~570px and ~634px,
   which is the intent. The lead paragraph underneath is unaffected: `.lead`
   carries its own 58ch, and 58ch of ITS font is ~640px, still inside this box
   and still a readable measure. Do not re-express this in `ch`. */
.s-head { margin-bottom: var(--s-6); max-width: min(100%, 920px); }
.s-head h2 + p { margin-top: var(--s-4); }

.rail { display: grid; gap: var(--s-5); }

/* Two-column split. Collapses to one column at 900px, declared once here
   rather than inline on each use. */
.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s-8);
  align-items: center;
}
.split--top { align-items: start; }
.split--wide-left { grid-template-columns: 1.3fr 1fr; }

/* --------------------------------------------------------------------------
   6. Buttons and links
   White on --accent is 5.17:1, and 6.70:1 on --accent-hover. Both pass AA.
   Minimum target 44px tall throughout.
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 44px;
  padding: var(--s-3) var(--s-5);
  border: 1px solid transparent;
  border-radius: var(--r-ctl);
  font-family: var(--sans);
  font-size: 0.9rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
/* PRESS FEEDBACK, 2026-08-18. Until now this was the only :active rule in the
   stylesheet, and the calendar, which is the surface a visitor actually presses
   before handing over a name, had none at all. A control that does not
   acknowledge the press reads as not having registered it, which on a picker is
   where somebody taps twice and books nothing. 0.97 is the smallest scale that
   is felt rather than seen. */
.btn:active { transform: scale(0.97); }

.btn--primary { background: var(--accent); color: var(--on-accent); }
.btn--primary:hover { background: var(--accent-hover); }

.btn--ghost {
  background: transparent;
  color: var(--text);
  border-color: var(--line-strong);
}
.btn--ghost:hover { border-color: var(--accent-text); color: var(--accent-text); }

.btn--full { width: 100%; }

/* Inline forward link */
.jump {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 44px;
  color: var(--accent-text);
  font-size: 0.9rem;
  font-weight: 500;
  text-decoration: none;
}
.jump::after { content: '\2192'; transition: transform var(--dur-fast) var(--ease); }
.jump:hover { color: var(--text-strong); }
.jump:hover::after { transform: translateX(3px); }

/* --------------------------------------------------------------------------
   7. Site chrome
   -------------------------------------------------------------------------- */
.site-nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  gap: var(--s-6);
  padding: 0 var(--s-8);
  background: var(--nav-bg);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line);
  transition: background var(--dur) var(--ease);
}
.site-nav.is-scrolled { background: var(--nav-bg-scrolled); }
.admin-bar .site-nav { top: 32px; }

/* Two possible brand elements: the custom-logo link WordPress renders when one
   is set in Site Identity, and our own fallback anchor when none is. Both are
   direct flex children of .site-nav and are sized identically. */
.site-nav__brand,
.site-nav .custom-logo-link {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  max-width: 210px;
}
.site-nav__brand img,
.site-nav .custom-logo {
  height: 46px;
  width: auto;
  max-width: 210px;
  object-fit: contain;
}

.site-nav__menu {
  display: flex;
  align-items: center;
  gap: var(--s-6);
  margin-inline-start: auto;
}
.site-nav__menu li { display: flex; }
.site-nav__menu a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--text-muted);
  font-size: 0.82rem;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease);
}
.site-nav__menu a:hover { color: var(--text-strong); }
.site-nav__menu .current-menu-item > a,
.site-nav__menu .current_page_item > a,
.site-nav__menu a[aria-current] { color: var(--text-strong); }

/* The nav CTA points at the offer page, not straight into a calendar. */
.site-nav__menu .nav-cta > a,
.site-nav__menu a.nav-cta {
  background: var(--accent);
  color: var(--on-accent);
  padding: var(--s-2) var(--s-4);
  border-radius: var(--r-ctl);
  text-transform: none;
  letter-spacing: 0;
  font-size: 0.85rem;
  font-weight: 600;
  transition: background var(--dur-fast) var(--ease);
}
.site-nav__menu .nav-cta > a:hover,
.site-nav__menu a.nav-cta:hover { background: var(--accent-hover); color: var(--text-strong); }

/* The language switcher. One link, printing the language it goes to in that
   language's own script, so an Arabic reader recognises the word rather than
   translating it and an English reader reads the convention. It lives in the
   footer only. The nav does not carry one.

   text-transform is off. Uppercase does nothing to Arabic letters and would
   have applied to the English name only, so the two states of one control
   would have been set differently from each other. It is a plain link in the
   site column: the column heading above it already says what the group is. */
.site-footer .lang-switch { text-transform: none; }

.nav-toggle {
  display: none;
  margin-inline-start: auto;
  flex-shrink: 0;
  width: 44px; height: 44px;
  align-items: center; justify-content: center;
  background: none;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-ctl);
  cursor: pointer;
}
.nav-toggle span {
  display: block;
  width: 18px; height: 2px;
  background: var(--text);
  position: relative;
  transition: background var(--dur-fast) var(--ease);
}
.nav-toggle span::before,
.nav-toggle span::after {
  content: '';
  position: absolute;
  inset-inline-start: 0;
  width: 18px; height: 2px;
  background: var(--text);
  transition: transform var(--dur) var(--ease);
}
.nav-toggle span::before { top: -6px; }
.nav-toggle span::after  { top:  6px; }
.nav-toggle[aria-expanded="true"] span { background: transparent; }
.nav-toggle[aria-expanded="true"] span::before { transform: translateY(6px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span::after  { transform: translateY(-6px) rotate(-45deg); }

/* footer */
.site-footer {
  background: var(--bg-raised);
  border-top: 1px solid var(--line);
  padding: var(--s-8) var(--s-8) var(--s-6);
}
/* The column count is not fixed: the Regions column renders in English only, so
   this is four columns under /ar/ and five at the root.

   One explicit track for the brand, then `grid-auto-flow: column` gives every
   remaining child its own equal implicit column, so the track count follows the
   child count with no empty track at the end and no media query.

   It is NOT `repeat(auto-fit, minmax(...))`. That cannot be combined with a
   flexible `fr` track: the declaration is invalid, the browser drops the whole
   of `grid-template-columns`, and the footer renders as one column down the
   left quarter of the page. Which is exactly what shipped on 2026-08-06 and had
   to be caught in a screenshot. */
.site-footer__grid {
  max-width: var(--shell);
  margin: 0 auto var(--s-6);
  display: grid;
  grid-template-columns: 1.5fr;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: var(--s-6) var(--s-5);
  align-items: start;
}
.site-footer__brand img { height: 38px; width: auto; max-width: 200px; object-fit: contain; margin-bottom: var(--s-4); }
.site-footer__brand p { font-size: 0.875rem; color: var(--text-muted); max-width: 30ch; }
.site-footer h2 {
  font-family: var(--sans);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--s-4);
}
.site-footer li + li { margin-top: var(--s-2); }
.site-footer a {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  color: var(--text);
  font-size: 0.9rem;
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease);
}
.site-footer a:hover { color: var(--accent-text); }
.site-footer address { font-style: normal; font-size: 0.9rem; color: var(--text); line-height: 1.7; }

.site-footer__bottom {
  max-width: var(--shell);
  margin: 0 auto;
  padding-top: var(--s-5);
  border-top: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  justify-content: space-between;
  align-items: center;
  font-size: 0.8rem;
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   8. Components
   -------------------------------------------------------------------------- */

/* 8.1 Credential badge — the only pill on the site.
   Third-party validation from the vendor whose models are being implemented.
   Renders as text until the official asset is supplied. */
.credential {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-4);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  background: rgba(37, 99, 235, 0.07);
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--text);
  text-decoration: none;
  line-height: 1.3;
}
.credential img, .credential svg { height: 16px; width: auto; }

/* 8.2 CTA block — every call to action on the site renders through this.
   Named once, in aa_offer(). */
.cta {
  border-top: 1px solid var(--line);
  /* --s-9 until 2026-08-06. The closing CTA is the last thing on every page and
     it was carrying 96px of air above a heading that already has the whole
     width to itself. */
  padding: var(--s-8) var(--s-8);
  position: relative;
  overflow: hidden;
  text-align: center;
}
.cta::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse 55% 80% at 50% 40%, rgba(37, 99, 235, 0.12), transparent 70%);
  pointer-events: none;
}
.cta__inner { position: relative; max-width: 640px; margin-inline: auto; }
.cta h2 { margin-bottom: var(--s-4); }
.cta__body { color: var(--text-muted); margin-bottom: var(--s-6); }
.cta__actions {
  display: flex;
  gap: var(--s-3);
  justify-content: center;
  flex-wrap: wrap;
  margin-bottom: var(--s-4);
}
.cta__micro {
  font-size: 0.85rem;
  color: var(--text-muted);
  max-width: 52ch;
  margin-inline: auto;
  line-height: 1.6;
}
.cta--inline { border-top: none; padding: 0; text-align: start; }
.cta--inline .cta__inner { margin-inline: 0; }
.cta--inline .cta__actions { justify-content: flex-start; }
.cta--inline .cta__micro { margin-inline: 0; }

/* 8.3 Proof — a figure cannot render without an attribution.
   The PHP part refuses to output an item lacking a client or a source. */
.proof {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
}
.proof__item {
  padding: var(--s-6);
  border-inline-end: 1px solid var(--line);
  background: var(--surface);
}
.proof__item:last-child { border-inline-end: none; }
.proof__figure {
  font-size: var(--step-3);
  font-weight: 500;
  color: var(--text-strong);
  line-height: 1;
  margin-bottom: var(--s-3);
}
.proof__label { font-size: 0.9rem; color: var(--text-muted); line-height: 1.55; }
.proof__source {
  display: block;
  margin-top: var(--s-4);
  padding-top: var(--s-3);
  border-top: 1px solid var(--line);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent-text);
  text-decoration: none;
}
a.proof__source:hover { color: var(--text-strong); }

/* 8.4 Logo wall — logos only, no category labels underneath. The sector line
   that a Gulf reader actually scans for is the section's own label, above the
   wall, not a caption repeated seventeen times.

   A wall rather than a strip since 2026-08-06, when seven marks became
   seventeen. Wrapping flex rather than grid, because 17 does not divide evenly
   into any sensible column count and a grid leaves the short last row hard
   against one edge, while wrapped flex centres it.

   These rules draw two different walls: the firm's seventeen, and a market's
   own clients on /egypt/. That is deliberate and is the whole reason the market
   wall has no rules of its own. The two answer the same question in the same
   place on the page, so they are one object, and the eight-mark wall gets the
   centred last row from the same property that gives the seventeen one. */
.logos__label {
  margin: 0 0 var(--s-5);
  text-align: center;
  max-width: 62ch;
  margin-inline: auto;
  color: var(--text-muted);
  font-size: 0.85rem;
}
.logos {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--s-5) var(--s-6);
}
/* Every file ships at 300x108 with the mark already contained and centred
   inside it, so one width draws all seventeen and no mark needs a rule of its
   own. That is a change from the shared-height arrangement that shipped in
   August 2026, which could not size a round seal and a wide wordmark from one
   rule and carried a per-file exception for Midwest Barrel Co. See the
   generator's place() for why the containment happens in the file.

   No grayscale filter. The files arrive keyed to transparency and flattened to
   one tone, so it was a no-op and the hover that removed it revealed nothing.
   Opacity does the whole job. */
.logos img {
  width: 150px;
  height: auto;
  opacity: 0.75;
  transition: opacity var(--dur) var(--ease);
}
.logos img:hover { opacity: 1; }

/* A market's own wall, which is eight marks rather than seventeen. Across the
   full shell those eight break six and two, and two marks alone on a second row
   do not read as the end of a wall, they read as two clients who arrived late.
   704px is four marks at 150px with the 2rem gap between them and not five, so
   the wall squares up as two rows of four.

   No effect below 900px, where the shell is already narrower than this and the
   marks drop to 96px. Nothing else about the wall changes: same size, same
   opacity, same hover, same centring, because a reader coming from /uae/ should
   be looking at the same object. */
.logos--market {
  max-width: 44rem;
  margin-inline: auto;
}

/* 8.4b Named clients, where there is a record but no marks.
   Added 2026-08-08 with /egypt/, which had eight named engagements and not one
   logo file, and demoted to a fallback the same day when the eight marks were
   sourced. It is not dead: parts/market.php drops back to it whole if any one
   of a market's marks is missing from disk, because a wall with gaps in it
   reads as fewer clients rather than as fewer files. Every market page that
   ships without marks opens on this until they arrive.

   Promoted out of a caption under the record paragraph before that. It had been
   0.95rem under a muted label, which is the size a footnote is set at, on the
   one block that answers the question the reader arrived with. --step-2 rather
   than --step-3: the longest name here is "Alexandria Facade Company LLC
   (AFCO)", and at --step-3 it wraps mid-name on a laptop, which makes a client
   look like two.

   Left-aligned rather than centred. The marks that replaced it centre, because
   a wrapped row of images has a ragged last line that centring fixes and text
   set to the shell's own measure does not.

   No separator character between them. A middle dot or a slash here would be
   decoration standing in for spacing, and the gap already does that work. */
.names {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-4) var(--s-7);
  margin-block: 0;
  padding-inline-start: 0;
  list-style: none;
}
.names li {
  /* Full contrast, not the 0.75 opacity the marks are held at. These are the
     evidence, and a client's name is not decoration to be dimmed. */
  color: var(--text);
  font-size: var(--step-2);
  line-height: 1.25;
  /* Long company names must not break across lines and read as two clients. */
  text-wrap: balance;
}

@media (max-width: 600px) {
  /* One per line below this, because two long names side by side at --step-2
     leaves a ragged column that reads worse than a list. */
  .names { gap: var(--s-3); flex-direction: column; }
}

/* 8.5 Engagement timeline — the most decision-useful content on the old site,
   promoted out of the page footer into a rail. */
.timeline {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0;
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
}
.timeline__step {
  padding: var(--s-6) var(--s-5);
  border-inline-end: 1px solid var(--line);
  background: var(--bg-raised);
  transition: background var(--dur) var(--ease);
}
.timeline__step:last-child { border-inline-end: none; }
.timeline__step:hover { background: var(--surface); }
.timeline__when {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent-text);
  margin-bottom: var(--s-4);
}
.timeline__when::after { content: ''; flex: 1; height: 1px; background: var(--line-strong); }
.timeline__step h3 { font-size: var(--step-1); margin-bottom: var(--s-3); }
.timeline__step p { font-size: 0.875rem; color: var(--text-muted); line-height: 1.6; }
.timeline__out {
  margin-top: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px solid var(--line);
  font-size: 0.8rem;
  color: var(--text-muted);
  line-height: 1.55;
}
.timeline__out b { display: block; color: var(--text); font-weight: 600; margin-bottom: var(--s-1); }

/* The engagement-level pricing band.
   Label styled identically to the outcome row above it, because two different
   label treatments stacked inside one card reads as an accident. The value
   carries the difference instead: the price sits in the reading colour rather
   than the muted one, since a number the buyer has to squint at defeats the
   point of publishing it. Absent entirely until aa_engagement_timeline()
   carries a cost. */
.timeline__cost {
  margin-top: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px solid var(--line);
  font-size: 0.8rem;
  color: var(--text);
  line-height: 1.55;
}
.timeline__cost b { display: block; color: var(--text); font-weight: 600; margin-bottom: var(--s-1); }

/* 8.6 Services, grouped by engagement stage.
   Six equal cards in a row is the pattern the audit's reader has seen a
   thousand times and it flattens six unequal things into one shape. Grouping
   into the three stages of an engagement carries information the row did not. */
.stage + .stage { margin-top: var(--s-7); }
.stage__head {
  display: flex;
  align-items: baseline;
  gap: var(--s-4);
  padding-bottom: var(--s-4);
  border-bottom: 1px solid var(--line-strong);
  margin-bottom: var(--s-5);
}
.stage__head h3 { font-size: var(--step-2); }
.stage__note { font-size: 0.85rem; color: var(--text-muted); margin-inline-start: auto; text-align: end; }

.svc-pair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-5); }

.svc {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--s-6);
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.svc:hover { border-color: var(--line-strong); transform: translateY(-2px); }
.svc h4 { font-family: var(--display); font-weight: 600; font-size: var(--step-2); margin-bottom: var(--s-3); }
.svc__outcome { color: var(--text-muted); font-size: 0.95rem; line-height: 1.6; margin-bottom: var(--s-5); }

/* Fixed pattern. A row is omitted entirely when its value is absent, so an
   unanswered Investment or Proof leaves no empty slot behind. */
.svc__spec { margin-top: auto; display: grid; gap: var(--s-3); }
.svc__spec div {
  display: grid;
  grid-template-columns: 5.5rem 1fr;
  gap: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px solid var(--line);
  font-size: 0.85rem;
  line-height: 1.5;
}
.svc__spec dt {
  color: var(--text-muted);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding-top: 0.15em;
}
.svc__spec dd { color: var(--text); }
.svc__spec dd a { color: var(--accent-text); text-decoration: none; }
.svc__spec dd a:hover { color: var(--text-strong); text-decoration: underline; }

/* 8.7 Failure paired with its control.
   The old site listed six failure modes and left the reader to infer that the
   firm prevents them. Each now carries the specific countermeasure. */
/* THE SIX CONTROLS, recomposed 2026-08-18.

   Six equal cards in a two column grid flattened six unequal things into one
   shape, and it was the third grid-of-equal-cards on the homepage. As a ledger
   the pairing carries the information the grid threw away: on the left the
   failure mode, on the right the countermeasure, one row each, so a reader can
   run down the modes alone or read across a single pair.

   The number is generated rather than authored, so the template stays free of
   ordinals that would have to be renumbered by hand. */
.controls {
  display: grid;
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
  counter-reset: aa-control;
}
.control {
  background: var(--surface);
  padding: var(--s-5) var(--s-6);
  display: grid;
  grid-template-columns: 2.25rem minmax(0, 1fr);
  column-gap: var(--s-4);
  row-gap: var(--s-3);
  counter-increment: aa-control;
}
.control::before {
  content: counter(aa-control, decimal-leading-zero);
  grid-row: span 3;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
  line-height: 1.9;
  color: var(--accent-text);
}
@media (min-width: 900px) {
  .control {
    grid-template-columns: 2.25rem minmax(0, 21rem) minmax(0, 1fr);
    column-gap: var(--s-6);
  }
  .control::before   { grid-row: span 2; }
  .control h3        { grid-column: 2; }
  .control p         { grid-column: 2; }
  .control__fix {
    grid-column: 3;
    grid-row: 1 / span 2;
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
    border-inline-start: 1px solid var(--line);
    padding-inline-start: var(--s-6);
  }
}
.control h3 { font-family: var(--display); font-size: 1.2rem; font-weight: 600; line-height: 1.3; }
.control p { font-size: 0.875rem; color: var(--text-muted); line-height: 1.6; }
.control__fix {
  margin-top: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px solid var(--line);
  font-size: 0.85rem;
  color: var(--text);
  line-height: 1.6;
}
.control__fix b {
  display: block;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent-text);
  margin-bottom: var(--s-2);
}

/* 8.7a The scale, on the homepage under the six controls.

   Four bands as one row, which is the shape of the thing: it is an ordered
   scale and reading it across is how somebody finds where they sit on it. The
   digits are in the record register with the rest of the site's checkable
   values. Collapses to two columns before it collapses to one, because four
   bands stacked is a list and stops reading as a scale. */
.scale {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  margin-top: var(--s-6);
  list-style: none;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
}
.scale__band {
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-5);
  background: var(--surface);
}
.scale__n {
  font-family: var(--mono);
  font-variant-numeric: lining-nums tabular-nums;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--accent-text);
}
.scale__name { font-size: 0.92rem; font-weight: 600; color: var(--text-strong); }

@media (max-width: 900px) { .scale { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 460px) { .scale { grid-template-columns: 1fr; } }

/* 8.7b The published rubric, /services/#rubric.

   Two columns that are not the same thing, which is the point: on one side the
   scale, on the other what it is applied to. A single list of six controls each
   with four bands under it would be twenty-four rows saying the same four
   things six times.

   Deliberately not `.controls`: those are cards because each is a self
   contained argument, and these are rows in a scale, where the relationship
   between them is the content. Cards would break the reading. */
.rubric {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--s-7);
  align-items: start;
}
.rubric h3 {
  font-family: var(--sans);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--accent-text);
  margin-bottom: var(--s-4);
}
.rubric__band {
  display: grid;
  grid-template-columns: 2.25rem 1fr;
  gap: var(--s-4);
  padding: var(--s-4) 0;
  border-top: 1px solid var(--line);
}
.rubric__band:last-child { border-bottom: 1px solid var(--line); }
.rubric__band dt {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  align-items: baseline;
}
/* Mono, and this is the clearest case on the site for it. The score IS the
   content here: "0" and "O" mean different things in a rubric band, and a
   reader comparing four rows needs the digits to line up. It was the sans until
   2026-08-18 and set in it for the same reason, because Cormorant Garamond sets
   oldstyle figures whose zero sits at x-height and reads as a lowercase o.
   Newsreader does not have that problem, but the record register is where every
   checkable value on the site now lives, and a band score is one. Lining and
   tabular are still declared rather than assumed. */
.rubric__score {
  font-size: var(--step-1);
  font-weight: 500;
  font-variant-numeric: lining-nums tabular-nums;
  line-height: 1.2;
  color: var(--accent-text);
}
.rubric__band dt b { font-size: 0.98rem; font-weight: 600; }
.rubric__band dd {
  grid-column: 2;
  margin: var(--s-2) 0 0;
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--text-muted);
}

.rubric__controls { display: grid; gap: var(--s-3); }
.rubric__controls li {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-1);
  padding-inline-start: var(--s-4);
  border-inline-start: 2px solid var(--accent);
}
.rubric__controls b { font-size: 0.9rem; font-weight: 600; }
.rubric__controls a { text-decoration: underline; text-underline-offset: 3px; }
.rubric__controls a:hover { color: var(--text-strong); }
/* The attribution beside a model name. Its own line rather than a parenthetical,
   because who built it is the fact that matters most in the row and a bracket
   reads as an aside. */
.model__from {
  display: block;
  font-weight: 400;
  font-size: 0.75rem;
  color: var(--text-muted);
  margin-top: var(--s-1);
}
/* MEASURE, added 2026-08-06. These rows sit directly on the 1200px shell with
   no column beside them, so the note ran the full width: about 150 characters a
   line at 0.85rem, roughly twice a readable measure, and the eye loses the line
   it is on coming back to the left. It was hidden while the section heads above
   were folded into a narrow column, because the whole page looked narrow. With
   the heads corrected it is the widest text on the site.

   65ch here is 65 of THIS element's characters, which is the case `ch` is
   actually for: the rule is written on the element whose font it measures. */
.rubric__controls span {
  font-size: 0.85rem;
  color: var(--text-muted);
  line-height: 1.5;
  max-width: 65ch;
}

.rubric__reading-head { margin-top: var(--s-6); }
.rubric__reading { display: grid; gap: var(--s-4); }
/* Same reason as .rubric__score: "0 to 6" set in the display face reads as
   "o to 6". */
.rubric__reading dt {
  font-family: var(--sans);
  font-size: 0.95rem;
  font-weight: 600;
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--text-strong);
}
.rubric__reading dd {
  margin: var(--s-1) 0 0;
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--text-muted);
  /* Same correction as .rubric__controls span above: this list also sits
     directly on the shell with nothing beside it. */
  max-width: 65ch;
}

/* 8.7c The facts table, /facts/.

   A definition list rather than a <table>, because these are label and value
   pairs rather than a grid with meaningful columns, and a screen reader reads a
   dl correctly without any of the header association a table would need.

   One hairline between rows and none above the first or below the last. A
   border on every side of every row is the spec-sheet look that makes a long
   list unreadable. */
.facts { border-top: 1px solid var(--line); }
.facts__row {
  display: grid;
  grid-template-columns: 13rem minmax(0, 1fr);
  gap: var(--s-5);
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--line);
}
.facts__row dt {
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}
.facts__row dd {
  margin: 0;
  font-size: 0.925rem;
  line-height: 1.65;
}

/* 8.8 Numbered editorial list */
.tenets { border-top: 1px solid var(--line); }
.tenets li {
  display: grid;
  grid-template-columns: 3rem 1fr;
  gap: var(--s-5);
  padding: var(--s-6) 0;
  border-bottom: 1px solid var(--line);
}
.tenets__n {
  font-family: var(--display);
  font-size: var(--step-2);
  color: var(--accent-text);
  line-height: 1;
}
.tenets h3 { font-family: var(--sans); font-size: 1rem; font-weight: 600; margin-bottom: var(--s-2); }
.tenets p { font-size: 0.9rem; color: var(--text-muted); line-height: 1.65; max-width: 62ch; }

/* TENETS AS A ROW, added 2026-08-06.

   The homepage ran this list as the right half of a .split, with a heading and
   a two line lead as the left half. The heading block is about 180px tall and
   the list is about 610px, so the left column ended and 390px of the section
   was empty down the left hand side. Measured, it was the largest single hole
   on the site, and it is the one the brief named first.

   Centring the short column was considered and rejected: .offer-hero already
   tried exactly that and the note there records why it measures worse, which is
   that it moves the hole from below the copy, where it reads as the list being
   long, up to the fold, where it reads as a fault. Rebalancing the column ratio
   was measured too and recovers about 110px of the 390px, because no ratio
   fixes a 430px difference in column height.

   So the head goes full width as an ordinary .s-head and the three items run as
   a row underneath. The section fills its row, and it is shorter than it was.
   These are numbered index entries with a hairline, not three feature cards:
   there is no container, no fill and no shadow, which is the distinction that
   matters. Below 900px it returns to the stacked list it has always been. */
@media (min-width: 901px) {
  .tenets--row {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0 var(--s-7);
    border-top: 1px solid var(--line);
  }
  /* The hairline belongs to the row here, not to each item, or three of them
     stack up as a heavy rule under a three column band. */
  .tenets--row li { border-bottom: 0; padding-block: var(--s-6) 0; }
}

/* 8.9 Quotes */
.quotes { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-5); }
.quote {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--s-6);
}
.quote blockquote {
  font-family: var(--display);
  font-size: var(--step-2);
  font-style: italic;
  font-weight: 400;
  color: var(--text);
  line-height: 1.45;
  padding-inline-start: var(--s-4);
  border-inline-start: 2px solid var(--accent);
  margin-bottom: var(--s-5);
}
.quote figcaption { font-size: 0.85rem; color: var(--text-muted); line-height: 1.5; }
.quote figcaption b { display: block; color: var(--text); font-weight: 600; }

/* 8.10 Risk reversal */
.reversal {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
}
.reversal__item { background: var(--bg-raised); padding: var(--s-5) var(--s-6); }
.reversal__item h3 { font-family: var(--sans); font-size: 0.95rem; font-weight: 600; margin-bottom: var(--s-2); }
.reversal__item p { font-size: 0.875rem; color: var(--text-muted); line-height: 1.6; }

/* 8.11 Forms.
   Labels above inputs, never placeholder-as-label. Helper text present in
   markup. Errors below the field. Input borders use --line-strong so the field
   boundary itself clears 3:1. */
.field { display: grid; gap: var(--s-2); text-align: start; }
.field label { font-size: 0.85rem; font-weight: 600; color: var(--text); }
.field input,
.field textarea {
  width: 100%;
  min-height: 44px;
  padding: var(--s-3) var(--s-4);
  background: var(--bg);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-ctl);
  color: var(--text);
  font-family: var(--sans);
  font-size: 0.95rem;
  transition: border-color var(--dur-fast) var(--ease);
}
/* -------------------------------------------------------------------------
   The three topic chips, added 2026-08-19.
   ----------------------------------------------------------------------
   What somebody is booking about, as one tap rather than a paragraph typed on
   a phone keyboard. See aa_booking_topics() for why the requirement stayed and
   the typing went.

   Radio inputs styled as chips. The input itself is not hidden with
   display:none, which would take it out of the accessibility tree and off the
   keyboard: it is clipped to a pixel, so it still focuses, still announces as a
   radio in a named group, and still drives :checked and :focus-visible on the
   label around it.

   Logical properties throughout, per CLAUDE.md, so this mirrors itself under
   dir=rtl without a second rule.
   ------------------------------------------------------------------------- */
.topics { border: 0; padding: 0; margin: 0; min-inline-size: 0; }
.topics__legend {
  padding: 0;
  margin-block-end: var(--s-2);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
}
.topics__list { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.topics__chip { position: relative; display: inline-flex; }
.topics__chip input {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  opacity: 0;
  margin: 0;
  pointer-events: none;
}
.topics__chip span {
  display: inline-flex;
  align-items: center;
  /* 44px, so the tap target clears the minimum on a phone without a spacer. */
  min-block-size: 44px;
  padding-inline: var(--s-4);
  padding-block: var(--s-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  background: var(--bg);
  color: var(--text);
  font-size: 0.9rem;
  line-height: 1.3;
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}
.topics__chip:hover span { border-color: var(--accent-text); }
.topics__chip input:checked + span {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  font-weight: 600;
}
/* On the label, not the input, because the input is the clipped pixel and a
   ring drawn on it would be invisible. */
.topics__chip input:focus-visible + span {
  outline: 2px solid var(--accent-text);
  outline-offset: 2px;
}

/* Resizable vertically only. Horizontal drag escapes the panel and breaks the
   grid on the contact page, which is the one place this field appears. */
.field textarea { resize: vertical; line-height: 1.6; min-height: 8rem; }
.field input::placeholder,
.field textarea::placeholder { color: var(--text-muted); opacity: 1; }
.field input:hover,
.field textarea:hover { border-color: var(--accent-text); }
.field input:focus-visible,
.field textarea:focus-visible { border-color: var(--accent-text); outline-offset: 1px; }
.field__help { font-size: 0.78rem; color: var(--text-muted); line-height: 1.5; }
.field__error { font-size: 0.82rem; color: var(--danger-text); }

.capture {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--s-6);
}
.capture form { display: grid; gap: var(--s-4); }
.capture__note { font-size: 0.78rem; color: var(--text-muted); line-height: 1.5; }

/* reCAPTCHA v3. Google's floating badge may be hidden only while this notice
   is visible in its place, which is what inc/recaptcha.php prints under the
   button. visibility, not display: the widget still has to load and run.
   Same size and colour as .field__help rather than a new step in the scale,
   because a second, smaller grey is how fine print becomes unreadable. */
.grecaptcha-badge { visibility: hidden; }
.capture__legal { font-size: 0.78rem; color: var(--text-muted); line-height: 1.5; }
.capture__legal a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.capture__legal a:hover { color: var(--text); }

/* The token is fetched from Google on submit, which is a network round trip
   with nothing to show for it. Without this the button absorbs the click and
   appears to do nothing. */
.capture form[aria-busy='true'] .btn { opacity: 0.7; cursor: progress; pointer-events: none; }
.notice {
  padding: var(--s-4) var(--s-5);
  border-radius: var(--r-ctl);
  border: 1px solid var(--line-strong);
  background: rgba(37, 99, 235, 0.08);
  font-size: 0.9rem;
  color: var(--text);
  margin-bottom: var(--s-5);
}
.notice--error { border-color: var(--danger-line); background: var(--danger-bg); }

/* 8.12 FAQ — native disclosure, no JS, keyboard accessible for free. */
.faq { max-width: 780px; border-top: 1px solid var(--line); }
.faq details { border-bottom: 1px solid var(--line); }
.faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  min-height: 44px;
  padding: var(--s-5) 0;
  cursor: pointer;
  list-style: none;
  font-family: var(--display);
  font-size: var(--step-1);
  color: var(--text);
  transition: color var(--dur-fast) var(--ease);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--text-strong); }
.faq summary::after {
  content: '+';
  flex-shrink: 0;
  font-family: var(--sans);
  font-size: 1.3rem;
  color: var(--accent-text);
  transition: transform var(--dur) var(--ease);
}
.faq details[open] summary::after { transform: rotate(45deg); }
.faq details > div { padding-bottom: var(--s-5); font-size: 0.92rem; color: var(--text-muted); line-height: 1.7; max-width: var(--measure); }

/* 8.13 Technical detail disclosure — depth stays available, stops leading. */
.tech { margin-top: var(--s-5); }
.tech summary {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 44px;
  cursor: pointer;
  list-style: none;
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--accent-text);
}
.tech summary::-webkit-details-marker { display: none; }
.tech summary::after { content: '+'; font-size: 1rem; transition: transform var(--dur) var(--ease); }
.tech[open] summary::after { transform: rotate(45deg); }
.tech > div {
  margin-top: var(--s-3);
  padding: var(--s-4) var(--s-5);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r-ctl);
  font-size: 0.85rem;
  color: var(--text-muted);
  line-height: 1.65;
}
.tech li { padding-inline-start: var(--s-4); position: relative; }
.tech li + li { margin-top: var(--s-2); }
.tech li::before { content: '\2013'; position: absolute; inset-inline-start: 0; color: var(--accent-text); }

/* 8.14 Empty state.
   A section with nothing to show says so plainly. It never renders filler,
   and it never renders a placeholder that looks like content. */
.empty {
  border: 1px dashed var(--line-strong);
  border-radius: var(--r);
  padding: var(--s-7) var(--s-6);
  text-align: center;
  color: var(--text-muted);
}
.empty h3 { font-family: var(--sans); font-size: 1rem; font-weight: 600; margin-bottom: var(--s-3); }
.empty p { font-size: 0.9rem; max-width: 52ch; margin-inline: auto; line-height: 1.65; }

/* 8.15 Article cards, used by the Insights index and archives */
.posts { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s-5); }
.post-card {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--s-5);
  text-decoration: none;
  color: inherit;
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.post-card:hover { border-color: var(--line-strong); transform: translateY(-3px); }
.post-card h3 { font-size: var(--step-1); line-height: 1.3; margin-bottom: var(--s-3); }
/* Clamped, because the index stopped being uniform on 2026-08-05. The firm's
   own articles carry no stored excerpt, so `excerpt_length` trims them to 28
   words; the 44 syndicated posts each carry a hand-written one of 60 to 100
   words, and `excerpt_length` does not apply to a stored excerpt. Merging the
   two into one grid put a 6 line card beside an 11 line card and the rows lost
   their rhythm. Clamping is the render-time fix; the alternative was rewriting
   44 excerpts in the database, which rule 4 does not allow. */
.post-card p {
  font-size: 0.875rem;
  color: var(--text-muted);
  line-height: 1.6;
  flex: 1;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 5;
  line-clamp: 5;
  overflow: hidden;
}
.post-card__tag {
  display: block;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent-text);
  margin-bottom: var(--s-3);
}
.post-card__meta {
  margin-top: var(--s-4);
  padding-top: var(--s-3);
  border-top: 1px solid var(--line);
  font-size: 0.75rem;
  color: var(--text-muted);
}

.post-lead {
  display: grid;
  grid-template-columns: 1.5fr 1fr;
  gap: var(--s-7);
  align-items: start;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--s-7);
  margin-bottom: var(--s-6);
  text-decoration: none;
  color: inherit;
  transition: border-color var(--dur) var(--ease);
}
.post-lead:hover { border-color: var(--line-strong); }
.post-lead h3 { font-size: var(--step-3); margin-bottom: var(--s-4); }
.post-lead p {
  color: var(--text-muted);
  line-height: 1.7;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
  line-clamp: 4;
  overflow: hidden;
}

/* One column, for a lead card with no key points beside it.
   The second column has always been the key-points block, and `key_points` is
   empty on every post in the database. That was survivable while the lead was
   always one of the firm's seven articles, which could grow one. The merge on
   2026-08-05 made the newest post of any kind the lead, and a syndicated news
   item will never carry key points, so the half-empty two-column card became
   the normal case rather than the exception. The measure is capped instead of
   running the full width of the card, which at this size would be a 130
   character line. */
.post-lead--solo { grid-template-columns: minmax(0, 68ch); }
.post-lead__meta { margin-top: var(--s-5); font-size: 0.8rem; color: var(--text-muted); }
.post-lead__points h4 {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--s-3);
}
.post-lead__points li {
  display: flex;
  gap: var(--s-3);
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--line);
  font-size: 0.875rem;
  color: var(--text);
  line-height: 1.5;
}
.post-lead__points li::before { content: '\2013'; color: var(--accent-text); flex-shrink: 0; }

.divider-label {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--s-5);
}
.divider-label::after { content: ''; flex: 1; height: 1px; background: var(--line); }

/* pagination */
.pagination { margin-top: var(--s-7); display: flex; justify-content: center; }
.pagination .nav-links { display: flex; gap: var(--s-2); flex-wrap: wrap; }
.page-numbers {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  height: 44px;
  padding: 0 var(--s-3);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-ctl);
  color: var(--text);
  font-size: 0.875rem;
  text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.page-numbers:hover { border-color: var(--accent-text); color: var(--accent-text); }
.page-numbers.current { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.page-numbers.dots { background: none; border-color: transparent; color: var(--text-muted); }

/* 8.16 Contact channels.
   The direct lines, as a record rather than as cards. Four ways to reach a
   firm is not four features, and giving each one a box would say it was.
   Same dt/dd idiom as .founder__record and .svc__spec, one hairline between
   rows rather than a border on every side of everything. */
.channels {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--s-6);
}
.channels > div { padding: var(--s-4) 0; border-bottom: 1px solid var(--line); }
.channels > div:first-child { padding-top: 0; }
.channels > div:last-child { padding-bottom: 0; border-bottom: none; }
.channels dt {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--s-1);
}
.channels dd { font-size: var(--step-1); line-height: 1.5; color: var(--text); }
.channels dd a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--text);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease);
}
.channels dd a:hover { color: var(--accent-text); }
.channels dd address { font-style: normal; font-size: 0.95rem; color: var(--text); line-height: 1.6; }
.channels__note { margin-top: var(--s-4); font-size: 0.85rem; color: var(--text-muted); line-height: 1.6; max-width: 46ch; }

/* 8.17 Syndicated feed signpost.
   One quiet line at the foot of the Insights index. The 43 republished news
   items keep their URLs and their own archive; this is how a reader still
   finds them without them sitting above the firm's own writing. Deliberately
   not a card: it is a pointer, not content. */
.feed-note {
  margin-top: var(--s-7);
  padding-top: var(--s-5);
  border-top: 1px solid var(--line);
  font-size: 0.875rem;
  color: var(--text-muted);
  line-height: 1.65;
}
.feed-note a {
  color: var(--accent-text);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.feed-note a:hover { color: var(--text-strong); }

/* 8.18 The scheduler.
   Replaces a link into a Google calendar. Two columns: dates on the left,
   times and the form on the right, which is the arrangement every scheduler
   converged on because it keeps the whole decision on one screen.

   Contrast, since a calendar is mostly small text in small boxes:
     available date   --text on --surface-2, 12.7:1, plus a --line-strong
                      border, which is the 3:1 boundary 1.4.11 asks for
     unavailable date --text-muted at full strength, 7.25:1. It is told apart
                      by having no fill and no border, never by being dimmer,
                      because a dimmed date is either unreadable or ambiguous
     chosen           --accent fill with white, 5.17:1
     chosen time      white on --accent, and #f0f5ff for the secondary line,
                      4.73:1

   Slot fills are --bg rather than --surface, matching .field inputs, so the
   --line-strong border clears 3:1 against what is immediately inside it.

   Motion is hover and selection feedback at --dur-fast. Nothing moves on its
   own, because somebody choosing a time is reading, not being entertained. */
.booking { scroll-margin-top: calc(var(--nav-h) + var(--s-4)); }
/* The section is a focus target so that following #book from another page
   moves focus here and not just the scroll position. Focus arrives
   programmatically, never by tabbing, so it must not paint a ring around
   the whole section: the control the visitor then tabs to shows its own. */
.booking:focus { outline: none; }

.booking__panel {
  display: grid;
  grid-template-columns: minmax(320px, 380px) 1fr;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
}
.booking__cal {
  padding: var(--s-5);
  border-inline-end: 1px solid var(--line);
}
.booking__pick { padding: var(--s-6); }

.cal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  margin-bottom: var(--s-5);
}
.cal__month {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--step-2);
  line-height: 1.1;
  color: var(--text-strong);
}
.cal__nav {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-ctl);
  background: var(--bg);
  color: var(--text);
  font-size: 1.15rem;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.cal__nav:hover { border-color: var(--accent-text); color: var(--accent-text); }
/* Hidden rather than removed, so the month title does not jump sideways at the
   two ends of the range. */
.cal__nav.is-off { visibility: hidden; }

.cal__dows {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: var(--s-1);
  margin-bottom: var(--s-2);
}
.cal__dows span {
  text-align: center;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.cal__days {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: var(--s-1);
}
.cal__day,
.cal__pad {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  border-radius: var(--r-ctl);
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}
.cal__pad { visibility: hidden; }
.cal__day.is-closed { color: var(--text-muted); }
.cal__day.is-open {
  position: relative;
  background: var(--surface-2);
  border: 1px solid var(--line-strong);
  color: var(--text);
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}
.cal__day.is-open:hover {
  background: var(--accent-deep);
  border-color: var(--accent-text);
  color: var(--text-strong);
}
.cal__day.is-picked,
.cal__day.is-picked:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
/* Which square is today. A rule rather than a dot, and supplementary: the
   calendar already opens on the first date that has anything in it. */
.cal__day.is-today { position: relative; }
.cal__day.is-today::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: 5px;
  width: 12px;
  height: 2px;
  transform: translateX(-50%);
  border-radius: 1px;
  background: currentColor;
  opacity: 0.6;
}

.cal__note {
  margin-top: var(--s-4);
  font-size: 0.78rem;
  color: var(--text-muted);
  line-height: 1.5;
}

.slots { border: 0; padding: 0; margin: 0; }
.slots legend {
  padding: 0;
  margin-bottom: var(--s-4);
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--step-2);
  line-height: 1.15;
  color: var(--text-strong);
}
.slots__none { font-size: 0.9rem; color: var(--text-muted); line-height: 1.6; }

.slot { display: block; position: relative; }
.slot + .slot { margin-top: var(--s-3); }
/* Kept in the accessibility tree and in the tab order. The visible control is
   the box, so the two must never come apart. */
.slot input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  opacity: 0;
}
.slot__box {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  min-height: 52px;
  padding: var(--s-3) var(--s-5);
  background: var(--bg);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-ctl);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.slot:hover .slot__box { border-color: var(--accent-text); }
.slot input:focus-visible + .slot__box {
  outline: 2px solid var(--text-strong);
  outline-offset: 3px;
}
.slot__time {
  font-size: 1rem;
  font-weight: 600;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.slot__meta { font-size: 0.8rem; color: var(--text-muted); }
.slot input:checked + .slot__box { background: var(--accent); border-color: var(--accent); }
.slot input:checked + .slot__box .slot__time { color: var(--on-accent); }
.slot:active .slot__box { transform: scale(0.97); }
.slot input:checked + .slot__box .slot__time { color: var(--text-strong); }
/* #d8e3fb was here and the note above claimed 5.14:1 for it. Measured, it is
   4.01:1 on --accent at 12.8px, which fails 1.4.3. #f0f5ff is 4.73:1 and still
   reads as the quieter of the two lines, because what makes it secondary is
   the size, not the last few percent of lightness. */
.slot input:checked + .slot__box .slot__meta { color: #f0f5ff; }

.booking__state {
  margin-top: var(--s-4);
  font-size: 0.85rem;
  color: var(--text-muted);
  line-height: 1.5;
}

.booking__fields {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s-4);
  margin-top: var(--s-6);
  padding-top: var(--s-6);
  border-top: 1px solid var(--line);
}
.field--wide { grid-column: 1 / -1; }
.field__optional { font-weight: 400; font-size: 0.78rem; color: var(--text-muted); }
/* Shorter than the contact page textarea. This one is a hint, not the message. */
.booking__fields textarea { min-height: 5.5rem; }

.booking__pick .btn--full { margin-top: var(--s-5); }
.booking__pick .btn[disabled] { opacity: 0.7; cursor: progress; }
.booking__micro {
  margin-top: var(--s-3);
  font-size: 0.78rem;
  color: var(--text-muted);
  line-height: 1.5;
  text-align: center;
}
/* This theme styles links by class rather than with a blanket `a` rule, so a
   bare anchor here inherits the user agent's #0000EE, which on this panel is
   1.5:1 and unreadable. --accent-text is 6.62:1 on --surface. Underlined as
   well, so the link is not signalled by colour alone. */
.booking__micro a {
  color: var(--accent-text);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.booking__micro a:hover { color: var(--text-strong); }

/* 8.19 The confirmation, and releasing a time. */
.booking__done {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--s-7);
}
.booking__done h2 { margin-bottom: var(--s-3); }
.booking__done p { color: var(--text-muted); line-height: 1.7; max-width: 62ch; }
.booking__done-when {
  font-size: var(--step-2);
  line-height: 1.3;
  color: var(--text-strong) !important;
  margin-bottom: var(--s-6);
}
.booking__facts {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-5);
  margin-bottom: var(--s-6);
  padding: var(--s-5) 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.booking__facts dt {
  margin-bottom: var(--s-2);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.booking__facts dd { font-size: 0.95rem; color: var(--text); }
.booking__facts dd a { color: var(--accent-text); text-decoration: underline; text-underline-offset: 3px; }
.booking__facts dd a:hover { color: var(--text-strong); }
/* Reschedule and cancel. Two equal-weight actions, because neither is the one
   we are steering somebody towards: a meeting that has stopped working should
   be as easy to move as to drop. Wrapping rather than a two-column grid, so
   they stack on a narrow screen without the labels being squeezed. */
.booking__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin-top: var(--s-6);
  padding-top: var(--s-6);
  border-top: 1px solid var(--line);
}
.booking__actions form { margin: 0; }
.booking__actions + .field__help { margin-top: var(--s-3); }

/* 8.20 The same picker, in a dialog.

   Behind the header button and the one in the homepage hero. Nothing here is a
   new look: --bg-raised holding a --surface panel is the exact relationship
   `.section--raised` has with `.booking__panel` on the offer page, so the thing
   that opens is the thing they would have been sent to.

   Native <dialog>, which means the top layer rather than a z-index. Nothing in
   this stylesheet has to make room for it, and it cannot end up underneath the
   nav at 100 or the skip link at 200.

   Motion is one 240ms fade and an 8px rise, matching every other transition
   here. The entry state is a class the script removes on the next frame, so the
   resting state in this file is the visible one: a stylesheet read on its own
   describes a dialog that shows. */
.book-modal {
  /* A modal dialog is centred by the user agent with `inset: 0; margin: auto`,
     and the reset at the top of this file resets that margin to 0 along with
     everybody else's, which pins the dialog to the top left corner. Restated
     here rather than carved out of the reset, because the reset is right and
     this element is the exception. */
  inset: 0;
  margin: auto;
  width: min(1060px, calc(100% - var(--s-6)));
  max-height: calc(100dvh - var(--s-6));
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--bg-raised);
  color: var(--text);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
/* The UA sheet hides a closed dialog with display:none, so the flex column is
   scoped to [open] rather than overriding it. */
.book-modal[open] {
  display: flex;
  flex-direction: column;
}
.book-modal.is-entering { opacity: 0; transform: translateY(8px); }

.book-modal::backdrop {
  background: rgba(3, 7, 16, 0.76);
  backdrop-filter: blur(2px);
}

/* THE ENQUIRY DIALOG, added 2026-08-06.

   Reuses the booking dialog's shell, backdrop and close button wholesale, and
   changes one thing: the width. The scheduler needs 1060px because it is a
   month beside a list of times. A four field form at that width is four very
   wide inputs with a lot of nothing beside them, which is the same defect this
   session was opened to fix, so it gets a form's measure instead. */
.contact-modal { width: min(560px, calc(100% - var(--s-6))); }
.contact-modal__title { font-size: var(--step-3); }
.contact-modal__lead {
  margin-top: var(--s-3);
  color: var(--text-muted);
  font-size: 0.95rem;
  line-height: 1.65;
}
.contact-modal .capture { margin-top: var(--s-5); }
.contact-modal__alt {
  margin-top: var(--s-5);
  padding-top: var(--s-5);
  border-top: 1px solid var(--line);
  font-size: 0.875rem;
  color: var(--text-muted);
  line-height: 1.65;
}

.book-modal__head {
  display: flex;
  justify-content: flex-end;
  flex: 0 0 auto;
  padding: var(--s-4) var(--s-4) 0;
}
/* Same control as the calendar arrows, for the same reason: it is the same
   size, the same border and the same 3:1 boundary. */
.book-modal__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-ctl);
  background: var(--bg);
  color: var(--text);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.book-modal__close:hover { border-color: var(--accent-text); color: var(--accent-text); }

/* The panel scrolls, not the dialog, so the close button stays put through a
   long form on a short screen. */
.book-modal__body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: var(--s-2) var(--s-6) var(--s-6);
}
.booking--embedded .s-head { margin-bottom: var(--s-5); }
.booking--embedded h2:focus { outline: none; }

/* What is coming, in its own shape. A spinner would say something is happening
   and nothing about what, and this is two panes and a calendar every time. */
.book-skeleton {
  display: grid;
  grid-template-columns: minmax(320px, 380px) 1fr;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
}
.book-skeleton__cal {
  padding: var(--s-5);
  border-inline-end: 1px solid var(--line);
}
.book-skeleton__pick { padding: var(--s-6); }
.book-skeleton__bar {
  display: block;
  height: 20px;
  margin-bottom: var(--s-5);
  border-radius: var(--r-ctl);
  background: var(--surface-2);
}
.book-skeleton__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: var(--s-1);
}
.book-skeleton__cell {
  min-height: 44px;
  border-radius: var(--r-ctl);
  background: var(--surface-2);
}
.book-skeleton__slot {
  display: block;
  min-height: 52px;
  margin-bottom: var(--s-3);
  border-radius: var(--r-ctl);
  background: var(--surface-2);
}
/* Slow, shallow, and off entirely under reduced motion, where the blocks are
   just blocks. */
@media (prefers-reduced-motion: no-preference) {
  .book-skeleton__bar,
  .book-skeleton__cell,
  .book-skeleton__slot { animation: aa-wait 1.6s var(--ease) infinite; }
}
@keyframes aa-wait {
  0%, 100% { opacity: 0.5; }
  50%      { opacity: 0.85; }
}

/* 8.24 The intake form.

   Seven questions on one page, reached from an email by somebody who has
   already booked. See parts/intake-form.php for why it is one page rather than
   a wizard.

   It borrows rather than invents. The fields are .field, the radio cards are
   the .slot pattern under another name, the buttons are .btn. Two things are
   new and both earn it: a measure narrower than .shell, because 1200px of form
   is unreadable and a textarea that wide invites an essay nobody wants to
   write; and .field__eg, because question 3 is the one the whole form turns on
   and showing the shape of a good answer is worth more than another sentence
   telling people to be specific.

   No reveal animation anywhere on this page. Every other surface on the site
   fades its sections in on scroll, and doing it here would delay the first
   interaction on a page whose only job is to be filled in. */
.intake {
  max-width: 42rem;
  margin-inline: auto;
  width: 100%;
}

/* 8.24.1 The meeting this form belongs to. */
.intake__meeting {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--s-5);
  margin-bottom: var(--s-7);
  display: grid;
  gap: var(--s-4);
}
.intake__meeting dt {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--s-1);
}
.intake__meeting dd {
  font-size: 0.95rem;
  line-height: 1.55;
  color: var(--text);
  margin: 0;
}
/* The address, quieter than the company beside it and on its own line, because
   it is a fact being read back rather than a second value. */
.intake__quiet { display: block; color: var(--text-muted); font-size: 0.85rem; }

/* 8.24.2 The form itself.

   --s-6 between questions rather than --s-4. These are questions with helper
   text and examples under them, not a name and an email, and at a tighter gap
   the help under one reads as the label of the next. */
.intake__form { display: grid; gap: var(--s-6); }

/* The questions are the content of this page, not labels on a form. Everywhere
   else on the site a .field label names a thing already obvious from context,
   "Full name", and 0.85rem is right for it. Here the label IS the question, it
   is a full sentence, and it is what the reader is here to read. It gets body
   size and the help under it gets most of the way there. */
.intake__form .field > label,
.intake__form .field--group legend {
  font-size: var(--step-0);
  font-weight: 600;
  line-height: 1.45;
  color: var(--text);
}
.intake__form .field__help { font-size: 0.85rem; }

/* A fieldset is the only correct wrapper for a radio group, and it arrives with
   a border and padding of its own.

   `display: block` is not tidiness, it undoes .field's grid. A <legend> inside
   a grid or flex fieldset is laid out inconsistently across engines, having
   historically not been a box at all, and the failure is a legend that either
   overlaps the first option or loses its own line. Block, and the spacing is
   margins. */
.field--group {
  display: block;
  border: 0;
  padding: 0;
  margin: 0;
  min-inline-size: 0;
}
.field--group legend {
  display: block;
  padding: 0;
  margin-bottom: var(--s-3);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
}
.field--group .field__error { margin-top: var(--s-3); }

/* The example under question 3. A hairline on the leading edge rather than a
   panel: it is an aside to the field above it, and boxing it would give it the
   same weight as the answer. */
.field__eg {
  margin-top: var(--s-3);
  padding-inline-start: var(--s-4);
  border-inline-start: 2px solid var(--line-strong);
  font-size: 0.78rem;
  line-height: 1.6;
  color: var(--text-muted);
}

/* 8.24.3 Radio cards.

   The .slot pattern from the calendar, with the input kept in the tab order and
   in the accessibility tree while the box does the showing. The two must never
   come apart; see the note on .slot. */
.choices { display: grid; gap: var(--s-3); }
/* The five size bands are short enough to sit beside each other and wrong as a
   stack: they are one ordered scale, and reading them as a row is how somebody
   finds their place on it. Flex rather than grid columns because they must
   wrap. Five of them at their full padding measure past 42rem, so the row
   variant is tighter, and below roughly 560px they fall onto two lines. */
.choices--row { display: flex; flex-wrap: wrap; }
.choices--row .choice__box { padding-inline: var(--s-4); }

.choice { display: block; position: relative; }
.choice input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  opacity: 0;
}
.choice__box {
  display: flex;
  align-items: center;
  min-height: 48px;
  padding: var(--s-3) var(--s-5);
  background: var(--bg);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-ctl);
  font-size: 0.9rem;
  line-height: 1.45;
  color: var(--text);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}
.choice__box:hover { border-color: var(--accent-text); }
.choice input:focus-visible + .choice__box {
  outline: 2px solid var(--text-strong);
  outline-offset: 3px;
}
/* --accent is a fill colour and --text-strong on it is 4.6:1, which clears AA
   at this size. --accent-text on --accent would not; see the token notes. */
.choice input:checked + .choice__box {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
}

/* 8.24.4 The two details that are not questions, and the notes. */
.intake__more {
  display: grid;
  gap: var(--s-6);
  padding-top: var(--s-6);
  border-top: 1px solid var(--line);
}
.intake__more h2 { font-size: var(--step-1); margin: 0; }

.intake__note {
  font-size: 0.8rem;
  line-height: 1.6;
  color: var(--text-muted);
}
.intake__note a {
  color: var(--accent-text);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.intake__note a:hover { color: var(--text-strong); }
.intake__note--foot {
  margin-top: var(--s-7);
  padding-top: var(--s-5);
  border-top: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
}

/* 8.24.5 Their answers, read back. */
.intake__answers { display: grid; gap: var(--s-5); margin-bottom: var(--s-6); }
.intake__answers dt {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--s-2);
}
.intake__answers dd {
  margin: 0;
  color: var(--text);
  line-height: 1.7;
  white-space: pre-wrap;
}

/* --------------------------------------------------------------------------
   9. Page compositions
   -------------------------------------------------------------------------- */

/* 9.1 Home hero — asymmetric split. The right column carries the offer, which
   is real content, not a decorative asset. */
.hero {
  /* Not 100dvh: at that height the content floats in the middle of an empty
     field and the first section below never hints at itself. Tall enough to
     own the fold, short enough that the page reads as continuing. */
  min-height: min(82dvh, 760px);
  display: flex;
  align-items: center;
  padding: calc(var(--nav-h) + var(--s-7)) var(--s-8) var(--s-8);
  position: relative;
  overflow: hidden;
}
.hero::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse 55% 55% at 75% 45%, rgba(37, 99, 235, 0.10), transparent 70%),
    radial-gradient(ellipse 40% 40% at 12% 85%, rgba(37, 99, 235, 0.06), transparent 65%);
  pointer-events: none;
}
.hero__grid {
  position: relative;
  max-width: var(--shell);
  margin-inline: auto;
  width: 100%;
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
  gap: var(--s-8);
  align-items: center;
}
.hero h1 { margin-bottom: var(--s-5); }
.hero__sub {
  font-size: var(--step-1);
  color: var(--text-muted);
  line-height: 1.65;
  max-width: 46ch;
  margin-bottom: var(--s-6);
}
.hero__actions { display: flex; gap: var(--s-3); flex-wrap: wrap; }
.hero__eyebrow { display: flex; align-items: center; gap: var(--s-4); flex-wrap: wrap; margin-bottom: var(--s-5); }
/* The credential, now under the client marks rather than in the hero. */
.logos__cred { margin-top: var(--s-5); text-align: center; }
.hero__eyebrow .eyebrow { margin-bottom: 0; }

/* the offer, above the fold */
.offer-card {
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r);
  padding: var(--s-6);
}
.offer-card__name { font-family: var(--display); font-size: var(--step-2); color: var(--text-strong); margin-bottom: var(--s-1); }
.offer-card__len { font-size: 0.8rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--accent-text); margin-bottom: var(--s-5); }
.offer-card h3 {
  font-family: var(--sans);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--s-3);
}
.offer-card ol { counter-reset: o; }
.offer-card ol li {
  counter-increment: o;
  display: grid;
  grid-template-columns: 1.5rem 1fr;
  gap: var(--s-3);
  padding: var(--s-3) 0;
  border-top: 1px solid var(--line);
  font-size: 0.9rem;
  color: var(--text);
  line-height: 1.5;
}
.offer-card ol li::before { content: counter(o); color: var(--accent-text); font-variant-numeric: tabular-nums; }
.offer-card__foot { margin-top: var(--s-5); font-size: 0.82rem; color: var(--text-muted); line-height: 1.6; }

/* 9.2 Inner page hero */
.page-hero {
  /* The bottom was --s-8 against the section below it, which now opens at
     --s-7, so the pair read as a single oversized gap under the h1. */
  padding: calc(var(--nav-h) + var(--s-7)) var(--s-8) var(--s-7);
  background: var(--bg-raised);
  border-bottom: 1px solid var(--line);
}
.page-hero__inner { max-width: var(--shell); margin-inline: auto; }
.page-hero h1 { max-width: 20ch; margin-bottom: var(--s-5); }
.page-hero .lead { max-width: 62ch; }
.page-hero--article h1 { font-size: var(--step-4); max-width: 24ch; }

.back-link {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 44px;
  font-size: 0.85rem;
  color: var(--text-muted);
  text-decoration: none;
  margin-bottom: var(--s-4);
}
.back-link::before { content: '\2190'; }
.back-link:hover { color: var(--text-strong); }

.article-meta {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  flex-wrap: wrap;
  margin-top: var(--s-5);
  font-size: 0.8rem;
  color: var(--text-muted);
}
.article-meta .tag {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent-text);
  background: rgba(37, 99, 235, 0.1);
  padding: var(--s-1) var(--s-3);
  border-radius: var(--r-ctl);
}

/* 9.3 Founder block */
.founder {
  display: grid;
  grid-template-columns: 260px 1fr;
  gap: var(--s-7);
  align-items: start;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--s-7);
}
/* No headshot yet: collapse the card instead of leaving a 260px hole where the
   photo will go. The narrower card keeps the text at the same measure it has in
   the two-column state, so adding the photo later does not reflow the reading. */
.founder--noimage { grid-template-columns: 1fr; max-width: 56rem; }
.founder__photo { border-radius: var(--r); border: 1px solid var(--line-strong); width: 100%; aspect-ratio: 4 / 5; object-fit: cover; }
.founder__name { font-family: var(--display); font-size: var(--step-3); color: var(--text-strong); margin-bottom: var(--s-1); }
.founder__role { font-size: 0.8rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--accent-text); margin-bottom: var(--s-5); }
.founder__record { border-top: 1px solid var(--line); margin-top: var(--s-5); }
.founder__record > div {
  display: grid;
  grid-template-columns: 10rem 1fr;
  gap: var(--s-4);
  padding: var(--s-4) 0;
  border-bottom: 1px solid var(--line);
  font-size: 0.9rem;
  line-height: 1.55;
}
.founder__record dt { color: var(--text-muted); font-size: 0.72rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; padding-top: 0.2em; }
.founder__record dd { color: var(--text); }
.founder__links { display: flex; gap: var(--s-4); flex-wrap: wrap; margin-top: var(--s-5); }

/* 9.4 Related reading */
.related { background: var(--bg-raised); border-top: 1px solid var(--line); padding: var(--s-8); }
.related__inner { max-width: 800px; margin-inline: auto; }
.related__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s-4); margin-top: var(--s-5); }

/* --------------------------------------------------------------------------
   9.5 The offer page.

   Laid out from rebuild/AI Readiness Review - Redesign (standalone).html, at
   that comp's measurements. Everything here is composition: no new colour, no
   new font, no new radius family.

   One deliberate departure. The comp's own type scale is smaller than this
   theme's, and the theme's `.s-head` clamps a section heading to 46ch. Applied
   here that clamp wraps every heading onto two lines and leaves two thirds of
   the row empty, which is what "a ton of white space" looked like. Section
   heads on this page are `.offer-head`: full width, at the comp's scale.
   -------------------------------------------------------------------------- */

/* 9.5.1 Hero. Copy and the scheduler, side by side. */
/* The top pad was nav + --s-7, which put 120px above the headline and pushed
   the card's own button off the bottom of the screen. The whole job of this
   hero is that somebody can pick a time without scrolling, so the space above
   is the first thing that gives. nav + --s-5 is 96px, inside the 6rem ceiling a
   hero should keep. */
.offer-hero {
  position: relative;
  overflow: hidden;
  padding: calc(var(--nav-h) + var(--s-5)) var(--s-8) var(--s-7);
  scroll-margin-top: var(--nav-h);
  background: radial-gradient(1100px 520px at 12% 0%, rgba(37, 99, 235, 0.13), transparent 65%);
}
/* Focus arrives here programmatically, by following #book from another page.
   Never by tabbing, so it must not paint a ring around a whole screen. */
.offer-hero:focus { outline: none; }
.offer-hero__inner {
  position: relative;
  max-width: var(--shell);
  margin-inline: auto;
  display: grid;
  grid-template-columns: 1.02fr 0.98fr;
  gap: var(--s-8);
  /* Top-aligned, and it was briefly centred. The card is a form with real
     labels above its inputs and a required question at the bottom, so it runs
     a couple of hundred pixels past the pitch beside it. Centring splits that
     difference above and below the copy, which sounds better and measures
     worse: it pushed the headline to 457px and put the empty half on the fold.
     Top-aligned, the same gap sits under the copy and below the fold, where it
     reads as the form being long rather than as a hole. */
  align-items: start;
}

.offer-hero__badges { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-bottom: var(--s-5); }

/* The comp's scale, not a step under it. At 3.4vw this headline set 49px against
   the comp's 64px, which cost it the authority the comp's has and, measurably,
   left the copy column ending 350px above the card beside it. That gap was the
   hole the page read as empty. At the comp's clamp the column runs to within
   about a line of the card and the headline is the first thing in the room. */
.offer-hero h1 {
  font-size: clamp(2.5rem, 4.2vw, 3.9rem);
  letter-spacing: -0.015em;
  margin-bottom: var(--s-5);
}
.offer-hero__sub {
  font-size: 1.15rem;
  line-height: 1.6;
  color: var(--text-muted);
  max-width: 32rem;
  margin-bottom: var(--s-6);
}

/* One line of named clients under the promise. Deliberately quiet: it is
   evidence, not a headline, and it sits between the pitch and the calendar
   where the eye is already travelling. */
/* -------------------------------------------------------------------------
   The four moves, and the three objections. Added 2026-08-19.
   ----------------------------------------------------------------------
   Two new layout families, deliberately unlike the card grids this page
   already uses twice: the moves are a numbered sequence read top to bottom,
   the objections are a definition list. Logical properties throughout, so both
   mirror from the dir attribute.
   ------------------------------------------------------------------------- */
.moves {
  list-style: none;
  counter-reset: move;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0;
  border-block-start: 1px solid var(--line);
  max-width: 46rem;
}
.move {
  counter-increment: move;
  display: grid;
  grid-template-columns: 3.25rem 1fr;
  /* Column gap only. Now that the p sits in the second column it is also on the
     second row, so a symmetric gap put 16px between a heading and its own
     sentence on top of the 8px the heading already carries. The two belong to
     each other more than either belongs to the next move, and 24px said the
     opposite. The row rhythm is the padding and the rule, not this. */
  gap: 0 var(--s-4);
  align-items: start;
  padding-block: var(--s-5);
  border-block-end: 1px solid var(--line);
}
/* Placement is explicit, and it has to be. `.move` declares two columns and
   holds three boxes: the counter, the h3 and the p. Left to auto-placement the
   counter takes row 1 column 1, the h3 takes row 1 column 2, and the p wraps
   onto row 2 into the column that is 3.25rem wide, where every sentence set one
   word per line at 52px on a laptop and 40px on a phone. It throws nothing and
   it overflows nothing, so the only thing that catches it is looking at the
   page. Shipped 2026-08-19 with the section itself.

   The counter is pinned to the first cell, both text boxes to the second, which
   is the layout the rule above was describing all along. */
.move::before {
  content: counter( move, decimal-leading-zero );
  grid-column: 1;
  grid-row: 1;
  font-family: var(--mono, var(--sans));
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--accent-text);
  padding-block-start: 0.2em;
  font-variant-numeric: tabular-nums;
}
.move h3,
.move p  { grid-column: 2; }
.move h3 { font-size: 1.05rem; line-height: 1.35; color: var(--text-strong); margin: 0 0 var(--s-2); }
.move p  { margin: 0; color: var(--text-muted); line-height: 1.6; max-width: 60ch; }
.moves__foot {
  margin-block-start: var(--s-6);
  max-width: 46rem;
  color: var(--text);
  line-height: 1.7;
}

.objections { margin: 0; display: grid; gap: var(--s-5); max-width: 46rem; }
.objection {
  padding-inline-start: var(--s-5);
  border-inline-start: 2px solid var(--accent-deep);
}
.objection dt {
  font-weight: 600;
  color: var(--text-strong);
  font-size: 1.05rem;
  line-height: 1.35;
  margin-block-end: var(--s-2);
}
.objection dd { margin: 0; color: var(--text-muted); line-height: 1.65; }

/* On a phone the counter goes above the heading rather than beside it. A
   2.5rem rail beside the text is 40px off a 342px column, and it was charging
   that to every line of every paragraph while `.moves__foot` directly below and
   `.objection dd` further down both run the full width. The moves read narrower
   than the page for no reason a reader could see.

   Stacked, the number is a label over its heading, which is exactly what
   `.step__n` already is in the agenda cards two sections down. Same idea, same
   size, same colour, so the phone layout of this section rhymes with one the
   page already has instead of inventing a third thing. */
@media (max-width: 640px) {
  .move { grid-template-columns: 1fr; gap: 0; }
  .move::before { margin-block-end: var(--s-2); padding-block-start: 0; }
  .move h3,
  .move p { grid-column: 1; }
}

/* The bottom margin is not optional and was missing until 2026-08-19. This line
   ends at 532px and the first deliverable started at 533, so a sentence naming
   three clients read as an unticked fourth item of the list under it rather
   than as the evidence for the promise above it. */
.offer-hero__clients {
  margin-block: var(--s-4);
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--text-muted);
  max-width: 32rem;
}

.deliver { display: grid; gap: var(--s-3); margin-bottom: var(--s-6); max-width: 33rem; }
.deliver li {
  display: grid;
  grid-template-columns: 20px 1fr;
  gap: var(--s-3);
  align-items: start;
  font-size: 0.96rem;
  line-height: 1.55;
  color: var(--text);
}
.deliver svg { width: 20px; height: 20px; margin-top: 0.22em; color: var(--accent-text); }
.deliver strong { color: var(--text-strong); font-weight: 600; }

.offer-hero__actions { display: flex; flex-wrap: wrap; gap: var(--s-3); align-items: center; }
.btn--xl { font-size: 1rem; padding: var(--s-4) var(--s-6); }
.offer-hero__micro {
  margin-top: var(--s-5);
  font-size: 0.85rem;
  color: var(--text-muted);
  line-height: 1.6;
  max-width: 44ch;
}

/* 9.5.2 The picker, as the hero's card.
   The inner panel gives up its own fill and border, or the hero would show a
   bordered box inside a bordered box. */
/* Everything under this heading is scoped to .booking--inline and only to it.
   The same picker is a full section at /booking and a dialog behind the header
   buttons; both have a screen to themselves and neither wants this density.

   The card measured 1056px against the comp's 713px, which put its own Confirm
   button 276px under the fold on a 900px screen. A booking page whose booking
   button cannot be seen is the whole of the conversion problem, so the rules
   below buy that height back a row at a time. What is not traded for it: the
   visible labels over every input, which stay because a placeholder is not a
   label, and the note field, which is required and answers why the meeting was
   booked. */
.booking--inline {
  background: linear-gradient(180deg, var(--panel-a), var(--panel-b));
  border: 1px solid var(--panel-line);
  border-radius: 12px;
  padding: var(--s-5) var(--s-6) var(--s-6);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.45);
}
.booking--inline .booking__panel {
  background: transparent;
  border: 0;
  border-radius: 0;
  overflow: visible;
}
/* Three classes deep on purpose. The responsive rules in section 11 restyle
   .booking__cal and .booking__pick at one class each and would otherwise win on
   document order and put a second set of paddings inside the card. */
.booking--inline .booking__panel .booking__cal {
  padding: 0 0 var(--s-5);
  margin-bottom: var(--s-5);
  border-inline-end: 0;
  border-bottom: 1px solid var(--line);
}
.booking--inline .booking__panel .booking__pick { padding: 0; }
.booking__panel--stacked { grid-template-columns: 1fr; }

/* ── The card's vertical budget ──────────────────────────────────────────────
   Each rule here is one row of the card, with what it was worth in pixels. */

/* The zone note. Inline it ships visually-hidden, because the visible copy of
   "Central" is the chip on the step 02 label and this card is held to a height
   budget. booking.js drops that class for a visitor whose own zone differs from
   ours, because then the two no longer say the same thing and the sentence is
   the one that explains the card. Absolutely positioned until that happens, so
   the margin below costs nothing to everybody else. -35px */
.booking--inline .cal__note { margin-top: var(--s-3); }

/* Step labels and the live region. -32px across the four of them. */
.booking--inline .booking__step { margin-bottom: var(--s-3); }
.booking--inline .booking__step-note {
  margin-inline-start: auto;
  font-weight: 500;
  letter-spacing: 0.08em;
  color: var(--text-muted);
}
.booking--inline .booking__state { margin-top: var(--s-2); }
/* The live region sits directly above the "03" label, and at one shared margin
   the two ran together into what looked like a single wrapped line. The step
   label needs the gap, not the region, so it is bought back here. Specificity
   has to clear the margin-top:0 rule above, which is why the selector is long. */
.booking--inline .booking__panel .booking__pick .booking__state + .booking__step {
  margin-top: var(--s-4);
}

/* Smaller than the month arrows for the same reason the strip is: they share a
   row with a label here. 30px still clears the 24x24 target 2.5.8 asks for. */
.booking--inline .days__head { margin-bottom: var(--s-3); }
.booking--inline .days__nav .cal__nav { width: 30px; height: 30px; }

/* The fields block. A rule plus --s-6 of padding above it separated the form
   from the times, which is a job the "03" label was already doing one line
   higher, so the card paid about 56px twice. */
.booking--inline .booking__fields {
  margin-top: var(--s-2);
  padding-top: 0;
  border-top: 0;
  gap: var(--s-3) var(--s-4);
}

/* Labels stay visible. They get the smaller of the two type steps and sit
   closer to their input, which is worth about 6px a field over four fields. */
.booking--inline .field { gap: var(--s-1); }
.booking--inline .field label { font-size: 0.8rem; }

/* Two lines of the note are enough to write one, and the field grows on focus
   for anybody who wants more room. -43px */
.booking--inline .booking__fields textarea { min-height: 3.75rem; }
.booking--inline .booking__pick .btn--full { margin-top: var(--s-4); }

.booking__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
  padding-bottom: var(--s-4);
  margin-bottom: var(--s-5);
  border-bottom: 1px solid var(--line);
}
.booking__head h2 { font-size: var(--step-2); }
.booking__head-meta {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent-text);
}
/* The reschedule head, which is a sentence rather than three facts. */
.booking__head > p:not(.booking__head-meta) {
  flex-basis: 100%;
  font-size: 0.85rem;
  color: var(--text-muted);
  line-height: 1.55;
}

/* The comp numbers the three things being asked for. Worth keeping: a calendar,
   a time list and a form is more steps than a stranger expects. */
.booking__step {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin-bottom: var(--s-4);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.booking__step b { color: var(--accent-text); }
.booking--inline .booking__panel .booking__pick .booking__step { margin-top: 0; }

/* The day strip. Five open days at a time instead of a month of squares, which
   is the single biggest reason the comp's card is shorter than a calendar. */
.days__head {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: var(--s-4);
}
.days__head .booking__step { margin-bottom: 0; }
.days__span {
  margin-inline-start: auto;
  font-size: 0.78rem;
  color: var(--text-muted);
  white-space: nowrap;
}
.days__nav { display: flex; gap: var(--s-2); flex: 0 0 auto; }
/* Smaller than the month arrows, which sit alone on a row. These share one with
   a label, and 36px still clears the 24x24 target 2.5.8 asks for. */
.days__nav .cal__nav { width: 36px; height: 36px; font-size: 1rem; }

.days { display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--s-2); }
.day {
  display: block;
  padding: var(--s-3) var(--s-1);
  text-align: center;
  text-decoration: none;
  background: var(--bg);
  border: 1px solid var(--line-strong);
  border-radius: 7px;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.day:hover { border-color: var(--accent-text); }
.day:active { transform: scale(0.97); }
.day__dow {
  display: block;
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.day__n {
  display: block;
  font-size: 1.3rem;
  font-weight: 500;
  line-height: 1.2;
  color: var(--text-strong);
}
/* The comp puts this in mint. One accent per page, so it is the accent. */
/* nowrap because the cell is narrower than the label. At the 0.78rem a phone
   gets, "3 open" measures about 44px inside 42px of content box and broke onto
   two lines, which made every day in the strip 25px taller for no information.
   It centres and spills a pixel into the 4px padding instead. The count is the
   one thing on this row that decides which day gets tapped, so it holds its
   size and the box gives way. */
.day__open { display: block; font-style: normal; font-size: 0.62rem; letter-spacing: 0.04em; color: var(--accent-text); white-space: nowrap; }
.day.is-picked { background: var(--accent); border-color: var(--accent); }
.day.is-picked .day__dow,
.day.is-picked .day__open { color: #e8f0ff; }
/* The numeral inside a picked day was still reading --text-strong, which is
   near black under a light theme and sat on the accent fill at 2.97:1. */
.day.is-picked .day__n { color: var(--on-accent); }

/* Times as chips, three across, wrapping to a second row on a day that drew
   four or five. Three columns rather than auto-fit so the chip is the same
   width on every day of the calendar: a two-window day would otherwise pay for
   its scarcity with chips half the width of the page. The trailing row is
   start-aligned, which the logical direction mirrors for Arabic.

   The second line is the closing time, or the visitor's own timezone when it
   differs; booking.js writes both into the same element, so it stays on the
   chip rather than being hidden. */
.slots--chips { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s-2); }
/* A <legend> in a grid container is a grid item, so it takes a cell and pushes
   the first chip onto the next row. Off the face of the card entirely here, as
   in the comp: the strip above already shows which day is picked, and this is
   the fieldset's accessible name, which still has to exist. */
.slots--chips legend {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
}
.slots--chips .slot + .slot { margin-top: 0; }
.slots--chips .slot__box {
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0;
  min-height: 52px;
  padding: var(--s-2) var(--s-1);
  text-align: center;
}
.slots--chips .slot__time { font-size: 0.85rem; }
/* 0.65rem until 2026-08-05, when this line stopped being the end time and
   became the meeting's actual time on the firm's clock, under the visitor's own.
   It is the only Central figure left on a converted card, so it is read rather
   than glanced at. The chip is min-height 52px and this does not reach it. */
.slots--chips .slot__meta { font-size: 0.7rem; }
.slots--chips .field__error { grid-column: 1 / -1; }
.slots--chips .slots__none { grid-column: 1 / -1; }

/* The one optional field, disclosed. A <details>, so it works with the script
   off and stays in the tab order. */
.booking__more > summary {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  min-height: 44px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
  cursor: pointer;
  list-style: none;
}
.booking__more > summary::-webkit-details-marker { display: none; }
.booking__more > summary::before {
  content: '+';
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-ctl);
  color: var(--accent-text);
  font-size: 0.95rem;
  line-height: 1;
}
.booking__more[open] > summary::before { content: '\2212'; }
.booking__more[open] > summary { margin-bottom: var(--s-3); }

/* The confirmation lands in the same slot, so it gives up its own card for the
   same reason the panel does. Three facts side by side need more width than a
   hero column has, so they stack here at every size. */
.booking--inline .booking__done {
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 0;
}
.booking--inline .booking__done h2 { font-size: var(--step-2); }
.booking--inline .booking__facts { grid-template-columns: 1fr; gap: var(--s-4); }

/* 9.5.3 The trust bar, straight under the hero.

   One flat tone for seven marks, dimmed and brought up on hover. The files are
   already keyed to transparency and flattened; see aa_client_logos() for why
   that happens at build time rather than with a CSS filter, which cannot key a
   white rectangle out of a JPEG. */
.trust {
  padding: var(--s-6) var(--s-8);
  background: var(--bg-raised);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
/* The label sits over the row rather than beside it. Seven marks at a size
   anyone can read do not leave room for a caption on the same line. */
.trust__inner {
  max-width: var(--shell);
  margin-inline: auto;
  display: grid;
  gap: var(--s-4);
}
/* A sentence since 2026-08-06, not a caption. It was uppercase at 0.13em
   tracking, which is legible for two words and unreadable for a line naming six
   sectors, so the tracking and the casing both come off. */
.trust__label {
  font-size: 0.85rem;
  color: var(--text-muted);
  max-width: 62ch;
  text-align: center;
  margin-inline: auto;
}
/* The trust bar draws the same wall as the homepage, at the same size, from the
   same rule. It used to be `space-between` across one row, which worked at
   seven marks and cannot work at seventeen: the last row of a wrapped
   `space-between` row spreads two logos to the full width of the shell with a
   thousand pixels of nothing between them.

   The per-file height exception for Midwest Barrel Co that lived here is gone.
   The files carry their own containment now, so a round seal and a wide
   wordmark size correctly from one rule. See .logos above. */
.trust__logos {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--s-5) var(--s-6);
}
.trust__logos img {
  width: 150px;
  height: auto;
  opacity: 0.75;
  transition: opacity var(--dur-fast) var(--ease);
}
.trust__logos img:hover { opacity: 1; }

/* 9.5.4 Section heads, full width, at the comp's scale.

   The padding here is the page's vertical rhythm and it is deliberately tighter
   than `.section` elsewhere on the site. At --s-9 top and bottom, two adjacent
   sections put 192px of nothing between the last line of one and the first of
   the next, which measured as the largest empty runs on the page. */
.offer-sec { padding: var(--s-7) var(--s-8); }
.offer-head { margin-bottom: var(--s-6); max-width: none; }
.offer-head h2 { font-size: clamp(1.75rem, 3vw, 2.75rem); }
.offer-head h2 + .lead { margin-top: var(--s-3); }
.offer-head .lead { max-width: 62ch; }
.offer-sec--tight { padding-top: 0; }
.offer-sec__foot { margin-top: var(--s-5); }

/* 9.5.5 The agenda, as three cards. */
.steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s-5); }
.step {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: var(--s-6);
}
.step__n {
  display: block;
  margin-bottom: var(--s-5);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.13em;
  color: var(--accent-text);
  font-variant-numeric: tabular-nums;
}
.step h3 { font-size: 1.25rem; margin-bottom: var(--s-3); }
.step p { font-size: 0.94rem; color: var(--text-muted); line-height: 1.65; }

/* 9.5.6 The quotes, as a centred band. */
.quote-band {
  padding: var(--s-7) var(--s-8);
  background: var(--bg-raised);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.quote-band__inner {
  max-width: 1000px;
  margin-inline: auto;
  display: grid;
  gap: var(--s-8);
  text-align: center;
}
.quote-big blockquote {
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(1.4rem, 2.3vw, 1.95rem);
  line-height: 1.35;
  letter-spacing: -0.01em;
  color: var(--text-strong);
  margin-bottom: var(--s-5);
  text-wrap: balance;
}
.quote-big figcaption { font-size: 0.9rem; color: var(--text-muted); line-height: 1.5; }
.quote-big figcaption b { color: var(--text); font-weight: 600; }
.quote-big figcaption b::after { content: ' '; }

/* 9.5.7 Fit, as a two-up of cards. */
.fit { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-5); }
.fit-card {
  display: grid;
  grid-template-columns: 22px 1fr;
  gap: var(--s-4);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: var(--s-6);
}
.fit-card > svg { width: 22px; height: 22px; margin-top: 0.2em; color: var(--accent-text); }
.fit-card h3 {
  font-family: var(--sans);
  font-size: 1.1rem;
  font-weight: 600;
  letter-spacing: -0.005em;
  margin-bottom: var(--s-3);
}
.fit-card p { font-size: 0.92rem; color: var(--text-muted); line-height: 1.65; }

/* 9.5.8 Terms, four columns under a rule. */
.terms { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s-5); }
.term { border-top: 2px solid var(--accent); padding-top: var(--s-4); }
.term h3 {
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 600;
  margin-bottom: var(--s-3);
}
.term p { font-size: 0.88rem; color: var(--text-muted); line-height: 1.6; }

/* 9.5.9 The read-instead offer, as a band. */
.alt {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-6);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--s-6) var(--s-7);
}
.alt h3 {
  font-family: var(--sans);
  font-size: 1.2rem;
  font-weight: 600;
  margin-bottom: var(--s-2);
}
.alt p { font-size: 0.92rem; color: var(--text-muted); line-height: 1.65; max-width: 62ch; }

/* 9.5.10 Closing CTA. */
.final {
  padding: var(--s-7) var(--s-8);
  position: relative;
  overflow: hidden;
  text-align: center;
  background: radial-gradient(800px 400px at 50% 0%, rgba(37, 99, 235, 0.15), transparent 70%);
}
.final h2 { font-size: clamp(1.9rem, 3.2vw, 3rem); margin-bottom: var(--s-4); }
.final p { color: var(--text-muted); max-width: 38rem; margin: 0 auto var(--s-6); }

/* 9.5.11 The standing offer, once the hero has gone. Raised by site.js and
   lowered again for the whole tail of the page, so it never covers the button
   it repeats or the footer under it. visibility rather than opacity alone: off
   screen it has to be out of the tab order and out of the accessibility tree,
   and it is never shown at all when the script does not run.

   Phones only, which is the whole reason it exists. .site-nav is fixed and
   carries this same button at every width above the menu breakpoint, so on a
   laptop this bar would put a second copy of one CTA on screen beside the
   first. Below 700px that button is folded behind the menu toggle, and this is
   the only standing way back to the calendar. */
.sticky-cta {
  display: none;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 90;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
  padding: var(--s-3) var(--s-5);
  background: rgba(11, 22, 40, 0.97);
  backdrop-filter: blur(10px);
  border-top: 1px solid var(--line-strong);
  visibility: hidden;
  opacity: 0;
  transform: translateY(100%);
  transition: opacity var(--dur) var(--ease),
              transform var(--dur) var(--ease),
              visibility 0s linear var(--dur);
}
.sticky-cta.is-up {
  visibility: visible;
  opacity: 1;
  transform: none;
  transition: opacity var(--dur) var(--ease),
              transform var(--dur) var(--ease),
              visibility 0s;
}
.sticky-cta p { font-size: 0.9rem; color: var(--text-muted); }
.sticky-cta strong { color: var(--text-strong); font-weight: 600; }

/* 9.6 /arabic-ai/, and the dead right-hand column.
   Added 2026-08-06 after an audit of that page alone.

   THE MEASUREMENT. Four of the five body sections there are `.s-head` over
   `.prose`, and `.prose p` is clamped to `--measure`, 68ch, which resolves to
   746px. The shell is 1200px. So three of those sections rendered a 746px
   column of text with 454px of permanently empty shell beside it, 38% of every
   row, three times down the page, and the model list under "Does it have to be
   an Arabic model?" did the same thing four more times at its own 65ch. Only
   the `.controls` card grid ever used the full width. The page read as a narrow
   ragged column pinned to one edge of a very wide dark field.

   WHAT THIS IS NOT. It is not a wider measure. 68ch is correct and a longer
   line is a readability regression, not a fix: the eye loses its place coming
   back to the start of the next line. The empty column is not solved by filling
   it with text, it is solved by giving it a job.

   So the two-paragraph prose blocks run as two columns and the four models run
   as a two by two. Each column lands at 576px, about 52 characters, which is
   inside a comfortable measure rather than outside it, and every paragraph on
   the page still starts and ends where the reader expects. Nothing was written,
   cut or invented to make this work: the same words are in the same order.

   The prose half of that is `.prose--columns` in section 6 and is no longer
   here. It was written page-scoped first, and when /data-residency/ needed the
   same thing the choice was a second copy or one component. The three prose
   blocks in page-arabic-ai.php opt in by class.

   The model grid below uses `--s-7`, which is what `.prose--columns` spends on
   its column gap, deliberately. The two grids stack in the same section, and at
   `--s-8` against `--s-7` their column edges missed each other by 8px, which is
   exactly the kind of thing that reads as sloppy without the reader being able
   to say why.

   Scoped to this page, because it was audited alone and the other templates
   were not measured. The `.s-head` above each section stays full width and
   stacked. A big heading on one side with the body floating in the opposite
   corner is the pattern the section head was written to avoid, and widening the
   body is not a reason to reintroduce it.

   Measured at 1440: 454px of dead band per section to zero, and the page from
   3,989px to 3,578px. */
.page-template-page-arabic-ai .rubric__controls { gap: var(--s-5); }

/* 1100px, not 1024px, and the number is arithmetic rather than taste. `.section`
   pads 64px a side, so the shell is `min(1200, vw - 128)`. At 1100 that leaves
   972px and two columns of 462px, about 42 characters, which is the last width
   where a column is still a column. Below it the shell is close enough to the
   measure that there is no dead band to remove, and one column is right. */
@media (min-width: 1100px) {
  /* Four models, two up. The component is a scale on /services/ where the
     relationship between the rows is the content and a single column is right.
     Here it is a list of four parallel options a reader compares, which is what
     a grid is for.

     `align-items: start` is not tidiness. Left at its default stretch, a grid
     item takes the height of the tallest cell in its row, and `.rubric__controls
     li` is itself a grid whose two auto rows then share out the extra. Fanar's
     entry is one line against ALLaM's three, so its attribution and its
     argument were pushed to opposite ends of a 113px box with a hole in the
     middle, and the accent rule ran on for 40px past the last word into
     nothing. The rule marks an entry, not a row. Sized to its content, a short
     entry simply reads as a short entry. */
  .page-template-page-arabic-ai .rubric__controls {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s-6) var(--s-7);
    align-items: start;
  }

  /* A section lead carries `.lead`'s own 58ch, which resolves to about 718px
     inside a 920px head. It reads as a narrower column than the heading above
     it and than the grid below it, which is the same dead-column complaint in
     miniature: on /arabic-ai/ it broke into three lines with "than a ranking."
     orphaned on the last one.

     The head box is already a measure. 920px of this font is about 74
     characters, inside a readable line rather than outside it.

     Listed per page rather than written once on `.s-head .lead`, because that
     58ch is a deliberate decision recorded in section 6 and these are the only
     two templates that have been measured. Generalise it when the rest have
     been, not before. */
  .page-template-page-arabic-ai .s-head .lead,
  .page-template-page-data-residency .s-head .lead { max-width: none; }
}

/* 9.7 /data-residency/, audited 2026-08-06 for the same complaint.

   Worse here than on /arabic-ai/, because this page leads with label and value
   pairs and they collapse hardest. Measured on the 1200px shell: "Google Cloud"
   was 98px of text against 1,102px of empty shell, "SOC 2" was 45px against
   1,155px, and "Certified." was 59px against 1,141px. Four prose blocks wasted
   the usual 454px each on top of that.

   The fix is structural and lives in page-data-residency.php: each list is now
   paired with the paragraph that qualifies it, in `.split`. That pairing is not
   an excuse to fill a column. The certifications sit beside the sentence that
   begins "The rest is written into the contract rather than into a badge", and
   the regions sit beside the one that says a database in region behind a model
   API outside it has not solved the problem. Each paragraph is about the list
   it now stands next to.

   Only the gutter belongs here. `.split` spends `--s-8` and `.prose--columns`
   spends `--s-7`, so the second column of "The commitment." began at 744px and
   the second column of the two sections under it at 752px. Eight pixels, one
   page, and the sort of thing a reader registers as sloppiness without being
   able to name it.

   Measured at 1440: content 2,640px to 2,228px, and the worst row on the page
   from 1,155px of unused width to 460px, which is a column edge rather than a
   void. */
@media (min-width: 901px) {
  .page-template-page-data-residency .split { column-gap: var(--s-7); }
}

/* --------------------------------------------------------------------------
   10. Motion
   MOTION_INTENSITY 4/10. Entry reveals driven by IntersectionObserver, hover
   feedback on interactive elements, nothing that hijacks the scroll. An
   executive scanning a page should never wait for an animation to finish.
   -------------------------------------------------------------------------- */
[data-reveal] {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 600ms var(--ease), transform 600ms var(--ease);
}
[data-reveal="fade"]  { transform: none; }
[data-reveal="right"] { transform: translateX(24px); }
[data-reveal].is-in   { opacity: 1; transform: none; }

/* If the observer never runs (JS disabled or errored), content must still be
   visible. The class is added to <html> by site.js the moment it loads. */
html:not(.js) [data-reveal] { opacity: 1; transform: none; transition: none; }

/* Reduced motion asks for less movement, not for an interface that stops
   answering. The blanket transition-duration override this replaced took hover
   colour, focus and button response down with the travel, which leaves the
   people who set the preference with the least responsive version of the site.
   Colour and opacity stay and are still timed; everything that moves does not. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
    transition-property: opacity, color, background-color, border-color, fill, stroke !important;
    transition-duration: var(--dur-fast) !important;
  }
  [data-reveal] { opacity: 1 !important; transform: none !important; transition: none !important; }
  .btn:active,
  .day:active,
  .slot:active .slot__box { transform: none !important; }
}

/* --------------------------------------------------------------------------
   11. Responsive
   Every multi-column layout above collapses explicitly below.
   -------------------------------------------------------------------------- */
@media (max-width: 1024px) {
  .site-nav { padding: 0 var(--s-5); gap: var(--s-4); }
  .site-nav__menu { gap: var(--s-4); }
  .site-nav__menu a { font-size: 0.76rem; letter-spacing: 0.03em; }
  .hero__grid { gap: var(--s-6); }
  /* grid-auto-flow reset too, or the desktop column flow overrides these. */
  .site-footer__grid { grid-template-columns: 1fr 1fr; grid-auto-flow: row; }

  /* The picker needs roughly 400px before the month grid starts squeezing its
     44px squares. Below this the hero is one column, copy then card, which is
     the order somebody reads them in anyway. */
  .offer-hero__inner { grid-template-columns: 1fr; gap: var(--s-7); }
  .offer-hero h1 { max-width: 20ch; }
  .offer-hero__do { max-width: 560px; }

  /* ---------------------------------------------------------------------
     THE PICKER COMES BEFORE THE ARGUMENT ON ONE COLUMN. Added 2026-08-19.

     This page was built so that "the offer and the means of accepting it are
     in one view" (booking.md section 7c). Two columns deliver that. One column
     did not: the card stacked under the whole pitch and the first slot sat
     915px down, 1.1 screens below the fold on a 390px phone. Measured on
     production, 85% of the traffic on this page is a phone, and the visits
     that bounced did so at four to six seconds having scrolled one percent.
     They never saw a calendar.

     So on one column the order is: badge, headline, promise, PICKER, then the
     three deliverables and the rest of the argument. Somebody who is already
     sold can book without reading; somebody who is not scrolls into the case
     for it, which is the same order the two-column layout puts them in when
     read left to right.

     display:contents on the copy column, so its children become items of the
     hero grid and can be ordered against the card. It carries no role and no
     styling of its own, so nothing is lost from the accessibility tree.
     --------------------------------------------------------------------- */
  .offer-hero__say { display: contents; }
  .offer-hero__badges  { order: 1; }
  .offer-hero h1       { order: 2; }
  .offer-hero__sub     { order: 3; }
  .offer-hero__do      { order: 4; }
  /* EVERY child needs a number here, not just the ones being moved. Under
     display:contents these are all items of the same grid, and an item with no
     `order` gets 0, which sorts ahead of the 1 on the badge rather than staying
     where the markup put it. The clients line was added on 2026-08-19 without
     one and went to the top of the page: an Arcosa and a Buildertrend were the
     first thing on a phone, above the credential and above the headline, which
     is a name-drop before anybody has been told what is on offer.

     It sits under the picker instead of over it. The line is worth 36px and the
     36px would come off the fold the reorder above exists to protect, and this
     is the better place for it anyway: somebody who has just read the open
     counts and not booked is exactly who a client name is for. */
  .offer-hero__clients { order: 5; margin-block-start: var(--s-7); }
  .offer-hero .deliver { order: 6; }
  .offer-hero__actions { order: 7; }
  .offer-hero__micro   { order: 8; }

  /* The grid gap is the rhythm between two columns of a two-column layout. Here
     the same gap now falls between a headline and the sentence under it, which
     is far too much air. Collapsed to nothing, and the spacing between blocks
     is set on the blocks themselves, which is where it can differ per pair.

     Measured at 390px: this brings the day strip and its open counts onto the
     fold. Before the reorder the first slot was at 915px. */
  .offer-hero__inner   { gap: 0; }
  .offer-hero__do      { margin-block-start: var(--s-5); }
  /* --s-7 until the clients line took the top of this block. The gap that
     separates the argument from the picker is now on that line, and stacking a
     second one here would put 80px between two things that belong together. */
  .offer-hero .deliver { margin-block-start: var(--s-5); }

  /* Three lines of display serif is a third of a 390px screen. One step down
     the clamp buys back a line without changing the voice, and the headline
     still reads as the largest thing on the page. */
  .offer-hero h1       { font-size: clamp(2.1rem, 8.4vw, 2.7rem); }
  .offer-hero__sub     { margin-bottom: 0; }

  /* Redundant now: the picker is directly above this row, so the button that
     scrolls down to it would scroll up. The secondary path stays, demoted to
     what it is, an alternative for somebody not ready to book. */
  .offer-hero__actions .btn--primary { display: none; }
  .offer-hero__actions .btn--ghost {
    border: 0;
    padding-inline: 0;
    text-decoration: underline;
    text-underline-offset: 3px;
    /* .btn centres its label, which is right for a button and wrong for what
       this has just become. Stretched to the full column by the flex rule in
       the 700px block, a centred label floated in the middle of a page where
       every other line starts at the same left edge. */
    text-align: start;
    justify-content: flex-start;
  }
}

@media (max-width: 900px) {
  /* --s-8 until 2026-08-06, which after the desktop retune left MOBILE more
     airy than desktop: 64px a side here against 48px at 1440, so a phone was
     scrolling past bigger dead bands than a laptop. Measured at 150px on the
     homepage against 135px on desktop. Mobile is now the tightest of the
     three, which is the order it should always have been in. */
  .section, .section--tight { padding: var(--s-6) var(--s-5); }
  .cta { padding: var(--s-7) var(--s-5); }
  .page-hero { padding: calc(var(--nav-h) + var(--s-6)) var(--s-5) var(--s-6); }

  .hero { padding: calc(var(--nav-h) + var(--s-6)) var(--s-5) var(--s-8); min-height: 0; }
  .hero__grid { grid-template-columns: 1fr; gap: var(--s-7); }

  .proof { grid-template-columns: 1fr; }
  .proof__item { border-inline-end: none; border-bottom: 1px solid var(--line); }
  .proof__item:last-child { border-bottom: none; }

  .timeline { grid-template-columns: 1fr 1fr; }
  .timeline__step:nth-child(2n) { border-inline-end: none; }
  .timeline__step:nth-child(n+3) { border-top: 1px solid var(--line); }

  .facts__row { grid-template-columns: 1fr; gap: var(--s-2); }

  .svc-pair,
  .controls,
  .rubric,
  .quotes,
  .reversal,
  .split,
  .split--wide-left { grid-template-columns: 1fr; gap: var(--s-6); }

  .offer-hero { padding: calc(var(--nav-h) + var(--s-6)) var(--s-5) var(--s-8); }
  .quote-band { padding: var(--s-6) var(--s-5); }
  .offer-sec { padding: var(--s-6) var(--s-5); }
  .final { padding: var(--s-6) var(--s-5); }
  .trust { padding: var(--s-5) var(--s-5); }
  .trust__inner { justify-content: flex-start; gap: var(--s-4) var(--s-5); }
  /* Three marks to a row on a phone. Seventeen at two to a row is nine rows and
     the wall becomes the page; at three it is six.
     The arithmetic is tight and it is why the width is 96 and not 104: a 390px
     viewport less the section's 24px of padding each side leaves 342, and three
     104px marks with a 24px gap need 360. Measured, after the first attempt
     shipped two to a row. */
  .logos,
  .trust__logos { gap: var(--s-4); }
  .logos img,
  .trust__logos img { width: 96px; }
  .quote-band__inner { gap: var(--s-7); }
  .steps,
  .fit { grid-template-columns: 1fr; }
  .terms { grid-template-columns: 1fr 1fr; }
  .alt { padding: var(--s-6); }
  .offer-head { margin-bottom: var(--s-6); }

  .stage__head { flex-direction: column; align-items: flex-start; gap: var(--s-2); }
  .stage__note { margin-inline-start: 0; text-align: start; }

  .posts { grid-template-columns: 1fr 1fr; }
  .post-lead { grid-template-columns: 1fr; gap: var(--s-5); padding: var(--s-6); }

  .founder { grid-template-columns: 1fr; gap: var(--s-6); }
  .founder__photo { max-width: 240px; }
  .founder__record > div { grid-template-columns: 1fr; gap: var(--s-1); }

  .related { padding: var(--s-7) var(--s-5); }
  .related__grid { grid-template-columns: 1fr; }

  /* Dates above times. The calendar keeps its own width rather than stretching
     seven 44px squares across a tablet. */
  .booking__panel { grid-template-columns: 1fr; }
  .booking__cal {
    border-inline-end: none;
    border-bottom: 1px solid var(--line);
  }
  .booking__facts { grid-template-columns: 1fr; gap: var(--s-4); }

  .book-skeleton { grid-template-columns: 1fr; }
  .book-skeleton__cal { border-inline-end: none; border-bottom: 1px solid var(--line); }
}

@media (max-width: 700px) {
  :root { --nav-h: 64px; }

  /* MOBILE TOUCH AND TYPE, added 2026-08-06 after auditing every surface at
     390px with a touch context.

     The calendar arrows are 30px and 36px on larger screens, which is a
     deliberate density decision and clears the 24x24 that WCAG 2.5.8 asks for.
     A thumb is not a mouse: on a phone they get the full 44px, and the row they
     share with the month label has the space for it here because the strip is
     the only thing on it. */
  .cal__nav,
  .days__nav .cal__nav,
  .booking--inline .days__nav .cal__nav { width: 44px; height: 44px; font-size: 1.05rem; }

  /* 0.62rem is 9.9px, which was the smallest text anywhere on the site and it
     sits inside a tap target a reader is trying to choose between. */
  .day__open { font-size: 0.78rem; }

  /* 0.68rem is 10.9px on a card the reader scans rather than reads. */
  .post-card__tag,
  .tag,
  .article-meta .tag { font-size: 0.78rem; }

  /* The footer column headings and the section eyebrows, at 0.7rem, are the
     other two labels under 12px. They are uppercase and tracked, so they hold
     up better than body text would, but not at arm's length on a phone. */
  .site-footer h2,
  .eyebrow { font-size: 0.76rem; }

  /* The rest of the uppercase micro-labels, all of which sat at 0.7rem or
     0.72rem, which is 11.2px and 11.5px. They are tracked and uppercase so they
     hold up better than body copy would at that size, but this is a phone held
     at arm's length and there is no reason for them to be under 12px. Body copy
     on this site is already 16px and is not touched.

     This list and the textarea below it were one rule until 2026-08-15, and the
     declaration on it was the textarea's `min-height: 6rem`. Every label here
     was therefore shipped in a 96px box on a phone and none of them ever got
     the size this comment describes. It read as padding, because that is what
     it was: an empty three quarters of an inch under a two word label. The
     giveaway was the time picker, where a 52px chip measured 136px on a phone
     and did so whether the day offered two windows or five.

     Keep them apart. A selector list that ends in a declaration meant for its
     last entry is a rule that silently applies to everything above it. */
  .timeline__when,
  .svc__spec dt,
  .control__fix b,
  .rubric h3,
  .divider-label,
  .channels dt,
  .founder__record dt,
  .facts__row dt,
  /* Same category, added with the intake form: both are uppercase tracked
     labels at 0.72rem, which is 11.5px. The rule above is the whole reason
     they are listed here rather than shipped a size smaller than everything
     else on a phone. */
  .intake__meeting dt,
  .intake__answers dt,
  .booking__head-meta,
  .booking__step { font-size: 0.78rem; }

  /* .slot__meta is not in that list. It is 0.8rem, which is 12.8px and already
     above the floor, and the chip variant of it is set a few lines down. */

  /* The inline picker's note field is 3.75rem, which fits two lines of its own
     placeholder out of three on a 390px screen and clips the third mid-word.
     The desktop width fits the placeholder in two lines; a phone does not. */
  .booking--inline .booking__fields textarea { min-height: 6rem; }

  .trust__label,
  .logos__label,
  .cal__dows span,
  .offer-card h3,
  .slots--chips .slot__meta { font-size: 0.78rem; }

  .nav-toggle { display: inline-flex; }
  .site-nav__menu {
    display: none;
    position: absolute;
    inset: var(--nav-h) 0 auto 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    margin-inline-start: 0;
    padding: var(--s-3) var(--s-5) var(--s-5);
    background: var(--bg-raised);
    border-bottom: 1px solid var(--line);
    max-height: calc(100dvh - var(--nav-h));
    overflow-y: auto;
  }
  .site-nav__menu.is-open { display: flex; }
  .site-nav__menu li { border-bottom: 1px solid var(--line); }
  .site-nav__menu li:last-child { border-bottom: none; padding-top: var(--s-4); }
  .site-nav__menu a { min-height: 52px; width: 100%; font-size: 0.9rem; }
  .site-nav__menu .nav-cta > a,
  .site-nav__menu a.nav-cta { justify-content: center; }

  .site-footer { padding: var(--s-7) var(--s-5) var(--s-5); }
  .site-footer__grid { grid-template-columns: 1fr; grid-auto-flow: row; gap: var(--s-6); }
  .site-footer__bottom { flex-direction: column; align-items: flex-start; }

  .timeline { grid-template-columns: 1fr; }
  .timeline__step { border-inline-end: none; border-bottom: 1px solid var(--line); }
  .timeline__step:last-child { border-bottom: none; }
  .timeline__step:nth-child(n+3) { border-top: none; }

  .posts { grid-template-columns: 1fr; }
  .tenets li { grid-template-columns: 2rem 1fr; gap: var(--s-4); }
  .hero__actions .btn, .cta__actions .btn { flex: 1 1 auto; }

  .booking__fields { grid-template-columns: 1fr; }
  .booking__cal, .booking__pick { padding: var(--s-5); }
  .booking__done { padding: var(--s-6) var(--s-5); }
  .cal__month, .slots legend { font-size: var(--step-1); }

  .booking--inline { padding: var(--s-5); }
  .terms { grid-template-columns: 1fr; gap: var(--s-6); }
  .offer-hero__actions .btn { flex: 1 1 auto; }

  /* The breakpoint where the nav folds its Book button behind the toggle, so
     this is where the bar starts earning its place. The sentence goes with it:
     at this width the button's own label is the only part carrying information
     the visitor does not already have. */
  .sticky-cta {
    display: flex;
    padding: var(--s-3) var(--s-5) calc(var(--s-3) + env(safe-area-inset-bottom));
  }
  .sticky-cta p { display: none; }
  .sticky-cta .btn { flex: 1 1 auto; }

  /* A sheet rather than a card. A calendar, four fields and a button do not fit
     on a phone with a margin around them, and an inset card here only buys a
     glimpse of the page behind at the cost of the room the form needs. */
  .book-modal {
    width: 100%;
    max-width: 100%;
    height: 100dvh;
    max-height: 100dvh;
    margin: 0;
    border: 0;
    border-radius: 0;
  }
  .book-modal__head { padding: var(--s-3) var(--s-3) 0; }
  .book-modal__body { padding: var(--s-2) var(--s-5) var(--s-5); }
  .book-skeleton__cal, .book-skeleton__pick { padding: var(--s-5); }
}
