/* =============================================================
   Raleigh Resume stylesheet. Editorial, typography-forward.

   Served by the one site folder, raleigh-resume (the second site
   was retired, ruling R4).

   MASTER copy: apps/masthead/design/styles.css. Edit there, then
   run apps/masthead/sync.py; the copy in the site folder is
   overwritten on sync. Accent colors live in the site's theme.css,
   generated from brand-tokens.json.
   ============================================================= */

/* -----------------------------------------------------------
   1. Fonts — self-hosted variable fonts (fonts/*.woff2)
   ----------------------------------------------------------- */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('fonts/fraunces-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('fonts/fraunces-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Fraunces';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/fraunces-italic-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Fraunces';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/fraunces-italic-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* -----------------------------------------------------------
   2. Design tokens (CSS custom properties)

   Proportion system (adopted 2026-08-12; the study, migration
   tables, and phase plan live in GOLDEN_RATIO_REPORT.md, one
   directory up). Values are precomputed and quantized, never
   calc()'d at runtime.

   TYPE   s(n) = 17px x phi^(n/2), rounded to whole px:
          12  13  15  [17]  22  28  35  45  56  72
          (12 and 15 are quarter-rungs, phi^-3/4 and phi^-1/4,
          sanctioned for utility text below body size only.)
   SPACE  8px x Fibonacci(1,2,3,5,8,13) = 8 16 24 40 64 104;
          fluid tokens travel between adjacent rungs.
   TRACK  caps runs 0.056em (phi^-6) or 0.09em (phi^-5), on
          uppercase only; display tightening -0.013em (phi^-9)
          or -0.021em (phi^-8).
   LEAD   body sits on phi (1.618); heading leading is optical,
          with h3 on sqrt(phi) (1.272).

   Off-ladder BY DESIGN, never to be snapped: WCAG geometry
   (48px buttons, 44px targets, input borders, contrast),
   breakpoints, container widths and prose measure, form
   ergonomics, micro-gaps of 6px and under.
   Signed off-ladder values, kept by eye (report section 6.4):
   the hero padding clamps, header height 72 with its
   scroll-margin 96, radii 4/8, button padding 14/28.

   Enforced: check.py's proportion gate fails any --fs-* or
   --space-* value off these rungs unless it is signed in
   PROPORTION_SIGNED there. Since the 2026-08-22 spacing review
   it also sweeps every literal font-size, letter-spacing and
   margin/padding/gap declaration in this file: literals sit on
   a rung, at or under the 6px micro threshold, or in check.py's
   LITERAL_SIGNED with a reason and an exact use count. Writing
   a new off-rung length here means signing it there, in the
   same commit, or taking the nearest rung.
   ----------------------------------------------------------- */
:root {
  /* Fonts */
  --font-display: 'Fraunces', Georgia, 'Times New Roman', serif;
  --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

  /* Color — neutrals (shared) */
  --bg-primary: #FAFAF8;
  --bg-secondary: #FFFFFF;
  --text-primary: #1A1A1A;
  --text-muted: #6B6B6B;
  /* --text-subtle (#8A8A8A) removed 2026-08-11: it measured 3.45:1 on white and
     3.30:1 on --bg-primary, failing 1.4.3 for the small text it carried (the footer
     legal line and every "(optional)" form marker). There is almost no room for a
     third grey below --text-muted at AA, so its three uses now point at that. */
  --border-subtle: #E8E6E2;
  /* Form controls set appearance:none, so their border is the only thing that
     identifies the control and 1.4.11 requires 3:1 against the field. Decorative
     dividers keep --border-subtle at 1.25:1, which is fine for them.
     Warmed from the dead-neutral #8A8A8A on 2026-08-15: the input frame was the
     one cold grey on a warm-paper page. #8C8578 sits in the same family as
     --border-medium/--border-subtle and measures 3.66:1 on the white field,
     3.50:1 on --bg-primary; both clear the 3:1 floor with more room than the
     old value's 3.45:1. */
  --border-input: #8C8578;
  --border-medium: #D4D2CE;

  /* The cool ink (lookalike audit rec 05, adopted 2026-08-21): the
     document world's ink, for surfaces where the site renders a
     document of its own (today: the tool memo sheets, section 26).
     A shared neutral by design, never a theme.css token: Signal's
     THEME_TEMPLATE pins exactly five accent tokens per site.
     14.4:1 on white, 13.9:1 on --bg-primary. */
  --ink-cool: #1F2A3C;

  /* Color — accent: the five --accent-* tokens are defined per
     site in theme.css (generated from brand-tokens.json). */

  /* Spacing */
  --space-xs: 8px;
  --space-sm: 16px;
  --space-md: 24px;
  --space-lg: 40px;
  --space-xl: clamp(64px, 8vw, 104px);
  --space-2xl: clamp(104px, 12vw, 168px);

  /* Layout */
  --width-max: 1200px;
  /* 680 -> 768 on 2026-08-20 (operator: running text read narrow beside
     the bordered cards, which fill the 912px body track). One quarter
     phi-step: 680 x phi^(1/4) = 767, kept on the 8px grid at 768; the
     track:prose relation lands near sqrt(phi). Prose measure remains
     off-ladder by doctrine (content-governed); the six long-form
     articles keep the 680 reading measure via the exception in
     section 25. */
  --width-prose: 768px;
  --width-narrow: 520px;
  --container-padding: 24px;

  /* Type sizes — fluid ones travel between ladder rungs */
  --fs-hero: clamp(2.8125rem, 5.5vw, 4.5rem);         /* 45 → 72px */
  /* Floor dropped 35 → 28 (both rungs) on 2026-08-15: at 35 the section
     h2 tied the page-header h1's own 35px floor, so phones read nine
     equally-loud headings and no top. 28 restores one clear step. */
  --fs-section: clamp(1.75rem, 3.5vw, 2.8125rem);     /* 28 → 45px */
  --fs-subsection: 1.375rem;                          /* 22px */
  --fs-body: 1.0625rem;                               /* 17px */
  --fs-body-large: 1.375rem;                          /* 22px subhead */
  --fs-small: 0.9375rem;                              /* 15px */

  /* Misc */
  --radius-sm: 4px;
  --radius-md: 8px;
  --transition: 0.15s ease;
}

@media (min-width: 1024px) {
  :root {
    --container-padding: 48px;
  }
}

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

/* Anchor targets clear the sticky header on scroll; the :focus-visible
   twin keeps a Tab-focused element (most have no id) from coming to rest
   underneath the header (2.4.11). Zero visual effect otherwise. */
[id] {
  scroll-margin-top: 96px;
}

/* Scoped to what scrolls with the page: inside the sticky header and the
   fixed drawer the margin scrolled the page on every Tab (the third sweep,
   2026-09-13, X-NAV-02). */
main :focus-visible,
.site-footer :focus-visible {
  scroll-margin-top: 96px;
}

body {
  margin: 0;
  /* Flex column at viewport height so a short page (thanks, 404) holds its
     first screenful as the message alone, with the footer arriving on
     scroll instead of dominating the opening view. Added 2026-08-15. */
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  background: var(--bg-primary);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.618;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* The body is a flex column (see above); main takes the slack so the
   footer sits at the viewport bottom on pages shorter than the screen.
   The min-height matters on phones, where the footer is taller than the
   leftover viewport and flex-grow alone gets no free space to hand out:
   it guarantees the first screenful belongs to the page's own message
   (404 was showing three footer tagline lines above the fold). 72px
   header + 1px border. */
main {
  flex: 1 0 auto;
  min-height: calc(100vh - 73px);
  min-height: calc(100svh - 73px);
}

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

a {
  color: var(--accent-primary);
  text-decoration: none;
  transition: color var(--transition);
}

a:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}

/* Links that sit INSIDE running text are underlined at rest: the accent
   rust measures 2.83:1 against the body ink, under the 3:1 that WCAG 1.4.1
   (G183) requires before colour alone may mark a link in prose. Standalone
   links (card links, nav, btn-rows, footer lists) keep their positional
   distinction and stay underline-on-hover. Added 2026-08-15. */
.prose a,
.hero__subhead a,
.section-header__lede a,
.service-detail p a,
.form-help a {
  /* Accent economy (design critique, 2026-08-20): in running text the pen
     belongs to the margin, so prose links read in ink with the underline
     as their marker, and hover takes the accent. Buttons, prices, margin
     notes, and standalone action links keep the accent. */
  color: var(--text-primary);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}

.prose a:hover,
.hero__subhead a:hover,
.section-header__lede a:hover,
.service-detail p a:hover,
.form-help a:hover {
  color: var(--accent-primary);
}

a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
  border-radius: 2px;
}

button {
  font: inherit;
  cursor: pointer;
}

ul,
ol {
  margin: 0 0 var(--space-md);
  padding-left: 1.25em;
}

li + li {
  margin-top: 6px;
}

p {
  margin: 0 0 var(--space-md);
  /* Avoids single-word orphan lines. Ignored where unsupported. Added 2026-08-12. */
  text-wrap: pretty;
}

p:last-child {
  margin-bottom: 0;
}

hr {
  border: 0;
  border-top: 1px solid var(--border-subtle);
  margin: var(--space-lg) 0;
}

::selection {
  background: var(--accent-tint-medium);
  color: var(--text-primary);
}

/* Skip link for keyboard users */
.skip-link {
  position: absolute;
  top: -100px;
  left: 16px;
  background: var(--accent-primary);
  color: var(--accent-text);
  padding: 8px 16px;
  border-radius: var(--radius-sm);
  z-index: 1000;
  text-decoration: none;
}

.skip-link:focus {
  top: 16px;
}

/* -----------------------------------------------------------
   4. Typography
   ----------------------------------------------------------- */
h1,
h2,
h3,
h4 {
  font-family: var(--font-display);
  font-weight: 500;
  color: var(--text-primary);
  margin: 0 0 var(--space-md);
  letter-spacing: -0.013em;
  /* Evens out line lengths on multi-line headings. Ignored where
     unsupported. Added 2026-08-12. */
  text-wrap: balance;
}

h1 {
  font-size: var(--fs-hero);
  line-height: 1.08;
  letter-spacing: -0.021em;
}

h2 {
  font-size: var(--fs-section);
  line-height: 1.15;
}

h3 {
  font-family: var(--font-body);
  font-size: var(--fs-subsection);
  font-weight: 600;
  letter-spacing: 0;
  line-height: 1.272;
  margin-bottom: var(--space-sm);
}

h4 {
  font-family: var(--font-body);
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: 0;
  line-height: 1.4;
  margin-bottom: var(--space-xs);
}

.eyebrow {
  font-family: var(--font-body);
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--space-md);
}

.lede {
  font-size: var(--fs-body-large);
  line-height: 1.55;
  color: var(--text-muted);
  font-weight: 400;
}

.pullquote,
blockquote {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  font-size: 1.375rem;
  line-height: 1.4;
  color: var(--text-primary);
  margin: 0 0 var(--space-md);
  padding: 0;
  border: 0;
}

.muted { color: var(--text-muted); }
.subtle { color: var(--text-muted); }

/* Visually hidden, still announced. Needed wherever a visual convention carries
   meaning that the markup does not, for example a struck-through former price. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.small { font-size: var(--fs-small); }

/* -----------------------------------------------------------
   5. Layout utilities
   ----------------------------------------------------------- */
.container {
  width: 100%;
  max-width: var(--width-max);
  margin-inline: auto;
  padding-inline: var(--container-padding);
}

.prose {
  max-width: var(--width-prose);
}

.narrow {
  max-width: var(--width-narrow);
}

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

.section--lg {
  padding-block: var(--space-2xl);
}

.section--tinted {
  background: var(--bg-secondary);
}

.section--accent-soft {
  background: var(--accent-tint-soft);
}

.section--accent-medium {
  background: var(--accent-tint-medium);
}

.section--accent-medium,
.section--accent-medium p,
.section--accent-medium li {
  color: var(--text-primary);
}

.stack-md > * + * { margin-top: var(--space-md); }
.stack-lg > * + * { margin-top: var(--space-lg); }
.stack-xl > * + * { margin-top: var(--space-xl); }

.u-mt-md { margin-top: var(--space-md); }
.u-mt-lg { margin-top: var(--space-lg); }

/* -----------------------------------------------------------
   6. Buttons
   ----------------------------------------------------------- */
.btn-primary,
.btn-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-family: var(--font-body);
  font-size: 1.0625rem;
  font-weight: 500;
  line-height: 1;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--transition), color var(--transition), border-color var(--transition);
  text-decoration: none;
  min-height: 48px;
}

.btn-primary {
  background: var(--accent-primary);
  color: var(--accent-text);
  padding: 14px 28px;
}

.btn-primary:hover {
  background: var(--accent-hover);
  color: var(--accent-text);
  text-decoration: none;
}

.btn-secondary {
  background: transparent;
  color: var(--accent-primary);
  padding: 14px 0;
  min-height: auto;
  border-radius: 0;
  /* Unlike the pill buttons this one wraps as text on mobile, and the
     buttons' shared line-height of 1 set its wrapped lines 17px apart
     against 27px body leading. 2026-08-15. */
  line-height: 1.4;
}

.btn-secondary:hover {
  text-decoration: underline;
  text-underline-offset: 4px;
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  align-items: center;
  margin-top: var(--space-lg);
}

/* Fine print directly under a CTA row sat ~10px off the button edge,
   tighter than anything else in the spacing rhythm. 2026-08-15. */
.btn-row + p {
  margin-top: var(--space-sm);
}

/* On accent-soft surfaces */
.section--accent-soft .btn-secondary {
  color: var(--accent-primary);
}

/* -----------------------------------------------------------
   7. Cards
   ----------------------------------------------------------- */
/* Flex column so the closing link or CTA row can pin to the bottom edge
   (grid tracks already stretch cards to equal height; before this the
   shorter cards ended in dead space below a floating link). Safe because
   card children carry bottom-only margins, so no collapse is lost — the
   two pinned elements below restate their old collapsed gap as padding.
   Added 2026-08-15. */
.card {
  background: var(--bg-secondary);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: var(--space-lg);
  transition: border-color var(--transition);
  display: flex;
  flex-direction: column;
}

a.card:hover,
.card--interactive:hover {
  border-color: var(--border-medium);
  text-decoration: none;
}

.card .card-eyebrow {
  display: block;
  font-size: var(--fs-small);
  color: var(--text-muted);
  margin-bottom: var(--space-xs);
}

.card .card-title {
  font-family: var(--font-display);
  font-size: 1.75rem;
  font-weight: 500;
  letter-spacing: -0.013em;
  line-height: 1.2;
  margin-bottom: var(--space-xs);
  color: var(--text-primary);
}

/* One price per line. Run inline, two struck former prices interleaved with two
   current ones read as four numbers rather than two discounted packages, both
   visually and to a screen reader. Added 2026-08-11. */
.card .price-pair {
  display: block;
}

.card .price-was {
  color: var(--text-muted);
  margin-right: 0.35em;
}

.card .card-price {
  font-family: var(--font-display);
  font-size: 1.375rem;
  font-weight: 500;
  letter-spacing: -0.013em;
  color: var(--accent-primary);
  margin-bottom: var(--space-md);
}

.card .card-link {
  display: inline-block;
  /* Pinned to the card's bottom edge; padding restates the --space-md gap
     the link kept above itself before the card went flex. flex-start keeps
     the hit area on the text instead of the full card width. */
  margin-top: auto;
  padding-top: var(--space-md);
  align-self: flex-start;
  font-weight: 500;
  color: var(--accent-primary);
}

/* A CTA row inside a card (the services pricing pair) pins the same way;
   --space-sm padding on top of the list's 24px bottom margin restates the
   40px its --space-lg top margin used to collapse to. */
.card .btn-row {
  margin-top: auto;
  padding-top: var(--space-sm);
}

/* -----------------------------------------------------------
   8. Grid patterns
   ----------------------------------------------------------- */
.grid {
  display: grid;
  gap: var(--space-lg);
}

.grid--2 {
  grid-template-columns: 1fr;
}

.grid--3 {
  grid-template-columns: 1fr;
}

@media (min-width: 768px) {
  .grid--2 { grid-template-columns: repeat(2, 1fr); }
  .grid--3 { grid-template-columns: repeat(2, 1fr); }
}

/* Three tracks plus two var(--space-lg) gaps need roughly 855px of container
   before the tracks clear each card's min-content floor. Going three-up at 768
   overflowed the viewport by up to 63px and clipped the third card, so hold at
   two columns until there is genuinely room. (The step moved 880 → 1024 on
   2026-08-15: min-content clearing is a floor, not a design endorsement — at
   880 the tracks ran 251px and a 28px Fraunces card title set one word per
   line. 1024 gives ~298px tracks, the narrowest that holds the titles.) */
@media (min-width: 1024px) {
  .grid--3 { grid-template-columns: repeat(3, 1fr); }
}

/* The articles catalog carries the longest card titles on the site; even
   298px tracks shred them, so its lanes hold two-up until 1200 (~357px
   tracks). The mid-band orphan row this accepts reads better than a display
   serif breaking one word per line. Added 2026-08-15. */
@media (min-width: 1024px) {
  .grid--3.grid--roomy { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 1200px) {
  .grid--3.grid--roomy { grid-template-columns: repeat(3, 1fr); }
}

/* Tool lanes (tools.html): the hub's five cards ran as one 2-up grid and
   always ended on an orphan card beside a card-sized hole; two named lanes
   (same vocabulary as the articles hub) tile 3+2 instead. The lane title
   speaks in the caps meta voice. Added 2026-08-15. */
.tool-lane + .tool-lane {
  /* Fibonacci rung; the fluid --space-xl would reach 104px here. */
  margin-top: 64px;
}

.lane-title {
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0 0 var(--space-md);
}

/* -----------------------------------------------------------
   9. Site header / navigation
   ----------------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(250, 250, 248, 0.92);
  backdrop-filter: saturate(140%) blur(10px);
  -webkit-backdrop-filter: saturate(140%) blur(10px);
  border-bottom: 1px solid var(--border-subtle);
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 72px;
  padding-block: var(--space-sm);
}

/* Brand wordmark — Fraunces, tightened tracking */
.brand-wordmark {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.375rem;
  letter-spacing: -0.021em;
  color: var(--text-primary);
  text-decoration: none;
  line-height: 1;
}

.brand-wordmark:hover {
  text-decoration: none;
  color: var(--accent-primary);
}

.site-nav {
  display: none;
}

.site-nav__list {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  list-style: none;
  padding: 0;
  margin: 0;
}

.site-nav__list li + li { margin-top: 0; }

.site-nav__link {
  font-family: var(--font-body);
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--text-primary);
  text-decoration: none;
  padding: 8px 0;
  border-bottom: 1px solid transparent;
  white-space: nowrap;
  transition: color var(--transition), border-color var(--transition);
}

.site-nav__link:hover {
  color: var(--accent-primary);
  text-decoration: none;
  border-bottom-color: var(--accent-primary);
}

.site-nav__link[aria-current="page"] {
  color: var(--accent-primary);
  border-bottom-color: var(--accent-primary);
}

/* The last nav item is an action, not one more destination, so it should not
   read as one of its siblings. Class-keyed since 2026-08-14: the action now
   points at the packages anchor while contact.html holds a plain destination
   slot, so keying on href would light the wrong link. (Href-keyed 2026-08-11;
   if signal-stance still carries the contact-href markup when next checked
   out, its action link needs the class too.) The action never carries
   aria-current, so no fill/accent-text collision rule is needed. */
.site-nav__link--cta {
  background: var(--accent-primary);
  color: var(--accent-text);
  padding: 8px 16px;
  border-radius: var(--radius-sm);
  border-bottom-color: transparent;
}

.site-nav__link--cta:hover {
  background: var(--accent-hover);
  color: var(--accent-text);
  border-bottom-color: transparent;
}

/* Tools dropdown (desktop nav): the WAI disclosure-navigation pattern. The
   trigger is a button, not a link: the hub stays reachable as the panel's
   first item ("All tools"), and the button only shows or hides the list.
   Behaviour lives in scripts.js via [data-nav-dropdown]; the panel rides the
   hidden attribute, so without JS it stays closed and the hub remains
   reachable from the drawer and footer. Click-to-open only, no hover-open:
   one predictable behaviour for mouse, touch and keyboard alike. No shadow
   on the panel: the site draws no shadows anywhere, so it separates by
   border and solid fill. Added 2026-08-14. */
.site-nav__item--dropdown {
  position: relative;
}

.site-nav__dropdown-toggle {
  background: none;
  border: 0;
  border-bottom: 1px solid transparent;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* The tool-family pages mark the trigger the way aria-current marks a link;
   the true aria-current sits on the matching link inside the panel. */
.site-nav__dropdown-toggle--current {
  color: var(--accent-primary);
  border-bottom-color: var(--accent-primary);
}

.site-nav__caret {
  width: 6px;
  height: 6px;
  margin-top: -3px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
  transition: transform var(--transition);
}

.site-nav__dropdown-toggle[aria-expanded="true"] .site-nav__caret {
  margin-top: 3px;
  transform: rotate(225deg);
}

.site-nav__dropdown {
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  min-width: 208px;
  margin: 0;
  padding: 8px 0;
  list-style: none;
  background: var(--bg-secondary);
  border: 1px solid var(--border-medium);
  border-radius: var(--radius-sm);
  z-index: 60;
}

.site-nav__dropdown li + li { margin-top: 0; }

.site-nav__dropdown-link {
  display: block;
  padding: 8px 16px;
  font-family: var(--font-body);
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--text-primary);
  text-decoration: none;
}

.site-nav__dropdown-link:hover {
  color: var(--accent-primary);
  text-decoration: none;
}

.site-nav__dropdown-link[aria-current="page"] {
  color: var(--accent-primary);
}

/* Mobile menu toggle (hamburger) */
.menu-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  background: transparent;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  padding: 0;
}

.menu-toggle:hover {
  border-color: var(--border-medium);
}

.menu-toggle__bars {
  display: block;
  width: 20px;
  height: 14px;
  position: relative;
}

.menu-toggle__bars::before,
.menu-toggle__bars::after,
.menu-toggle__bars span {
  content: "";
  display: block;
  position: absolute;
  left: 0;
  width: 100%;
  height: 1.5px;
  background: currentColor;
  transition: transform 0.2s ease, opacity 0.2s ease, top 0.2s ease;
}

.menu-toggle__bars::before { top: 0; }
.menu-toggle__bars span { top: 50%; transform: translateY(-50%); }
.menu-toggle__bars::after { top: 100%; transform: translateY(-100%); }

/* Mobile overlay menu */
.mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: var(--bg-primary);
  transform: translateY(-100%);
  visibility: hidden;
  transition: transform 0.25s ease, visibility 0.25s ease;
  display: flex;
  flex-direction: column;
}

.mobile-menu[data-open="true"] {
  transform: translateY(0);
  visibility: visible;
}

.mobile-menu__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 72px;
  padding-inline: var(--container-padding);
  padding-block: var(--space-sm);
  border-bottom: 1px solid var(--border-subtle);
}

.mobile-menu__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  background: transparent;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  padding: 0;
  font-size: 1.375rem;
  line-height: 1;
}

.mobile-menu__close:hover {
  border-color: var(--border-medium);
}

.mobile-menu__nav {
  flex: 1;
  padding: var(--space-xl) var(--container-padding);
  overflow-y: auto;
}

.mobile-menu__list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.mobile-menu__list li + li { margin-top: 0; }

.mobile-menu__link {
  display: inline-block;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 2.1875rem;
  line-height: 1.1;
  letter-spacing: -0.013em;
  color: var(--text-primary);
  text-decoration: none;
  padding: 8px 0;
}

.mobile-menu__link:hover {
  color: var(--accent-primary);
  text-decoration: none;
}

/* Current page: accent plus weight, so the marker is never colour alone
   (WCAG 1.4.1, 2026-08-14 hub sweep) and cannot be mistaken for either
   the transient hover colour or the always-ruled action link below. */
.mobile-menu__link[aria-current="page"] {
  color: var(--accent-primary);
  font-weight: 600;
}

/* Same reasoning at mobile scale. A filled pill on a 2rem display face would
   dominate the overlay, so the action is marked with the accent plus a rule
   beneath it. The rule also means it is not distinguished by colour alone.
   Added 2026-08-11; class-keyed 2026-08-14 alongside the desktop rule. */
.mobile-menu__link--cta {
  color: var(--accent-primary);
  border-bottom: 2px solid currentColor;
}

body[data-menu-open="true"] {
  overflow: hidden;
}

@media (min-width: 1024px) {
  .site-nav { display: block; }
  .menu-toggle { display: none; }
}

/* Client sign-in (2026-10-11) made the primary nav nine items, and at 40px gaps the
   row no longer fit a 1024px window: the wordmark and "Client sign-in" each broke
   onto two lines. Below 1200 the gaps step down one rung; no nav link wraps. */
@media (min-width: 1024px) and (max-width: 1199.98px) {
  .site-nav__list { gap: var(--space-md); }
}

/* -----------------------------------------------------------
   10. Hero
   ----------------------------------------------------------- */
.hero {
  padding-block: clamp(72px, 10vw, 144px) clamp(64px, 9vw, 112px);
}

.hero__inner {
  max-width: 880px;
}

.hero__headline {
  margin-bottom: var(--space-md);
}

.hero__subhead {
  font-family: var(--font-body);
  font-size: var(--fs-body-large);
  line-height: 1.55;
  color: var(--text-muted);
  font-weight: 400;
  margin-bottom: var(--space-lg);
  max-width: 640px;
}

/* -----------------------------------------------------------
   11. Sections — content patterns
   ----------------------------------------------------------- */
/* Accent rule — short geometric bar above hero / page-header titles */
.accent-rule {
  display: block;
  width: 56px;
  height: 3px;
  background: var(--accent-primary);
  border-radius: 2px;
  margin-bottom: var(--space-md);
}
.section-header {
  margin-bottom: var(--space-lg);
  max-width: var(--width-prose);
}

.section-header__title {
  margin-bottom: var(--space-sm);
}

/* Caps a form section's intro to the field column, so the intro and the
   fields share a right edge (the intake lede overhung its 520px fields by
   160px and nothing in the column lined up). Added 2026-08-15. */
.section-header--narrow {
  max-width: var(--width-narrow);
}

.section-header__lede {
  font-size: var(--fs-body-large);
  color: var(--text-muted);
  line-height: 1.55;
  margin: 0;
}

/* Named principle blocks (How we work / What we don't do) */
.principles {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  max-width: var(--width-prose);
}

.principle__title {
  font-family: var(--font-body);
  font-size: 1.0625rem;
  font-weight: 600;
  margin: 0 0 var(--space-xs);
  color: var(--text-primary);
}

.principle__body {
  margin: 0;
  color: var(--text-primary);
}

/* Client groups (Who hires us) — tighter than principles */
.client-groups {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  max-width: var(--width-prose);
}

/* Was seven items at prose width: one long column with the right half of the
   container empty, which is why the page read narrower than its own grid. Now
   three groups, one per offer, so the columns mirror the offer cards above in
   the same order. Only index.html uses this class. Added 2026-08-11. */
@media (min-width: 1024px) {
  .client-groups {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    column-gap: var(--space-xl);
    max-width: none;
  }

  .client-groups > .prose {
    grid-column: 1 / -1;
    max-width: var(--width-prose);
  }

  /* One heading sets on one line, its neighbours on two, so the three body
     paragraphs started at different heights and the row read as unrelated
     blocks. A two-line floor aligns the body starts; lh degrades harmlessly
     where unsupported. 2026-08-15. */
  .client-groups .principle__title {
    min-height: 2lh;
  }
}

/* The people, on the homepage. The hero promises working directly with the
   people doing the work, so show them. Same assets about.html uses. */
.people-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-lg);
  margin-top: var(--space-lg);
}

.people-strip__person {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.people-strip__photo {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  object-fit: cover;
  object-position: center 20%;
  flex-shrink: 0;
}

.people-strip__name {
  font-weight: 600;
  margin: 0;
}

/* The link that follows the strip ("Meet the team") sat ~12px under the
   avatars where everything else in the rhythm gets 24. 2026-08-15. */
.people-strip + p {
  margin-top: var(--space-md);
}

.people-strip__role {
  font-size: var(--fs-small);
  color: var(--text-muted);
  margin: 0;
}

/* Testimonials placeholder panel */
.testimonial-panel {
  background: var(--accent-tint-soft);
  border-radius: var(--radius-md);
  /* --space-md below 768: 40px padding plus the 24px page margin left the
     22px italic quotes a 231px measure on a 360 screen — three to five
     words a line. 24px buys the quote a third more line. 2026-08-15. */
  padding: var(--space-md);
  max-width: var(--width-prose);
}

.testimonial-panel__quote {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.375rem;
  line-height: 1.5;
  color: var(--text-primary);
  margin: 0 0 var(--space-md);
}

.testimonial-panel__meta {
  font-size: var(--fs-small);
  color: var(--text-muted);
  margin: 0 0 var(--space-md);
}

@media (min-width: 768px) {
  .testimonial-panel {
    /* Vertical padding was a bare var(--space-xl), which is fluid and reaches
       96px at 1440 around a quote that stays 60px tall, so the panel grew to
       300px of mostly empty tint. The 768 value is correctly proportioned, so
       cap it there rather than letting it scale. Corrected 2026-08-11. */
    padding: min(var(--space-xl), 64px) var(--space-lg);
  }
}

/* CTA section */
.cta {
  background: var(--accent-tint-soft);
  text-align: left;
}

.cta__inner {
  max-width: var(--width-prose);
}

.cta__headline {
  margin-bottom: var(--space-sm);
}

.cta__subhead {
  font-size: var(--fs-body-large);
  color: var(--text-muted);
  line-height: 1.55;
  margin-bottom: var(--space-lg);
}

/* -----------------------------------------------------------
   12. Forms
   ----------------------------------------------------------- */
.form {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  max-width: var(--width-narrow);
}

/* Fieldset groups give a long form landmarks: the intake ran fourteen
   questions with a single 24px interval between every one, so nothing told
   the visitor where they were or how much was left. Groups break at 64px
   (24px flex gap + 40px margin, the next Fibonacci rung) under an
   eyebrow-voiced legend. Added 2026-08-15. */
.form-section {
  border: 0;
  padding: 0;
  margin: 0;
  min-width: 0;
}

.form-section + .form-section {
  margin-top: var(--space-lg);
}

.form-section__legend {
  padding: 0;
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--space-sm);
}

.form-section .form-row + .form-row {
  margin-top: var(--space-md);
}

.form-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.form-label {
  font-family: var(--font-body);
  font-size: var(--fs-small);
  font-weight: 500;
  color: var(--text-primary);
}

.form-label .form-optional,
.form-label .form-required {
  color: var(--text-muted);
  font-weight: 400;
}

.form-input,
.form-select,
.form-textarea {
  width: 100%;
  background: var(--bg-secondary);
  border: 1px solid var(--border-input);
  border-radius: var(--radius-sm);
  padding: 14px 16px;
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.5;
  color: var(--text-primary);
  transition: border-color var(--transition);
  -webkit-appearance: none;
  appearance: none;
}

.form-textarea {
  min-height: 140px;
  resize: vertical;
  /* Grows with the message instead of scrolling inside itself; capped so a long
     paste cannot swallow the page. Ignored where unsupported. Added 2026-08-12. */
  field-sizing: content;
  max-height: 560px;
}

/* The compact variant renders one line tall (the profile analyzer's
   headline field); a drag grip on a nominally single-line field reads as a
   bug, so it drops the manual resize the tall textareas keep. 2026-08-15. */
.form-textarea--compact {
  resize: none;
}

.form-select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%231A1A1A' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 16px center;
  background-size: 12px 8px;
  padding-right: 44px;
}

/* The file input's selector button ships as raw OS chrome (Arial, a grey
   bevel) inside an otherwise fully designed field — the loudest default on
   the intake page, at the exact moment a client uploads their resume.
   Dressed as a quiet secondary control. Added 2026-08-15. */
.form-input[type="file"] {
  padding: 10px 16px;
}

.form-input[type="file"]::file-selector-button {
  font: inherit;
  font-size: var(--fs-small);
  font-weight: 500;
  color: var(--accent-primary);
  background: var(--bg-primary);
  /* --border-input, not --border-medium: on the white field the medium
     grey measured 1.51:1, a boundary that was effectively not there. */
  border: 1px solid var(--border-input);
  border-radius: var(--radius-sm);
  padding: 8px 16px;
  margin-right: var(--space-sm);
  cursor: pointer;
  transition: border-color var(--transition), color var(--transition);
}

.form-input[type="file"]::file-selector-button:hover {
  border-color: var(--accent-primary);
  color: var(--accent-hover);
}

/* Deliberately no outline here. This rule used to declare
   `outline: 2px solid var(--accent-tint-soft)`, which measured 1.17:1 against the
   white field, and at higher specificity than the global :focus-visible rule it
   silently replaced a visible 5.9:1 ring with an invisible one on the most
   important interaction on the site. Let the global rule apply. The accent border
   is a supporting cue, not the focus indicator. */
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
  border-color: var(--accent-primary);
}

/* Error state. 3.3.1 needs the failure identified in text and associated with
   the field, and it has to survive after the native validation bubble goes.
   Colour is a second cue here, never the only one. Added 2026-08-11. */
.form-error {
  margin: var(--space-xs) 0 0;
  font-size: var(--fs-small);
  font-weight: 500;
  color: var(--accent-hover);
}

.form-error[hidden] {
  display: none;
}

.form-input[aria-invalid="true"],
.form-select[aria-invalid="true"],
.form-textarea[aria-invalid="true"] {
  border-color: var(--accent-hover);
  border-width: 2px;
}

/* Tie the label into the error state scripts.js already sets on the field.
   Colour is a supporting cue here; the error text and thickened border above
   remain the primary indicators. Added 2026-08-12. */
.form-row:has([aria-invalid="true"]) .form-label {
  color: var(--accent-hover);
}

.form-honeypot {
  position: absolute;
  left: -10000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form-help {
  font-size: var(--fs-small);
  color: var(--text-muted);
  margin: 0;
}

/* The submit spans the field column (was flex-start, a 255px button ending
   57px short of the fields the eye tracked for the whole form — the
   raggedest edge in the stack, on its most important control), and takes a
   40px total interval so it reads as the form's close rather than one more
   row. 2026-08-15. */
.form .btn-primary {
  align-self: stretch;
  margin-top: var(--space-sm);
}

/* -----------------------------------------------------------
   13. Site footer
   ----------------------------------------------------------- */
.site-footer {
  background: var(--bg-secondary);
  border-top: 1px solid var(--border-subtle);
  padding-block: var(--space-xl) var(--space-lg);
  margin-top: 0;
}

.site-footer__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-lg);
}

/* The brand block carries the contact links; give them air below the tagline. */
.site-footer__brand .site-footer__list {
  margin-top: var(--space-md);
}

.site-footer__brand .brand-wordmark {
  font-size: 1.375rem;
  display: inline-block;
  margin-bottom: var(--space-sm);
}

.site-footer__tagline {
  color: var(--text-muted);
  font-size: var(--fs-small);
  max-width: 360px;
  margin: 0;
}

.site-footer__heading {
  font-family: var(--font-body);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0 0 var(--space-sm);
}

.site-footer__list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.site-footer__list li + li { margin-top: 0; }

.site-footer__list a {
  color: var(--text-primary);
  font-size: var(--fs-small);
  text-decoration: none;
}

.site-footer__list a:hover {
  color: var(--accent-primary);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.site-footer__bottom {
  margin-top: var(--space-xl);
  padding-top: var(--space-md);
  border-top: 1px solid var(--border-subtle);
  font-size: var(--fs-small);
  color: var(--text-muted);
  line-height: 1.5;
}

/* Tablet: brand block full-width on top, the three link groups in equal
   columns beneath. Desktop: four tracks, brand widest. The 880px step matches
   the grid--3 breakpoint (three tracks need ~855px before min-content clears). */
@media (min-width: 640px) {
  .site-footer__grid {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-lg) var(--space-xl);
  }
  .site-footer__brand {
    grid-column: 1 / -1;
  }
}

@media (min-width: 880px) {
  .site-footer__grid {
    /* max-content floors keep every link on one line: with plain 1fr
       tracks and the fluid --space-xl gap at its 104px ceiling, the link
       columns fell to ~158px and "Presence Management" wrapped — the one
       ragged column in an otherwise ruled footer, on every page. The brand
       column absorbs the difference. Added 2026-08-15. */
    grid-template-columns: 2fr minmax(max-content, 1fr) minmax(max-content, 1fr) minmax(max-content, 1fr);
    gap: var(--space-xl);
    /* Fixed, not the fluid --space-xl (which reaches 104px): the smaller
       gap keeps real slack around the max-content floors so WCAG 1.4.12
       text spacing cannot push the row past the container. 64 is the
       Fibonacci rung the fluid token starts from. */
    column-gap: 64px;
  }
  .site-footer__brand {
    grid-column: auto;
  }
}

/* -----------------------------------------------------------
   14. Image placeholders
   (for founder photo & og-image while assets are pending)
   ----------------------------------------------------------- */
.photo-placeholder {
  background: var(--accent-tint-soft);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  aspect-ratio: 4 / 5;
  width: 100%;
  max-width: 480px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--space-md);
}

.photo-placeholder__caption {
  font-family: var(--font-body);
  font-size: var(--fs-small);
  color: var(--accent-primary);
  letter-spacing: 0.056em;
  text-transform: uppercase;
  font-weight: 500;
}

/* -----------------------------------------------------------
   15. Page header (used on inner pages)
   ----------------------------------------------------------- */
.page-header {
  padding-block: clamp(64px, 8vw, 104px) clamp(40px, 5vw, 64px);
  border-bottom: 1px solid var(--border-subtle);
}

.page-header__title {
  font-size: clamp(2.1875rem, 4.5vw, 3.5rem);
  line-height: 1.1;
  letter-spacing: -0.021em;
  margin: 0 0 var(--space-md);
  max-width: 880px;
}

/* The article byline: the credential is the whole differentiator, so it
   speaks in the caps-tracked meta voice with its own interval instead of
   reading as a shrunken fourth line of the deck. Added 2026-08-15. */
.page-header__byline {
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: var(--space-sm) 0 0;
}

.page-header__subhead {
  font-family: var(--font-body);
  font-size: var(--fs-body-large);
  color: var(--text-muted);
  line-height: 1.55;
  max-width: 640px;
  margin: 0;
}

/* -----------------------------------------------------------
   16. Service detail blocks (Services page)
   ----------------------------------------------------------- */
.service-detail {
  max-width: var(--width-prose);
}

/* A service article whose card grid needs more than the prose measure: at
   two tracks inside 680px each pricing card ran ~320px and every
   includes-list item wrapped at three words, while the right half of the
   container sat empty — the same finding .client-groups recorded on
   2026-08-11, "the page read narrower than its own grid". The modifier
   lifts the cap from the article and restates it per child, so only the
   grid widens. Widths sit off the proportion ladder by doctrine (section
   2); 55rem/880px echoes the grid's own three-up breakpoint. */
.service-detail--wide {
  max-width: none;
}

.service-detail--wide > * {
  max-width: var(--width-prose);
}

.service-detail--wide > .grid {
  /* Was 55rem when the section ran the full container; under the margin
     grid (section 25) the body track is the measure that matters, and the
     880 cap left ~224px dead right of the two-up pricing cards (design
     critique, 2026-08-20). The grid now fills the body track. */
  max-width: none;
}

/* A margin grid nested inside that article (the client quotes beside the
   packages, 2026-09-07): the modifier restates the prose cap on every child,
   which would squeeze the nested grid's body track to 576 and set a quote
   narrower than the prose above it. The nested grid takes the article's body
   track instead; .quote__text keeps its own measure (section 25). */
.service-detail--wide > .mg {
  max-width: none;
}

.service-detail + .service-detail {
  margin-top: var(--space-xl);
  padding-top: var(--space-xl);
  border-top: 1px solid var(--border-subtle);
}

.service-detail__title {
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 3vw, 2.1875rem);
  font-weight: 500;
  letter-spacing: -0.013em;
  line-height: 1.15;
  margin: 0 0 var(--space-sm);
}

.service-detail__tagline {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.375rem;
  line-height: 1.4;
  color: var(--text-muted);
  margin: 0 0 var(--space-md);
}

.service-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-lg);
  margin: 0 0 var(--space-lg);
  padding: var(--space-md) 0;
  border-top: 1px solid var(--border-subtle);
  border-bottom: 1px solid var(--border-subtle);
  list-style: none;
}

.service-meta li {
  margin: 0;
  font-size: var(--fs-small);
}

.service-meta li + li { margin-top: 0; }

.service-meta dt {
  font-weight: 500;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.056em;
  font-size: 0.75rem;
  margin-bottom: 2px;
}

.service-meta dd {
  margin: 0;
  font-size: 1.0625rem;
  color: var(--text-primary);
}

/* Subsection inside a service block. Was 17px bold body — the same voice
   as inline emphasis, so "How an engagement runs" introduced the four-step
   process at paragraph weight. Raised to the 22px display rung (one step
   under the section h2) on 2026-08-15. Principle titles opt out: that
   component renders 17px bold on index and about, and it must not fork
   inside a service block. */
.service-detail h3:not(.principle__title) {
  font-family: var(--font-display);
  font-size: var(--fs-subsection);
  font-weight: 500;
  letter-spacing: -0.013em;
  line-height: 1.272;
  margin: var(--space-lg) 0 var(--space-sm);
  color: var(--text-primary);
}

.service-detail h3:first-of-type {
  margin-top: var(--space-lg);
}

.service-detail h4 {
  font-family: var(--font-body);
  font-size: 1.0625rem;
  font-weight: 600;
  margin: var(--space-md) 0 4px;
}

/* Pricing tiers — definition list (not pricing-card grid) */
.tier-list {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-md);
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.tier-list li + li { margin-top: 0; }

.tier-list .tier {
  display: grid;
  grid-template-columns: 1fr;
  gap: 4px;
  padding-bottom: var(--space-md);
  border-bottom: 1px solid var(--border-subtle);
}

.tier-list .tier:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}

.tier__price {
  font-family: var(--font-display);
  font-size: 1.375rem;
  font-weight: 500;
  letter-spacing: -0.013em;
  color: var(--accent-primary);
}

.tier__name {
  font-weight: 600;
  font-size: 1.0625rem;
  color: var(--text-primary);
}

.tier__desc {
  margin: 0;
  color: var(--text-muted);
  font-size: 1.0625rem;
}

@media (min-width: 640px) {
  .tier-list .tier {
    grid-template-columns: 180px 1fr;
    gap: var(--space-md);
    align-items: baseline;
  }
}

/* "Best for" / "Not the right fit for" — paired columns on desktop */
.fit-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-lg);
  margin-top: var(--space-md);
}

@media (min-width: 768px) {
  .fit-grid {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-xl);
  }
}

.fit-block h3 {
  margin-top: 0;
}

/* House dash markers, matching .includes-list — these lists shipped with
   the browser's default black discs, the only unstyled markers on the
   site. Fixed 2026-08-15. */
.fit-block ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.fit-block li {
  padding-left: 1.5em;
  position: relative;
  margin: 0 0 var(--space-sm);
}

.fit-block li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.7em;
  width: 8px;
  height: 1.5px;
  background: var(--accent-primary);
}

/* Includes list — cleaner than default bullets */
.includes-list {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-md);
}

.includes-list li {
  padding-left: 1.5em;
  position: relative;
  margin: 0 0 var(--space-md);
}

.includes-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.7em;
  width: 8px;
  height: 1.5px;
  background: var(--accent-primary);
}

.includes-list li:last-child { margin-bottom: 0; }

.includes-list strong {
  display: block;
  font-weight: 600;
  margin-bottom: 2px;
}

/* Engagement timeline */
.timeline {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.timeline li + li { margin-top: 0; }

.timeline__phase {
  font-weight: 600;
  font-size: 0.8125rem;
  text-transform: uppercase;
  letter-spacing: 0.056em;
  color: var(--accent-primary);
  display: block;
  margin-bottom: 4px;
}

/* Add-on rows */
.addon-list {
  list-style: none;
  padding: 0;
  margin: 0;
  max-width: var(--width-prose);
}

.addon-list li {
  padding: var(--space-md) 0;
  border-bottom: 1px solid var(--border-subtle);
  margin: 0;
}

.addon-list li:first-child { border-top: 1px solid var(--border-subtle); }
.addon-list li + li { margin-top: 0; }

.addon__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  margin-bottom: 4px;
}

.addon__name {
  font-weight: 600;
  font-size: 1.0625rem;
  color: var(--text-primary);
}

.addon__price {
  font-family: var(--font-display);
  font-size: 1.0625rem;
  font-weight: 500;
  letter-spacing: -0.013em;
  color: var(--accent-primary);
}

.addon__desc {
  margin: 0;
  color: var(--text-muted);
  font-size: 1.0625rem;
}

/* -----------------------------------------------------------
   17. Founder / bio panel (About page)
   ----------------------------------------------------------- */
.bio-panel {
  background: var(--accent-tint-medium);
  border-radius: var(--radius-md);
  /* --space-md below 768 for the same mobile-measure reason as the
     testimonial panel (the bio text ran a 232px ribbon). 2026-08-15. */
  padding: var(--space-md);
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-lg);
  align-items: start;
  color: var(--text-primary);
}

/* --accent-hover, not --accent-primary: the primary rust measures 4.24:1
   on this panel's own tint (below the 4.5:1 AA line for 17px text) while
   the hover rust measures 5.59:1. The accent loses on a tint of its own
   hue. Corrected 2026-08-15. */
.bio-panel a {
  color: var(--accent-hover);
}

.bio-panel .photo-placeholder {
  background: var(--bg-secondary);
  border-color: rgba(0, 0, 0, 0.06);
  max-width: 100%;
  aspect-ratio: 4 / 5;
}

.bio-panel__photo {
  display: block;
  width: 100%;
  max-width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: center 25%;
  border-radius: var(--radius-md);
}

.bio-panel__name {
  font-family: var(--font-display);
  font-size: 1.75rem;
  font-weight: 500;
  letter-spacing: -0.013em;
  margin: 0 0 4px;
}

.bio-panel__role {
  font-size: 0.9375rem;
  letter-spacing: 0.056em;
  text-transform: uppercase;
  color: var(--text-primary);
  margin: 0 0 var(--space-md);
  font-weight: 500;
  opacity: 0.75;
}

.bio-panel__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  margin-top: var(--space-md);
  list-style: none;
  padding: 0;
}

.bio-panel__links li { margin: 0; }
.bio-panel__links li + li { margin-top: 0; }

@media (min-width: 768px) {
  .bio-panel {
    grid-template-columns: 280px 1fr;
    gap: var(--space-xl);
    padding: var(--space-xl);
    /* Centred against the 350px portrait: top-aligned, the shorter text
       column left a ~165px empty quadrant in the panel's bottom right on
       every article. 2026-08-15. */
    align-items: center;
  }
}

/* -----------------------------------------------------------
   18. Case studies (Work page)
   ----------------------------------------------------------- */
.case-study {
  max-width: var(--width-prose);
}

.case-study + .case-study {
  margin-top: var(--space-xl);
  padding-top: var(--space-xl);
  border-top: 1px solid var(--border-subtle);
}

.case-study__eyebrow {
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--accent-primary);
  margin: 0 0 var(--space-sm);
}

.case-study__title {
  font-family: var(--font-display);
  font-size: clamp(1.375rem, 2.4vw, 1.75rem);
  font-weight: 500;
  letter-spacing: -0.013em;
  line-height: 1.2;
  margin: 0 0 var(--space-md);
}

.case-meta {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-md);
  margin: var(--space-md) 0 var(--space-lg);
  padding: var(--space-md) 0;
  border-top: 1px solid var(--border-subtle);
  border-bottom: 1px solid var(--border-subtle);
}

.case-meta dt {
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.056em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 4px;
}

.case-meta dd {
  margin: 0;
  font-size: 1.0625rem;
  color: var(--text-primary);
}

@media (min-width: 640px) {
  .case-meta {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-md) var(--space-lg);
  }
}

.case-study h3 {
  font-family: var(--font-body);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: var(--space-lg) 0 var(--space-xs);
}

.case-study__outcome {
  margin-top: var(--space-md);
  padding: var(--space-md) var(--space-md);
  background: var(--accent-tint-soft);
  border-radius: var(--radius-md);
}

.case-study__outcome h3 {
  color: var(--accent-primary);
  margin-top: 0;
}

.case-study__outcome p { margin: 0; }

/* -----------------------------------------------------------
   19. FAQ (Contact page) — native <details>
   ----------------------------------------------------------- */
.faq {
  max-width: var(--width-prose);
}

.faq details {
  border-top: 1px solid var(--border-subtle);
  padding: var(--space-md) 0;
}

.faq details:last-of-type {
  border-bottom: 1px solid var(--border-subtle);
}

.faq summary {
  list-style: none;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: 1.0625rem;
  font-weight: 500;
  color: var(--text-primary);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-md);
  padding: 4px 0;
}

.faq summary::-webkit-details-marker { display: none; }

.faq summary::after {
  content: "+";
  flex-shrink: 0;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 1.375rem;
  line-height: 1;
  color: var(--accent-primary);
  transition: transform var(--transition);
  transform-origin: center;
}

.faq details[open] summary::after {
  transform: rotate(45deg);
}

.faq summary:hover {
  color: var(--accent-primary);
}

.faq details > p,
.faq details > div {
  margin-top: var(--space-sm);
  color: var(--text-primary);
  max-width: 60ch;
}

.faq details > p:last-child {
  margin-bottom: 0;
}

/* Animate the answer open instead of snapping. Gated twice: on support for
   interpolating to intrinsic sizes, and on motion being welcome, because the
   global kill switch in the last section matches elements and ::before/::after
   but NOT ::details-content, so this must exclude itself. 0.25s matches the
   mobile menu, the site's only other structural motion. Added 2026-08-12. */
@supports (interpolate-size: allow-keywords) {
  @media (prefers-reduced-motion: no-preference) {
    .faq details {
      interpolate-size: allow-keywords;
    }

    .faq details::details-content {
      block-size: 0;
      overflow-y: clip;
      transition: block-size 0.25s ease, content-visibility 0.25s allow-discrete;
    }

    .faq details[open]::details-content {
      block-size: auto;
    }
  }
}

/* -----------------------------------------------------------
   20. Two-up "How to start" / contact options
   ----------------------------------------------------------- */
.start-options {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-lg);
  max-width: var(--width-prose);
}

@media (min-width: 768px) {
  .start-options {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-xl);
  }
}

.start-option__num {
  font-family: var(--font-display);
  font-size: 2.1875rem;
  font-weight: 500;
  letter-spacing: -0.021em;
  line-height: 1;
  color: var(--accent-primary);
  margin: 0 0 var(--space-sm);
}

.start-option h3 {
  margin-top: 0;
}

/* What Happens Next — numbered editorial list */
.next-steps {
  list-style: none;
  padding: 0;
  margin: 0;
  max-width: var(--width-prose);
  counter-reset: step;
}

.next-steps li {
  position: relative;
  padding: var(--space-md) 0 var(--space-md) 56px;
  border-bottom: 1px solid var(--border-subtle);
  margin: 0;
  counter-increment: step;
}

.next-steps li:first-child { border-top: 1px solid var(--border-subtle); }
.next-steps li + li { margin-top: 0; }

.next-steps li::before {
  content: counter(step, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: var(--space-md);
  font-family: var(--font-display);
  font-size: 1.0625rem;
  font-weight: 500;
  color: var(--accent-primary);
  letter-spacing: -0.013em;
}

.next-steps strong {
  display: block;
  font-weight: 600;
  margin-bottom: 2px;
}

.next-steps p { margin: 0; color: var(--text-muted); }

/* -----------------------------------------------------------
   21. In-browser tools — resume check (resume-check.html) and
   keyword scanner (keyword-scanner.html). Each page's logic
   lives in a page-local .js file in the raleigh-resume folder
   (deliberately not shared files); the scanner page reuses the
   resume-check form, drop-zone, verdict, and read styles, and
   adds its own term chips at the end of this section.
   Added 2026-08-12; scanner 2026-08-14.
   ----------------------------------------------------------- */
.resume-check {
  max-width: var(--width-prose);
}

.resume-check__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  align-items: flex-start;
}

.resume-check__form .form-row {
  align-self: stretch;
}

/* Room for a real paste; field-sizing (section 12) then grows it to its cap. */
.resume-check__form .form-textarea {
  min-height: 260px;
}

/* A one-line field (the profile analyzer's headline) opts out of the paste
   floor; its rows attribute and field-sizing set the height instead. */
.resume-check__form .form-textarea--compact {
  min-height: 0;
}


.resume-check__hint {
  font-size: var(--fs-small);
  color: var(--text-muted);
  margin: 0;
}

.resume-check__verdict {
  font-family: var(--font-display);
  font-size: 1.375rem;
  font-weight: 500;
  letter-spacing: -0.013em;
  color: var(--text-primary);
  margin: var(--space-lg) 0 var(--space-xs);
}

.resume-check__read {
  color: var(--text-muted);
  margin: 0 0 var(--space-md);
  /* The meter's "Set aside:" line quotes the visitor's own text, an
     unbroken email address included; every other user of this class is
     the tools' own prose, which the rule cannot hurt (the third sweep,
     2026-09-13, X-MOB-01). */
  overflow-wrap: anywhere;
}

.findings {
  list-style: none;
  padding: 0;
  margin: 0;
}

.findings li + li { margin-top: 0; }

.finding {
  padding: var(--space-md) 0;
  border-bottom: 1px solid var(--border-subtle);
  margin: 0;
}

.finding:first-child { border-top: 1px solid var(--border-subtle); }

.finding__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  margin-bottom: 4px;
}

.finding__name {
  font-weight: 600;
  font-size: 1.0625rem;
  color: var(--text-primary);
}

/* The status is carried by the WORD (Strong / Worth a look / Needs work);
   colour is a supporting cue, never the only one. */
.finding__status {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.056em;
  text-transform: uppercase;
  white-space: nowrap;
}

.finding--strong .finding__status { color: var(--text-muted); }
.finding--look .finding__status { color: var(--accent-primary); }
.finding--work .finding__status { color: var(--accent-hover); }

.finding__detail {
  margin: 0;
  color: var(--text-muted);
  font-size: 1.0625rem;
  /* A quoted headline or file name with no break in it wraps rather
     than scrolling the page sideways at 390 (bug sweep 2026-09-11, AT-9). */
  overflow-wrap: anywhere;
}

/* PDF drop zone (stage B). The native file input is visually hidden but
   stays in the tab order; its focus ring is forwarded to the styled label.
   The zone is a control, so its boundary is the input frame's 3.66:1 rule,
   not the 1.44:1 divider grey (bug sweep 2026-09-12, A11Y-L4-12). */
.resume-check__drop {
  border: 1px dashed var(--border-input);
  border-radius: var(--radius-md);
  padding: var(--space-md);
  transition: border-color var(--transition), background var(--transition);
}

/* The zone while a file is being read (bug sweep 2026-09-12, RC-L3-06). */
.resume-check__drop.is-busy {
  cursor: progress;
  /* The read is visible where the visitor tapped, not only in the cursor
     (the third sweep, 2026-09-13, X-MOB-03); the label reads as inert
     (X-FILE-08). */
  border-color: var(--accent-primary);
  background: var(--accent-tint-soft);
}

.resume-check__drop.is-busy .resume-check__file-btn {
  cursor: progress;
  opacity: 0.6;
  text-decoration: none;
}

.resume-check__drop.is-dragover {
  border-color: var(--accent-primary);
  background: var(--accent-tint-soft);
}

.resume-check__drop-lead {
  margin: 0 0 var(--space-xs);
  font-weight: 500;
}

.resume-check__file {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.resume-check__file-btn {
  color: var(--accent-primary);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}

.resume-check__file-btn:hover {
  color: var(--accent-hover);
}

.resume-check__file:focus-visible ~ .resume-check__drop-lead .resume-check__file-btn {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
  border-radius: 2px;
}

/* role="status" line under the button; min-height so appearing text does
   not shift the layout. */
.resume-check__status {
  font-size: var(--fs-small);
  color: var(--text-muted);
  margin: 0;
  min-height: 1.5em;
  /* The extraction line names the file; a long underscore name wraps
     (bug sweep 2026-09-11, AT-9). */
  overflow-wrap: anywhere;
}

/* Keyword scanner: ranked-term chips and their result groups. Hit and
   miss are carried by the group heading the chips sit under ("In your
   resume" / "Not found in your resume"); the chip styling is a
   supporting cue, never the only one. */
.kw-scan {
  max-width: var(--width-prose);
}

/* The verb bank is 87 chips in eight groups; inside the prose measure it
   ran four mobile screenfuls tall and nearly every group ended in a one-
   or two-chip orphan row. 55rem (the service-detail--wide width) shortens
   the block by a third and settles the rag; its section-header keeps its
   own prose cap. Added 2026-08-15. */
.kw-scan.verb-bank {
  max-width: 55rem;
}

.kw-group {
  margin-top: var(--space-md);
}

.kw-group__title {
  font-size: 1.0625rem;
  font-weight: 600;
  color: var(--text-primary);
  margin: 0 0 var(--space-xs);
}

.kw-group__hint {
  font-size: var(--fs-small);
  color: var(--text-muted);
  margin: 0 0 var(--space-xs);
}

.kw-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

.kw-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  margin: 0;
  padding: 4px 14px;
  border: 1px solid var(--border-medium);
  border-radius: 999px;
  font-size: var(--fs-small);
  color: var(--text-primary);
  /* A chip never runs wider than its row: a repeated web address in a
     posting wraps inside the chip (bug sweep 2026-09-11, AT-9). */
  max-width: 100%;
}

.kw-chip__term {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Hit chips take the accent border so the two match groups differ by more
   than a whisper; the group headings still carry the meaning in text. */
.kw-chip--hit {
  border-color: var(--accent-primary);
  background: var(--accent-tint-soft);
}

.kw-chip--miss {
  border-style: dashed;
  color: var(--text-muted);
}

.kw-chip__count {
  font-size: 0.75rem;
  color: var(--text-muted);
}

/* Post previewer: feed-shaped fold cards. The fake post header is visual
   dressing (aria-hidden in the markup); the caption under each card
   carries the numbers, so the muted below-the-fold styling is never the
   only carrier of the cut. The hidden text stays at full --text-muted
   contrast (5.33:1 on the card): it is the user's own prose, and the
   2026-08-14 sweep confirmed an opacity dim on it fails WCAG 1.4.3. */
.post-card {
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: var(--space-md);
  background: var(--bg-secondary);
  max-width: 555px;
}

.post-card--mobile {
  max-width: 380px;
}

/* The post previewer's cards (post-card--feed) draw the draft at the
   feed's measure, so a card draws the lines its fold model budgets and
   no more (tools campaign a5_fix, 2026-10-08, A5-C01 to C09, A5-C22 to
   C27). LinkedIn's desktop column is 526px of system-ui 14px (the round
   A5 fold set, 104 posts), which is 564px at this card's 15px type, and
   the phone-width stand-in's 362px column is 388px. The model budgets a
   line in cells and the browser wraps at words, so each measure adds
   36px, about five characters, for the word that does not fit at a
   line's end: 600px and 424px. The width is the content box, so the
   header, the frames' skeleton bars and the text all share it; each card
   keeps its measure on a phone and scrolls sideways inside .post-scroll
   (PP-L2-05). The profile analyzer's About cards take their own
   modifier, post-card--about, below (tools campaign a6_fix2, 2026-10-09:
   this note said they carried none, the a6_fix verifier's note). */
.post-card--feed {
  box-sizing: content-box;
  width: 600px;
  max-width: none;
}

/* The app card's measure is the narrowest that holds the model's 45
   cells a line for every post the reading bench reads (tools campaign
   a5_apply, 2026-10-08, the operator's ruling on round A5, "Mobile app":
   "45 a line, fit the phone", judged by V3). 336px, 45 characters of
   prose, drew a fourth line on 4 real tune posts and on invented drafts
   of long addresses, emoji, Arabic and CJK, where a line the model counts
   as 45 cells wraps sooner in the browser; 400px draws three lines or
   fewer on all of them, and holds about 60 characters of plain prose. */
.post-card--feed.post-card--mobile {
  width: 400px;
}

.post-card--feed .post-card__text {
  font-family: system-ui, sans-serif;
}

/* On a phone the app card fits the screen with no sideways scroll: the
   12px type rung and the same measure in proportion (320px), with 8px
   padding, make a 338px card. A 390px phone's column is 342px, and the
   report sheet's padding leaves 292px inside it, so on the sheet the app
   card's scroller takes the sheet's padding on each side (340px). The
   13px rung would need 347px of measure and does not fit. The desktop
   card keeps its measure and scrolls (PP-L2-05). */
@media (max-width: 639.98px) {
  .post-card--feed.post-card--mobile {
    width: 320px;
    padding: var(--space-xs);
  }
  .post-card--feed.post-card--mobile .post-card__text {
    font-size: 0.75rem;
  }
  .tool-memo .post-scroll--mobile {
    margin-inline: calc(-1 * var(--space-md));
  }
}

/* The profile analyzer's About cards (post-card--about) draw the section
   at the measure their fold model budgets, so a card draws no more lines
   than its model's cap (tools campaign a6_fix, 2026-10-09, A6-C09 to
   A6-C38, X-16, bar P17). The model budgets 80 cells a line on desktop
   and 50 in the app, with the " ...see more" marker's room kept on the
   last line; on the shared box (505px, 330px for the app, both 242px at a
   390px viewport) the app card drew a fifth line on 237 of 242 tune About
   units. The desktop card takes the feed's measure for the same 80 cells
   (600px of system-ui 15px, above); the app card holds 50 cells at the
   same proportion as the feed's 45 in 400px. Each card keeps its measure
   on a phone and scrolls sideways inside its .post-scroll. The previewer's
   cards and the byline card do not move. */
.post-card--about {
  box-sizing: content-box;
  width: 600px;
  max-width: none;
}

.post-card--about.post-card--mobile {
  width: 448px;
}

.post-card--about .post-card__text {
  font-family: system-ui, sans-serif;
}

/* The desktop card holds its width on a phone and scrolls sideways
   inside its own wrapper, so the desktop and app previews differ there
   the way the two feeds do (bug sweep 2026-09-12, PP-L2-05). */
.post-scroll {
  overflow-x: auto;
  /* The scroller is the containing block for the visually hidden fold
     marker inside the card, so the marker cannot escape the clip and
     drag the page sideways at a large root font (the third sweep,
     2026-09-13, X-DEG-01). */
  position: relative;
}

.post-scroll > .post-card:not(.post-card--mobile) {
  min-width: 555px;
}

.post-card__header {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  margin-bottom: var(--space-sm);
}

.post-card__avatar {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--accent-tint-medium);
  flex: none;
}

.post-card__id {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.post-card__name {
  font-weight: 600;
  font-size: var(--fs-small);
  color: var(--text-primary);
}

.post-card__meta {
  font-size: 0.75rem;
  color: var(--text-muted);
  /* The byline card quotes the headline's slice; a run of hashtags with no
     space wraps like the post text beside it (bug sweep 2026-09-11, AT-9). */
  overflow-wrap: anywhere;
}

.post-card__text {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: var(--fs-small);
  color: var(--text-primary);
}

.post-card__more {
  color: var(--text-muted);
}

.post-card__hidden {
  display: block;
  color: var(--text-muted);
  border-top: 1px dashed var(--border-medium);
  margin-top: var(--space-xs);
  padding-top: var(--space-xs);
}

/* Bullet meter: the graded line is quoted inside the standard finding
   row. overflow-wrap so a pasted URL or long identifier cannot push the
   row past a 320px viewport; flex-basis 100% so the status word always
   drops to its own line, no matter how short the quote, instead of
   flip-flopping between inline-right and own-line within one list. */
.finding__name--quote {
  font-weight: 400;
  font-style: italic;
  overflow-wrap: anywhere;
  flex: 1 1 100%;
}

/* The tools move focus to the verdict line after a run; it is not a
   control, so the global element-typed focus rule does not reach it. */
.resume-check__verdict:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}

/* Tier-1 polish (2026-08-14). The example and print affordances are
   quiet inline text-buttons in the hint voice; the ambient live line
   reserves its height so typing never shifts the form; counts read in
   tabular figures so digits align like set type. */
.tool-example-btn,
.tool-print-btn {
  font: inherit;
  font-size: var(--fs-small);
  color: var(--accent-primary);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}

/* Both buy explicit target height: the print button always stood alone,
   and the example/clear pair's containing hint holds no non-target text,
   so the 2.5.8 inline exemption does not cover them either (their bare
   line box measured ~24.3px, right on the floor). 2026-08-15. */
.tool-print-btn,
.tool-example-btn {
  padding: 2px 0;
}

.tool-example-btn:hover,
.tool-print-btn:hover {
  color: var(--accent-hover);
}

.tool-live {
  min-height: 1.5em;
}

.resume-check__verdict,
.kw-chip__count,
.kw-group__hint,
.finding__detail,
.tool-live {
  font-variant-numeric: tabular-nums;
}

/* Tool empty states (2026-08-15): each tool draws its RESULT SHAPE
   before input — scaffolding, never plausible data (bars, dashed
   rules, ghost labels; nothing a visitor could mistake for output).
   The scaffold block is aria-hidden in the page markup: pre-submit
   it is a visual echo of the field plus decorative framing, and the
   accessible carriers stay the hint text, the ambient count line,
   and the submitted report announced by the focus-move pattern.
   The page script hides the block once a full report renders. */
.tool-empty {
  margin-top: var(--space-lg);
}

/* A text line reduced to structure. Bar height plus margins land each
   line on the 24px grid of the frame's own text (fs-small x phi line
   box), so the swap from bars to the typed draft does not move the
   fold rule. Widths are layout geometry, off-ladder by doctrine. */
.skel-bar {
  display: block;
  height: 10px;
  border-radius: 999px;
  background: var(--border-medium);
  margin: 7px 0;
}

.skel-bar[hidden] { display: none; }

.skel-bar--dim { background: var(--border-subtle); }

.skel-bar--w90 { width: 90%; }
.skel-bar--w75 { width: 75%; }
.skel-bar--w60 { width: 60%; }
.skel-bar--w45 { width: 45%; }
.skel-bar--w30 { width: 30%; }

/* Header stand-ins for a card's name/meta lines, with margins sized
   to those line boxes so the scaffold header holds the report
   header's height. */
.skel-bar--name { width: 110px; margin: 7px 0; }

.skel-bar--meta {
  width: 150px;
  height: 8px;
  margin: 5px 0;
  background: var(--border-subtle);
}

/* The fold, drawn and named: the scaffold's dashed rule carries a
   small-caps label so the page's promise (we mark the cut) is visible
   before a single character is typed. Decorative, inside the
   aria-hidden scaffold; the numbers live in the hint under each frame
   and in the page prose. */
.tool-skel-fold {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  margin: var(--space-xs) 0;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.056em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--text-muted);
}

.tool-skel-fold::before,
.tool-skel-fold::after {
  content: '';
  flex: 1;
  border-top: 1px dashed var(--border-medium);
}

/* The scaffold frame's above-fold zone: min-height is three lines on
   the 24px grid, pinning the fold rule in place while the typed draft
   is still shorter than the fold, so the rule never chases the caret.
   flow-root contains the skeleton bars' margins: without it the first
   bar's top margin collapses out of the zone and the fold rule steps
   up 7px the moment bars swap for typed text (visible on the About
   frame, whose card has no header margin to absorb the escape). */
.tool-frame__above {
  display: flow-root;
  min-height: 72px;
}

/* Pre-submit overflow sits under the labeled rule, which already draws
   the boundary; strip the report card's own border-top twin. */
.tool-frame__overflow {
  border-top: 0;
  margin-top: 0;
  padding-top: 0;
}

.tool-frame__overflow[hidden] { display: none; }

/* The About-section frame (profile analyzer) reserves four lines: its
   desktop fold sits near 300 characters, one line-grid step deeper
   than a post's. */
.tool-frame__above--tall {
  min-height: 96px;
}

/* Scaffold verdict rows (bullet meter, resume check): the finding
   vocabulary with no grades in it. Rows tighten to the 8px rung, a
   sketch of the ledger rather than the ledger, and the rubric text
   drops to the muted voice so nothing reads as a result. "Verdict"
   in the status column names the column and claims no value. */
.tool-empty .finding {
  padding: var(--space-xs) 0;
}

/* Equal specificity with the status-modifier color rules and later
   in source: fine while scaffolds never hold graded rows, but reusing
   .tool-empty around real findings would flatten their status colors
   to muted. Keep graded rows out of scaffold containers. */
.tool-empty .finding__name,
.tool-empty .finding__status {
  color: var(--text-muted);
}

.tool-empty .finding__head .skel-bar {
  align-self: center;
}

/* Chip-shaped skeleton for list results (keyword scanner): the real
   chip's box with nothing in it. Dashed is the miss group's voice.
   Widths stand in for term lengths, layout geometry off-ladder. */
.skel-chip {
  display: inline-block;
  height: 34px;
  border: 1px solid var(--border-medium);
  border-radius: 999px;
}

/* Dashed keeps the real miss chip's border-medium: at --border-subtle
   the outline measured 1.25:1 on the tinted section's white and read
   as blank space (2026-08-15 sweep), which made the miss group look
   empty rather than awaiting terms. */
.skel-chip--dashed {
  border-style: dashed;
}

.skel-chip--w1 { width: 72px; }
.skel-chip--w2 { width: 96px; }
.skel-chip--w3 { width: 120px; }
.skel-chip--w4 { width: 148px; }

/* The one-line hint that closes a scaffold block. */
.tool-empty > .kw-group__hint {
  margin-top: var(--space-sm);
}

/* The results cascade: verdict first, then each row eases in, the way a
   reader works down a report. Self-gated per section 23's note (the
   global kill switch does not reach patterns like this), and inert
   without the class, so reduced-motion renders everything at once. */
@media (prefers-reduced-motion: no-preference) {
  .stagger-item {
    animation: result-rise 0.3s cubic-bezier(0.2, 0.7, 0.3, 1) both;
    animation-delay: calc(var(--stagger-i, 0) * 55ms);
  }

  @keyframes result-rise {
    from {
      opacity: 0;
      transform: translateY(8px);
    }
  }

  /* The card-link's hover nudge (translateX 2px) retired 2026-08-21
     in favour of the drawn underline in section 26: one hover motion
     per element, and the pen line is the more on-brand of the two. */
}


/* -----------------------------------------------------------
   22. Motion — cross-page transitions and scroll reveals
   Progressive enhancement, added 2026-08-12. Everything here is
   gated on motion being welcome, and scroll reveals additionally
   on @supports; any browser that fails a gate gets exactly the
   pre-existing behaviour. The kill switch below does not reach
   view transitions or scroll timelines, so the gate here is the
   only thing honouring reduced motion for these.
   ----------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  /* Animated navigation between pages (Chrome/Edge 126+, Safari 18.2+;
     others navigate as before). The header is named so it holds still
     while page content cross-fades beneath it. */
  @view-transition {
    navigation: auto;
  }

  .site-header {
    view-transition-name: site-header;
  }

  /* The mark and the name are one group of their own, so the R holds
     still in its own right while everything around it cross-fades
     (2026-09-07, next-draft n7_plumb). Scoped to the header because a
     view-transition-name must be unique in the document and the mobile
     menu and the footer carry the same wordmark; a duplicate name makes
     the browser skip the transition entirely. */
  .site-header .brand-wordmark {
    view-transition-name: brand-wordmark;
  }

  /* The cross-fade at house speed: the UA default (~0.25s) read
     sluggish between pages (operator, 2026-08-21), so every
     transition group runs 0.1s: felt, never waited on. Pairs with
     the speculation rules in each page's head, which prerender the
     next page on pointerdown so the fade starts from a ready page. */
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation-duration: 0.1s;
  }

  /* Scroll-driven reveals: content below the fold fades up 12px as it
     enters. Runs on the compositor, no script. Elements already in view
     at load sit at the end of their entry range, so nothing flashes. */
  @supports (animation-timeline: view()) {
    @keyframes rise-in {
      from {
        opacity: 0;
        transform: translateY(12px);
      }
    }

    @keyframes rule-grow {
      from {
        transform: scaleX(0);
      }
    }

    /* animation-timeline must come after the shorthand: `animation`
       resets it to auto. */
    .section-header,
    .card {
      animation: rise-in linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 60%;
    }

    .accent-rule {
      transform-origin: left;
      animation: rule-grow linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 80%;
    }

    /* Marginalia settles in a beat after the text it annotates: the
       note's range starts and ends later than the body's (entry 0-60%
       above), so the body always lands first, the way a note is
       written after the line it comments on. 2026-08-21. */
    .mg__margin .note {
      animation: rise-in linear both;
      animation-timeline: view();
      animation-range: entry 10% entry 75%;
    }
  }
}

/* -----------------------------------------------------------
   23. Reduced motion
   ----------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* -----------------------------------------------------------
   24. Print — the tools produce a report worth keeping.
   Sitewide, printing drops the chrome; on the tool pages
   (scoped via their .resume-check / .kw-scan containers) the
   forms, explainers, FAQs, and the verb bank drop too, so what
   prints is the page title, the tool's section header, and the
   rendered results under a provenance line. Added 2026-08-14.
   ----------------------------------------------------------- */
@media print {
  .site-header,
  .mobile-menu,
  .menu-toggle,
  .site-footer,
  .skip-link,
  .cta,
  noscript,
  .tool-print-btn,
  .tool-example-btn,
  .tool-empty {
    display: none !important;
  }

  /* A print mid-cascade must never capture rows at their from-state
     opacity; some engines re-run animations in the print clone. The
     scroll-driven reveals of section 22 hold every below-the-fold card
     and heading at opacity 0 on paper, where nothing scrolls (the third
     sweep, 2026-09-13, X-ART-04). */
  .stagger-item,
  .section-header,
  .card,
  .accent-rule,
  .mg__margin .note {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }

  body {
    background: #fff;
  }

  /* The class twins (body.print-tool, .print-hide, .print-empty) are set
     by scripts.js for engines without :has() (bug sweep 2026-09-12,
     COMPAT-L1-08). */
  body:has(.resume-check, .kw-scan) form,
  body:has(.resume-check, .kw-scan) section:has(.faq),
  body:has(.resume-check, .kw-scan) section:has(.prose),
  body:has(.resume-check, .kw-scan) section:has(.verb-bank),
  body.print-tool .print-hide {
    display: none !important;
  }

  /* A step with no report prints nothing, not a heading over a blank; a
     report's navigation link (the hand-off to the meter) is screen-only
     (bug sweep 2026-09-11, AT-8). */
  body:has(.resume-check, .kw-scan) section:has(.tool-memo):not(:has(.tool-memo:not(:empty))),
  section:has(.profile-analyzer__form):not(:has(#profile-results:not(:empty))),
  .tool-memo p:has(> a.btn-secondary),
  body.print-tool .print-empty,
  body.print-tool .tool-memo a.btn-secondary {
    display: none !important;
  }

  body:has(.resume-check, .kw-scan) main::before,
  body.print-tool main::before {
    content: 'Raleigh Resume · raleighresume.com';
    display: block;
    font-family: var(--font-display);
    font-size: var(--fs-small);
    letter-spacing: 0.056em;
    text-transform: uppercase;
    color: var(--text-muted);
    padding: var(--space-md) var(--container-padding) 0;
  }
}

/* -----------------------------------------------------------
   25. The editor's margin
   The accent used as an editor's pen, never as paint: below the
   header the page gains a left margin column that carries the
   rule, the portraits, and short italic notes holding facts the
   site already states. The firm mark is the R monogram tile
   (inline SVG symbol #rmark in each page, the favicon geometry;
   the acorn held this spot 2026-08-20 to -21 and is retired),
   18px beside the wordmark; the
   caret glyph was retired 2026-08-20. Spec of record:
   plans/2026-08-20-editors-margin-brief.md. Sizes are ladder
   rungs, existing tokens, micro-gaps of 6px and under (the
   doctrine's threshold; this note once claimed 12, and the two
   12px gaps that slipped in under that claim were snapped to
   the 16 rung in the 2026-08-22 spacing review), or image
   geometry (portraits): nothing new is off-ladder.
   Adopted 2026-08-20 (operator: acorn yes, hero hand-off no).
   ----------------------------------------------------------- */
:root {
  --margin-col: 168px;   /* space rung (Fibonacci 21 x 8) */
  --margin-gap: 24px;    /* = --space-md */
}

/* A margin note: what the editor wrote beside the text. Notes are
   real text in source order (before the text they annotate) and
   carry facts, never slogans; at most two per margin. Accent on
   --bg-primary measures ~5.9:1, on white ~6.2:1. The caret glyph
   that once preceded notes was retired 2026-08-20 (operator: "don't
   like the little ^ symbols"); notes sit flush on the column edge. */
.note {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  font-size: var(--fs-small);
  line-height: 1.4;
  color: var(--accent-primary);
  margin: 0 0 var(--space-xs);
  text-wrap: pretty;
}
.note--quiet { color: var(--text-muted); }

/* The CPRW badge (B-008, 2026-10-14): PARWCC's own artwork, shown beside the
   firm's credential claim (two CPRWs), never beside copy about who writes.
   Black on transparent, so it sits on --bg-primary or a tinted section. */
.credential-badge {
  display: block;
  width: 112px;
  height: auto;
  margin-top: var(--space-sm);
}
/* Never place a note on --accent-tint-medium: accent drops to 4.24:1 and
   quiet to 3.67:1 there (a11y sweep, 2026-08-20), and .section--accent-medium p
   out-specifies .note for color anyway. Notes live on --bg-primary, white,
   or --accent-tint-soft only. */

/* Tabular figures for the firm's numbers (2026-09-07): the price lines on the
   priced pages and the margin notes that carry a count set with aligned
   digits, the same reason the tool counts do (section 21). Both faces ship the
   feature, so a note in Fraunces and a price in Fraunces agree column to
   column. */
.card .card-price,
.note {
  font-variant-numeric: tabular-nums;
}

/* The margin grid. Below 1024 the margin stacks above the text,
   except in the hero (see below). */
.mg { display: block; }
.mg__margin { margin-bottom: var(--space-sm); }
.mg__margin .accent-rule { margin-bottom: var(--space-sm); }
.mg__body { min-width: 0; }

@media (min-width: 1024px) {
  .mg {
    display: grid;
    grid-template-columns: var(--margin-col) minmax(0, 1fr);
    column-gap: var(--margin-gap);
    align-items: start;
  }
  .mg__margin { margin-bottom: 0; }
  .mg__margin .note:first-child { margin-top: 0.35em; }
  .mg__margin .accent-rule + .note,
  .mg__margin .accent-rule + .people { margin-top: 0; }
}

/* Hero: the rule, the people who do the work, the place. */
.hero__inner--mg { max-width: none; }
.hero .mg__body .hero__headline { max-width: 880px; }
.hero .mg__margin .accent-rule { margin-bottom: var(--space-md); }
.people {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  margin: 0 0 var(--space-md);
  padding: 0;
  list-style: none;
}
.people__person { display: flex; gap: 16px; align-items: center; }
.people__photo {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
}
.people__name { font-size: var(--fs-small); font-weight: 500; color: var(--text-primary); line-height: 1.3; margin: 0; }
.people__role { font-size: var(--fs-small); color: var(--text-muted); line-height: 1.3; margin: 0; }
@media (min-width: 1024px) {
  .people { gap: var(--space-md); }
  .people__person { flex-direction: column; align-items: flex-start; gap: 8px; }
  .people__photo { width: 104px; height: 104px; }
}
/* The hero's margin is the one whose DOM order differs from the grid:
   the body comes FIRST in source so the h1 is the first thing in main
   at every width (1.3.2), and explicit grid placement puts the margin
   back in the left column on desktop. On phones natural order already
   reads headline first; the portraits become a row, the rule hides. */
@media (min-width: 1024px) {
  .hero .mg__body { grid-column: 2; grid-row: 1; }
  .hero .mg__margin { grid-column: 1; grid-row: 1; }
}
@media (max-width: 1023.98px) {
  .hero .mg__margin { margin: var(--space-lg) 0 0; }
  .hero .mg__margin .accent-rule { display: none; }
  /* Two people in a row: each person stacks photo-above-name so the
     pair fits side by side at 390 (photo-beside-name is ~250px each
     and wrapped to a stack, against the brief's "portraits in a row"). */
  .people { flex-direction: row; flex-wrap: wrap; gap: var(--space-md); }
  .people__person { flex: 0 1 calc(50% - var(--space-sm)); flex-direction: column; align-items: flex-start; gap: 8px; }
  .people__photo { width: 64px; height: 64px; }
}

/* Eyebrows lose the caps-and-tracking voice and speak italic.
   (Overrides sections 4 and 7; margins are inherited unchanged.) */
.eyebrow,
.card .card-eyebrow {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  font-size: var(--fs-body);
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-muted);
}

/* Who hires us: rows, the engagement named in the margin. */
.fit { display: flex; flex-direction: column; gap: var(--space-lg); }
.fit .mg__body { max-width: var(--width-prose); }
.fit .mg__body h3 { margin-bottom: var(--space-xs); }
.fit .mg__body p { margin: 0; }

/* Testimonials: provenance in the margin, the quote on the page. */
.quote {
  padding-block: var(--space-md);
  border-top: 1px solid var(--border-subtle);
}
.quote:last-of-type { border-bottom: 1px solid var(--border-subtle); }
.quote__text {
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--fs-subsection);
  line-height: 1.5;
  color: var(--text-primary);
  max-width: var(--width-prose);
  margin: 0;
}
.quote .note { margin-bottom: 4px; }
.quote .note:first-child { margin-top: 0.25em; }

/* The long-form exception: articles are the one place the wider 768
   measure costs real reading comfort (they already ran at the top of
   the 65-75ch band at 680), so pages with a byline keep the reading
   measure. :has() misses only in older engines, which then get 768,
   a graceful cost. 2026-08-20. */
body:has(.page-header__byline) .prose {
  max-width: 680px;
}

/* The mark beside the wordmark (header, mobile menu, footer). The footer
   variant restates display: the reset makes svg block-level and section 13's
   .site-footer__brand .brand-wordmark is more specific than one class. */
.brand-wordmark--mark { display: inline-flex; align-items: center; gap: 8px; }
.site-footer__brand .brand-wordmark--mark { display: inline-flex; }
.brand-mark { width: 18px; height: 18px; color: var(--accent-primary); flex: none; }
.brand-wordmark:hover .brand-mark { color: var(--accent-hover); }

/* The standalone eyebrow link (the Articles breadcrumb) measured 21px
   tall, under the 24px 2.5.8 floor; padding buys the target without
   moving the text (negative margin cancels the box growth). */
.eyebrow a { display: inline-block; padding-block: 4px; margin-block: -4px; }

/* The article byline and the About roles join the italic voice; the
   bio panel gives up its accent fill (the accent never fills a surface)
   for the card grammar. Footer column headings keep their small caps:
   utility labels, the design system's sanctioned use. 2026-08-20. */
.page-header__byline {
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--fs-small);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
}
/* The bio panels read as abstracted resume documents (operator ask,
   2026-08-20): a square-cornered sheet under a letterhead rule, the
   name over the person's own flair mark (the same rule and dot-rule
   the personal LinkedIn banners carry, generate.py FLAIRS), the photo
   affixed with a hairline mat, and a footer rule before the links,
   like a document's closing line. Styling and decorative marks only;
   the curated bio copy is untouched. No shadows, no tint fills. */
.bio-panel {
  background: var(--bg-secondary);
  border: 1px solid var(--border-medium);
  border-top: 2px solid var(--accent-primary);
  border-radius: 0;
}
.bio-panel__photo {
  border-radius: 0;
  border: 1px solid var(--border-medium);
  padding: 8px;
  background: var(--bg-secondary);
}
/* 8 above, 16 below (space rungs, snapped 2026-08-22 from the eyeballed
   12/14): the mark binds to the name it signs, then a full step before
   the role line, the signature-under-the-name logic. */
.bio-panel__flair {
  margin: 8px 0 16px;
  color: var(--accent-primary);
}
.bio-panel__flair svg { display: block; }
.bio-panel__name { margin-bottom: 0; }
.bio-panel__links {
  border-top: 1px solid var(--border-subtle);
  padding-top: var(--space-sm);
}
.bio-panel__role {
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--fs-body);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-muted);
  opacity: 1;
}

/* -----------------------------------------------------------
   26. The document details (2026-08-21)
   The document grammar the bio panels opened (section 25),
   carried further: the tool memo on the cool ink, the article
   end mark, the monogram watermark on the low-stakes pages,
   hanging punctuation on the testimonials, and the drawn
   underline on standalone action links. Sizes are ladder rungs,
   existing tokens, micro-gaps, or image geometry, as in 25.
   ----------------------------------------------------------- */

/* The tool memo: a populated results container reads as a typed
   memo on the firm's letterhead: a white sheet, square corners,
   the cool ink's rule, and the provenance line print already
   gives the report (section 24). Carried by the .tool-memo class
   on each tool's results container; the five page-local tool
   scripts are untouched. An empty container (the scripts clear
   with textContent = '') keeps no box: the sheet exists only
   while a report does. */
.tool-memo:not(:empty) {
  background: var(--bg-secondary);
  border: 1px solid var(--border-medium);
  border-top: 2px solid var(--ink-cool);
  padding: var(--space-md);
  margin-top: var(--space-md);
}

/* The slash marks the letterhead decorative for screen readers
   (the report announces itself by moving focus to the verdict);
   engines without the syntax drop the line and keep the sheet.
   Same string as the print provenance in section 24. */
.tool-memo:not(:empty)::before {
  content: 'Raleigh Resume · raleighresume.com' / '';
  display: block;
  font-family: var(--font-display);
  font-size: var(--fs-small);
  letter-spacing: 0.056em;
  text-transform: uppercase;
  color: var(--ink-cool);
  border-bottom: 1px solid var(--border-subtle);
  padding-bottom: var(--space-xs);
  margin-bottom: var(--space-sm);
}

.tool-memo > :first-child { margin-top: 0; }

/* The verdict takes the document's ink; the finding statuses keep
   the accent, the editor's pen marking the document. */
.tool-memo .resume-check__verdict { color: var(--ink-cool); }

/* The memo's sign-off (2026-08-22): the promise the page makes above
   the box, restated at the foot of every delivered sheet. Muted small
   voice over the letterhead's hairline, closing the frame the
   provenance line opens. The five page-local tool scripts append it
   as .tool-memo__foot on every successful report. */
.tool-memo__foot {
  margin: var(--space-sm) 0 0;
  padding-top: var(--space-xs);
  border-top: 1px solid var(--border-subtle);
  font-size: var(--fs-small);
  color: var(--text-muted);
}

@media print {
  /* Print keeps its own provenance line and layout (section 24);
     the sheet flattens so the printed report is unchanged. The
     animation guard is section 24's .stagger-item reasoning: a
     print clone must never capture the sheet at from-state
     opacity. */
  .tool-memo:not(:empty) {
    border: 0;
    padding: 0;
    margin-top: 0;
    animation: none !important;
  }
  .tool-memo:not(:empty)::before { content: none; }
}

/* The end mark: the firm's mark closes each long-form article,
   the magazine convention for "this piece is over". A standalone
   block after the prose container, so rewording the article
   never touches it. 18px is the wordmark mark's size. (The R
   monogram tile since 2026-08-21; the acorn before it.) */
.article-end {
  margin: var(--space-lg) 0 0;
}
.article-end svg {
  display: block;
  width: 18px;
  height: 18px;
  color: var(--accent-primary);
}

/* The watermark: the R monogram at watermark strength (the crest
   was retired with the acorn system, 2026-08-21), on the
   low-stakes sheets only (404, thanks, intake-received), the
   papermaker's device on the pages with room to carry it. Ink,
   never accent; desktop only, where the hero's right side is
   empty; hidden where forced colors would print it solid. 272
   square is a space rung (8 x 34). */
.hero--watermark .container { position: relative; }
.watermark { display: none; }
@media (min-width: 1024px) {
  .watermark {
    display: block;
    position: absolute;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 272px;
    height: 272px;
    color: var(--text-primary);
    opacity: 0.06;
    pointer-events: none;
  }
}
@media (forced-colors: active) {
  .watermark { display: none; }
}

/* The drop cap (2026-08-21): each long-form article opens with a
   three-line Fraunces initial, the book convention for "the essay
   starts here". Scoped by the byline only the article pages carry,
   and a pseudo-element, so it costs nothing anywhere else and
   survives any rewording: the cap re-applies to whatever the new
   first letter is. Ink, not accent: the pen annotates, the book
   opens. Engines without initial-letter (Firefox) get a two-line
   float cut instead, sized to Fraunces' 0.7em cap height, which
   is safer against metric drift than faking three. */
body:has(.page-header__byline) .prose > p:first-child::first-letter {
  font-family: var(--font-display);
  font-weight: 500;
  color: var(--text-primary);
}
@supports (initial-letter: 3) or (-webkit-initial-letter: 3) {
  body:has(.page-header__byline) .prose > p:first-child::first-letter {
    -webkit-initial-letter: 3;
    initial-letter: 3;
    margin-right: 16px;
  }
}
@supports not ((initial-letter: 3) or (-webkit-initial-letter: 3)) {
  body:has(.page-header__byline) .prose > p:first-child::first-letter {
    float: left;
    font-size: 3.4em;
    line-height: 0.75;
    margin: 0.05em 16px 0 0;
  }
}

/* Hanging punctuation: the testimonial's opening quotation mark
   hangs outside the measure, so the first word of every quote
   sits on the column edge with the text below it. Engines with
   the property get the true hang; the indent is the fallback,
   sized to Fraunces italic's open quote by eye. */
.quote__text,
.testimonial-panel__quote {
  text-indent: -0.45em;
}
@supports (hanging-punctuation: first) {
  .quote__text,
  .testimonial-panel__quote {
    text-indent: 0;
    hanging-punctuation: first;
  }
}

/* The drawn underline: a standalone action link underlines
   itself left to right on hover, the pen drawn under the phrase.
   Replaces the card-link's translateX nudge (section 22 note).
   Section 23's kill switch zeroes the transition, so reduced
   motion gets the line without the draw. */
.card-link { position: relative; }
.card-link::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -3px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--transition);
}
.card-link:hover::after,
.card-link:focus-visible::after {
  transform: scaleX(1);
}

/* The acknowledgment letter (thanks, intake-received; added
   2026-08-21): the page's message set on the firm's stationery,
   the bio panel's sheet grammar at page scale. The letterhead
   rule replaces the hero's accent rule; the heading speaks at
   section size, a letter's voice rather than a hero's; a hairline
   closes the letter above the actions, as on the bio panels; the
   watermark moves inside the sheet, in the paper. Structural
   only: the copy is untouched. 404 keeps the plain hero (its
   letter treatment is a future copy decision). */
.letter {
  position: relative;
  max-width: 880px;
  background: var(--bg-secondary);
  border: 1px solid var(--border-medium);
  border-top: 2px solid var(--accent-primary);
  padding: var(--space-md);
}
@media (min-width: 640px) {
  .letter { padding: var(--space-lg); }
}
@media (min-width: 1024px) {
  .letter { padding: 64px; } /* space rung */
}
.letter .hero__headline {
  font-size: var(--fs-section);
  margin-bottom: var(--space-md);
}
.letter .hero__subhead {
  font-size: var(--fs-body);
  line-height: 1.618;
  color: var(--text-primary);
  margin-bottom: var(--space-md);
}
.letter__closing {
  margin-top: var(--space-lg);
  border-top: 1px solid var(--border-subtle);
  padding-top: var(--space-md);
}
.letter__closing .btn-row { margin-top: 0; }
.letter .watermark { right: var(--space-lg); }

/* The engagement record (work; added 2026-08-21): each case
   study on the same sheet, its file label and meta block reading
   as the record's header, the outcome squared to the document
   grammar. Overrides section 18's hairline-separator rhythm:
   sheets separate themselves. */
.case-study {
  background: var(--bg-secondary);
  border: 1px solid var(--border-medium);
  border-top: 2px solid var(--accent-primary);
  padding: var(--space-md);
}
.case-study + .case-study {
  margin-top: var(--space-lg);
  padding-top: var(--space-md);
  border-top: 2px solid var(--accent-primary);
}
@media (min-width: 640px) {
  .case-study { padding: var(--space-lg); }
  .case-study + .case-study { padding-top: var(--space-lg); }
}
.case-study__outcome { border-radius: 0; }

/* The report arrives rather than appears: the memo sheet takes
   the results cascade's own rise (the result-rise keyframes live
   with .stagger-item), under the same motion gate. Only the
   empty-to-filled edge animates; a re-run empties and repopulates
   within one style pass, so the selector never unmatches and the
   animation does not restart. */
@media (prefers-reduced-motion: no-preference) {
  .tool-memo:not(:empty) {
    animation: result-rise 0.3s cubic-bezier(0.2, 0.7, 0.3, 1) both;
  }
}

/* The journal furniture (2026-08-21): the articles hub set as a
   contents page with dot leaders, the running head over a long
   read, hyphenation on the article measure, and articles that
   print as typeset essays. */

/* The contents page: each entry a line of the classic shape,
   title, leader dots, author, with the lede beneath. Leaders are
   a dotted border, never a gradient. Where engines know last
   baseline, a turnover title keeps the leader on its last line;
   elsewhere first baseline stands. */
.toc {
  list-style: none;
  padding: 0;
  margin: 0;
}
.toc__entry + .toc__entry { margin-top: var(--space-md); }
.toc__row {
  display: flex;
  align-items: baseline;
  align-items: last baseline;
  gap: 16px;
  color: var(--text-primary);
}
/* The link wraps the title alone (2026-09-08): the leader and the
   author sit beside it, so the link's name is the title. */
.toc__link {
  display: block;
  min-width: 0;
  color: inherit;
  text-decoration: none;
}
.toc__title {
  font-family: var(--font-display);
  font-size: var(--fs-subsection);
  font-weight: 500;
  letter-spacing: -0.013em;
  line-height: 1.272;
  min-width: 0;
}
.toc__link:hover .toc__title,
.toc__link:focus-visible .toc__title { color: var(--accent-primary); }
.toc__leader {
  flex: 1;
  min-width: 48px;
  border-bottom: 1px dotted var(--border-input);
  position: relative;
  top: -5px;
}
.toc__author {
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--fs-small);
  color: var(--text-muted);
  white-space: nowrap;
}
.toc__lede {
  margin: var(--space-xs) 0 0;
  color: var(--text-muted);
  font-size: var(--fs-small);
  max-width: var(--width-prose);
}
@media (max-width: 639.98px) {
  .toc__row { flex-wrap: wrap; gap: 4px 16px; }
  .toc__leader { display: none; }
  .toc__author { flex-basis: 100%; }
}

/* The running head: past the headline, the piece's title rides a
   slim strip under the site header, the journal's convention for
   "you are deep in this piece". Fixed and decorative (aria-hidden
   markup, no pointer), fading with the page-header's exit through
   a named view timeline; engines without scroll timelines, and
   reduced motion, keep the plain header. Desktop only: phones
   have no room to spend on furniture. 72 is the header's signed
   height; z-index sits under the header's 50. */
.running-head { display: none; }
@media (min-width: 1024px) and (prefers-reduced-motion: no-preference) {
  @supports (timeline-scope: --a) {
    body:has(.running-head) { timeline-scope: --article-title; }
    body:has(.running-head) .page-header { view-timeline: --article-title block; }
    .running-head {
      display: block;
      position: fixed;
      top: 72px;
      left: 0;
      right: 0;
      z-index: 40;
      background: var(--bg-primary);
      border-bottom: 1px solid var(--border-subtle);
      padding: var(--space-xs) var(--container-padding);
      pointer-events: none;
      opacity: 0;
      animation: running-head-in linear both;
      animation-timeline: --article-title;
      animation-range: exit 0% exit 100%;
    }
    .running-head span {
      display: block;
      max-width: var(--width-max);
      margin-inline: auto;
      font-family: var(--font-display);
      font-style: italic;
      font-size: var(--fs-small);
      color: var(--text-muted);
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    @keyframes running-head-in {
      to { opacity: 1; }
    }
  }
}

/* Hyphenation: the article measure reads with a book's edge; the
   browser breaks against the page's lang="en". Byline scope, the
   same scope as the 680 reading measure. */
body:has(.page-header__byline) .prose {
  -webkit-hyphens: auto;
  hyphens: auto;
}

@media print {
  /* The printed essay: an article prints as title, byline, drop
     cap, prose and end mark under the same provenance line the
     tool reports carry (section 24's pattern). The running head
     is fixed and would stamp every sheet; the author panel and
     its photo stay on screen. */
  .running-head { display: none !important; }
  body:has(.page-header__byline) section:has(.bio-panel) { display: none !important; }
  body:has(.page-header__byline) main::before {
    content: 'Raleigh Resume · raleighresume.com';
    display: block;
    font-family: var(--font-display);
    font-size: var(--fs-small);
    letter-spacing: 0.056em;
    text-transform: uppercase;
    color: var(--text-muted);
    padding: var(--space-md) var(--container-padding) 0;
  }
}

/* -----------------------------------------------------------
   28. The parse demonstration (resume-check.html, 2026-09-07)

   "See what the software sees": a dropped PDF's first page as
   pdf.js drew it, beside the text box's own lines in the order
   the software pulled them. The page is a sheet in section 26's
   grammar with a drawing canvas over it (rectangles only, no
   pointer); the lines are real text in a scroll box, one row per
   line, the row a keyboard target with aria-current, so the pane
   is the canvas's text alternative. The notes are section 25's
   pen: Fraunces italic in the accent, the meaning carried by the
   words ("read across, not down", "date separated from its role")
   and the gutter line only a supporting cue. Sizes are tokens,
   rungs, micro-gaps, or box geometry (widths and heights,
   unscanned by doctrine). The arrival is the results cascade's
   own rise (section 21), inert under reduced motion.
   ----------------------------------------------------------- */
.parse__panes {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-md);
  margin-top: var(--space-md);
}

@media (min-width: 1024px) {
  .parse__panes {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
}

.parse__pane {
  min-width: 0;
}

/* The section header carries its own interval; the verdict beneath
   it drops the top margin it takes inside a memo. */
.parse .resume-check__verdict {
  margin-top: 0;
}

/* The pane labels: the finding-status voice, small caps. */
.parse__label {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.056em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0 0 var(--space-xs);
}

/* The page as drawn: the sheet, the canvas filling its width at the
   page's own proportion, the overlay laid over it taking no pointer. */
.parse__page {
  position: relative;
  background: var(--bg-secondary);
  border: 1px solid var(--border-medium);
}

.parse__canvas,
.parse__overlay {
  display: block;
  width: 100%;
  height: auto;
}

.parse__overlay {
  position: absolute;
  inset: 0;
  height: 100%;
  pointer-events: none;
}

/* The fallback when the page will not paint: the hint inside the sheet. */
.parse__page > .resume-check__hint {
  padding: var(--space-md);
}

/* The caption under the list takes a breath above it; a hint carries
   no margin of its own. */
.parse__pane > .resume-check__hint {
  margin-top: var(--space-xs);
}

/* The lines the software read: a scroll box on the sheet, real text in
   the body face at the small size, tabular figures so dates align. The
   list scrolls inside itself; the page never scrolls sideways. */
.parse__runs {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 40rem;
  overflow: auto;
  background: var(--bg-secondary);
  border: 1px solid var(--border-medium);
  font-size: var(--fs-small);
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
}

@media (max-width: 1023.98px) {
  .parse__runs {
    max-height: 24rem;
  }
}

/* A row: the gutter line is transparent on every row and takes the
   accent on a marked one, so the text never shifts. 4px is a micro-gap. */
.parse__run {
  margin: 0;
  padding: 4px var(--space-sm);
  border-bottom: 1px solid var(--border-subtle);
  border-left: 3px solid transparent;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.parse__run:last-child {
  border-bottom: 0;
}

.parse__run--crossing,
.parse__run--date {
  border-left-color: var(--accent-primary);
}

.parse__run[aria-current="true"] {
  background: var(--accent-tint-soft);
}

.parse__run:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: -2px;
}

.parse__text {
  display: block;
}

/* The pen note under a marked line: section 25's note voice, on
   --bg-secondary or --accent-tint-soft only. */
.parse__mark {
  display: block;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  font-size: var(--fs-small);
  line-height: 1.4;
  color: var(--accent-primary);
  white-space: normal;
}

.parse__caret {
  font-style: normal;
}

/* The arrival: rows rise in extraction order over about a second, the
   overlay highlights fading in with them (drawn by the script on the
   same clock). Self-gated; without the class, or under reduced motion,
   every row is simply present. */
@media (prefers-reduced-motion: no-preference) {
  .parse__runs--arrive .parse__run {
    animation: result-rise 0.3s cubic-bezier(0.2, 0.7, 0.3, 1) both;
    animation-delay: calc(var(--arrive-i, 0) * var(--arrive-step, 20ms));
  }
}

@media print {
  /* The printed report keeps the whole list; the scroll box opens. */
  .parse__runs {
    max-height: none;
    overflow: visible;
  }
}

/* -----------------------------------------------------------
   30. The Red Pen (index.html hero, rewrite.html, services.html;
       2026-09-07, next-draft n5; the hero mark simplified to three
       lines 2026-09-08, next-draft n10_hero, by the operator's ruling)

   The accent as the editor's pen. On the hero the mark (.rp) is
   three lines in the firm's own outcome words, each one word struck
   and the firm's word written in after it: Noticed. Considered.
   Interviewed. Hired. Visible. Known. Nothing else sits with them:
   no note, no label, no hairline. The strike is drawn by the
   page-local redpen.js as an SVG path over the struck word and the
   replacement is written in after it. The rewrite card (.rw) keeps
   the earlier document idiom: a composite resume line struck
   through, its replacement written in beneath it in the editor's
   hand, the note, on the document sheet section 26 gave the bio
   panels and the case studies, with a toggle that reads the
   original alone. The mark's words are the display face at the
   section rung, the struck word on --text-muted, the written word
   Fraunces italic in the accent; the card's own ink is Inter on
   --ink-cool with tabular figures. The accent appears nowhere else
   new. Every size is a token or a ladder rung: 35 and 45 to 56 for
   the headline step. Resting state is the finished page: without
   the script the strike is a line-through in the accent and every
   word is present; reduced motion is the finished page at load.
   ----------------------------------------------------------- */

/* 30a. The mark sits under the buttons as one horizontal row at every
   width (2026-09-08, the operator's ruling after the deploy: the
   three-column hero squeezed the headline into a narrow middle track;
   the headline keeps its full measure and the mark reads across, not
   down). The three pairs are flex items with the 40 rung between them
   and the 8 rung between wrapped rows; on a phone they wrap one pair
   per row. The DOM order is headline, subhead, buttons, mark, link at
   every width. Scoped to the hero that carries the mark; no other
   page's hero or heading moves. */
.hero--specimen .rp {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-lg);
  margin-top: var(--space-lg);
}
.hero--specimen .hero__more {
  margin: var(--space-sm) 0 0;
  font-size: var(--fs-small);
}
/* Phones: the headline at the inner pages' own 35 floor and the hero's
   top padding on the 40 rung (the 2026-09-08 fold follow-up), so the
   buttons sit inside a 390 by 844 first screen. */
@media (max-width: 639.98px) {
  .hero--specimen .hero__headline { font-size: 2.1875rem; margin-bottom: var(--space-sm); }
  .hero--specimen { padding-top: var(--space-lg); }
}

/* 30b. The mark's three pairs: the display face at the subsection rung
   (22) so the three sit on one row inside the body track at 1280; the struck word on --text-muted, the written
   word Fraunces italic in the accent, each an inline block so a word
   never breaks and a line wraps, if it must, only between the two.
   At rest every mark is present: the strike is a line-through in the
   accent, and only a line whose drawn mark exists (.rp__line--drawn,
   set by the script once the path is on the page) gives the
   line-through up. If the draw ever fails, the word is still struck. */
.rp__line {
  position: relative;
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-subsection);
  font-weight: 400;
  line-height: 1.15;
  color: var(--text-muted);
  text-wrap: pretty;
}
.rp__line:nth-child(2) { --i: 1; }
.rp__line:nth-child(3) { --i: 2; }
.rp del {
  display: inline-block;
  text-decoration: line-through;
  text-decoration-color: var(--accent-primary);
  text-decoration-thickness: from-font;
}
.rp__line--drawn del { text-decoration: none; }
.rp ins {
  display: inline-block;
  font-style: italic;
  color: var(--accent-primary);
  text-decoration: none;
}

.rp__strike {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}
.rp__strike path {
  fill: none;
  stroke: var(--accent-primary);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

/* 30c. The card's lines (rewrite.html, services.html): the document's
   own ink, Inter on --ink-cool with tabular figures, 15 then 17 from
   1024; the written line Fraunces italic in the accent; the strike a
   line-through in the accent. */
.rw__line {
  position: relative;
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--fs-small);
  line-height: 1.4;
  color: var(--ink-cool);
  font-variant-numeric: tabular-nums;
  text-wrap: pretty;
}
.rw__line + .rw__line { margin-top: var(--space-xs); }
@media (min-width: 1024px) {
  .rw__line { font-size: var(--fs-body); }
}
.rw__line--after {
  font-family: var(--font-display);
  font-style: italic;
  color: var(--accent-primary);
}
.rw del {
  text-decoration: line-through;
  text-decoration-color: var(--accent-primary);
  text-decoration-thickness: from-font;
}
.rw ins { text-decoration: none; }

/* 30d. The signature moment, retimed 2026-09-08 by the operator's
   ruling (the first cut ran the three strikes 250 ms apart and was
   over before it could be read): the page settles for --rp-start,
   then the pairs go one at a time, left to right. Each pair is a
   strike (--rp-strike, stroke-dashoffset, the site's own ease-out),
   then its replacement written in left to right (--rp-write, a clip,
   never opacity), then a rest (--rp-rest) before the next strike
   begins. Nothing overlaps: the second strike waits for the first
   word to finish writing. Gated on motion being welcome and on the
   script's .rp--go, so the resting stylesheet state is the finished
   page, and reduced motion is the finished page at load.
   pathLength="1" normalizes each path; the dash is twice that because
   Chromium's normalized length runs a few percent under the rendered
   one and a dash of exactly 1 stops short of the last letters. The
   order is the line's own --i (30b), by document order; the script
   sets no timing. One pass: a redraw removes .rp--go first. */
@media (prefers-reduced-motion: no-preference) {
  .rp--go {
    --rp-start: 400ms;
    --rp-strike: 450ms;
    --rp-write: 350ms;
    --rp-rest: 200ms;
    --rp-period: calc(var(--rp-strike) + var(--rp-write) + var(--rp-rest));
  }
  .rp--go .rp__strike path {
    stroke-dasharray: 2 2;
    stroke-dashoffset: 2;
    animation: rp-write var(--rp-strike) cubic-bezier(0.2, 0.7, 0.3, 1) both;
    animation-delay: calc(var(--rp-start) + var(--i, 0) * var(--rp-period));
  }
  .rp--go ins {
    clip-path: inset(0 100% 0 0);
    animation: rp-reveal var(--rp-write) cubic-bezier(0.2, 0.7, 0.3, 1) both;
    animation-delay: calc(var(--rp-start) + var(--i, 0) * var(--rp-period) + var(--rp-strike));
  }
  @keyframes rp-write { to { stroke-dashoffset: 0; } }
  @keyframes rp-reveal { to { clip-path: inset(0 0 0 0); } }
}

/* 30e. The rewrite card: one composite pair on the document sheet
   (section 26's square-cornered white, the hairline frame, the accent
   letterhead rule), the sector in the eyebrow's italic voice, the note
   beneath the pair, and a small line naming the Bullet Meter check the
   original would have failed. The toggle at the foot reads the original
   alone (aria-pressed true hides the written line and lifts the strike,
   so the visitor sees the bullet as it arrived); the resting state, the
   print state and the no-script state all show both lines. */
.rw-grid {
  display: grid;
  gap: var(--space-md);
}
@media (min-width: 1024px) {
  .rw-grid { grid-template-columns: 1fr 1fr; }
}
.rw {
  display: flex;
  flex-direction: column;
  background: var(--bg-secondary);
  border: 1px solid var(--border-medium);
  border-top: 2px solid var(--accent-primary);
  padding: var(--space-md);
}
.rw__sector {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  font-size: var(--fs-body);
  letter-spacing: 0;
  line-height: 1.4;
  color: var(--text-muted);
  margin: 0 0 var(--space-sm);
}
.rw__note { margin: var(--space-sm) 0 0; }
.rw__check {
  margin: var(--space-xs) 0 0;
  font-size: var(--fs-small);
  color: var(--text-muted);
}
.rw__check strong {
  font-weight: 500;
  color: var(--text-primary);
}
/* The toggle rides .tool-example-btn's underlined text-button voice
   (section 21) and pins to the sheet's foot, so the twelve cards in a
   row end on the same line. 44px tall, the 2.5.8 target the section 21
   button never needed because it sits in running text. */
.rw__toggle {
  margin-top: auto;
  padding: var(--space-sm) 0 0;
  min-height: 44px;
  align-self: flex-start;
}
.rw--original .rw__line--after { display: none; }
.rw--original del { text-decoration: none; }
/* Returning to the rewrite writes the line in again, once, under the
   motion gate; the script adds .rw--play for the duration and removes it
   on animationend, so nothing is ever parked mid-animation. Reduced motion
   never sees the class (the script checks the same query). */
@media (prefers-reduced-motion: no-preference) {
  .rw--play .rw__line--after ins {
    display: block;
    clip-path: inset(0 100% 0 0);
    animation: rp-reveal 800ms cubic-bezier(0.2, 0.7, 0.3, 1) both;
  }
}

@media print {
  /* Paper shows both states: the toggle goes, a card left on the
     original comes back to the pair, and the sheet flattens the way
     the memo does. */
  .rw__toggle { display: none !important; }
  .rw--original .rw__line--after { display: block; }
  .rw--original del { text-decoration: line-through; }
  .rw { border-left: 0; border-right: 0; border-bottom: 0; padding-inline: 0; }
  .rp--go .rp__strike path,
  .rp--go ins { animation: none !important; clip-path: none; stroke-dashoffset: 0; }
}


/* 31 is retired: it styled a feature the operator's 2026-09-08
   ruling took off the five tool pages (see that day's log
   entry). The number stays vacant so the log entries that cite a
   section by number keep pointing at what they described. */

/* -----------------------------------------------------------
   32. Inline booking (thanks.html, intake-received.html;
       2026-09-07, next-draft n7_plumb)

   The two thank-you pages end with the booking page itself,
   framed in the document vocabulary: the memo sheet's hairline
   and radius around a plain iframe on the paper white. The
   frame is an enhancement and never the only route. The link
   beneath it goes to the same address, and it is what a
   visitor reaches whenever cal.com declines to be framed, or
   the frame is blocked, or the visitor would rather have a
   tab of their own. Spacing is tokens; the two frame heights
   are lengths, which the proportion ladder does not govern
   (it sweeps type, tracking, and the box's own spacing).
   ----------------------------------------------------------- */

.booking {
  max-width: var(--width-prose);
  margin-inline: auto;
}

.booking__lead {
  margin-bottom: var(--space-md);
}

.booking__frame {
  border: 1px solid var(--border-medium);
  border-radius: var(--radius-md);
  background: var(--bg-secondary);
  overflow: hidden;
}

/* A booking grid needs height before it needs width, and one height
   serves both layouts: 980px holds the event details and a whole month
   on the wide view, and the same month plus the first of the day's
   times where the narrow view stacks them. Past that the frame scrolls
   on its own, the way every booking embed does. */
.booking__frame iframe {
  display: block;
  width: 100%;
  height: 980px;
  border: 0;
}

.booking__link {
  margin-top: var(--space-sm);
}

/* ==========================================================================
   32. The third bug sweep's low fixes (2026-09-13)
   Each rule names its finding. Print rules sit in their own block so
   section 24's order is untouched.
   ========================================================================== */

/* X-SEC-06: the SVG sprite's one inline style, which tripped style-src
   'self' on every page, is this rule instead. */
.sprite {
  position: absolute;
  width: 0;
  height: 0;
}

/* X-RCI-05: a disabled control reads as disabled everywhere, the honestly
   dead form (COMPAT-L1-09) and every busy beat included. */
.btn-primary:disabled,
.btn-secondary:disabled,
.tool-example-btn:disabled,
.tool-print-btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* X-BM2-10: the meter's "Set aside:" label was styled by nothing. */
.resume-check__aside-label {
  font-weight: 600;
}

/* X-MOB-08: at the narrowest widths the finding rows settle one way, the
   status under the name on every row. */
@media (max-width: 389.98px) {
  .finding__head .finding__name {
    flex: 1 1 100%;
  }
}

/* X-MOB-10: a landscape phone fits the whole mobile menu. */
@media (max-height: 460px) {
  .mobile-menu__link {
    font-size: 1.375rem;
    padding: 6px 0;
  }
  .mobile-menu__list {
    gap: var(--space-sm);
  }
}

/* X-A11Y-09: the parse pane's current row carries a contrast cue that does
   not depend on focus. */
.parse__run[aria-selected="true"] {
  box-shadow: inset 3px 0 0 var(--accent-primary);
}

/* X-DEG-05: in a forced-colors mode the hero mark keeps the stylesheet's
   resting strike, whose colour the mode forces. */
@media (forced-colors: active) {
  .rp__line--drawn del {
    text-decoration: line-through;
  }
  .rp__strike {
    display: none;
  }
}

@media print {
  /* X-PRINT-09, X-PRINT-13: the page box and its margins are the sheet's,
     not the dialog's, and the last section adds no blank trailing sheet. */
  @page {
    size: letter;
    margin: 0.65in 0.85in;
  }
  main > section:last-of-type {
    padding-bottom: 0;
  }

  /* X-PRINT-01, X-PRINT-02, X-RW-04: a row, a group, a card, a pane and a
     Rewrite card never split across a sheet; a heading never ends one. */
  .finding,
  .kw-group,
  .post-card,
  .parse__pane,
  .rw,
  .tool-memo__foot {
    break-inside: avoid;
    page-break-inside: avoid;
  }
  .finding__head,
  .kw-group__title,
  .section-header,
  .parse__label,
  .rw__sector {
    break-after: avoid;
    page-break-after: avoid;
  }
  .parse__page {
    max-height: 7in;
  }
  .parse__canvas {
    object-fit: contain;
  }

  /* X-PRINT-03: no focus ring boxes the verdict of a keyboard-run report. */
  *:focus,
  *:focus-visible {
    outline: none !important;
    box-shadow: none !important;
  }

  /* X-PRINT-06: the parse demonstration's arrival never prints blank. */
  .parse__runs--arrive .parse__run,
  .parse__run,
  .parse__overlay {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }

  /* X-RW-05: a card printed mid-motion prints its rewrite whole. */
  .rw--play .rw__line--after ins {
    animation: none !important;
    clip-path: none !important;
  }

  /* X-PRINT-11: a collapsed FAQ prints its answers. */
  .faq details > *:not(summary) {
    display: block !important;
  }
  .faq details::details-content {
    display: block !important;
    content-visibility: visible !important;
    block-size: auto !important;
    overflow: visible !important;
  }
}
