CSS reference

CSS reference

Every rule in the source stylesheet, grouped by the section and sub-section comments it sits under, with the comment above it as its note.

Generated from src/input.css on every build. The source has 4793 lines, 1073 rules and 73 custom properties. Tailwind compiles it to assets/css/site.css, and to assets/css/style-guide.css for the style guide and this page.

To change this page, edit the comments in src/input.css: /* ==== */ banners open a section, /* --- Title: text --- */ opens a sub-section, and a comment directly above a rule becomes its note.

Custom properties

73 custom properties on :root. Dark is the value from :root[data-theme="dark"] and the prefers-color-scheme block (listed twice only where the two disagree).

TokenLightDarkElsewhere · line
Palette — identity, never changes
--navy#0F1E4A43
--sky#4FACFE44
--cyan#00F2FE45
--seagrass#0BA67846
--coral#FE1E1A47
--lantern#E07A0048
--sand#FFC93C49
--midnight#0A0F2450
--paper#F5FBFF51
Semantic — light
--bg#FEFEFE#0A0F24@media print › :root:root:root: #FFFFFF 54
--surface#FDFEFF#101A3A@media print › :root:root:root: #FFFFFF 55
--ink#0F1E4A#EAF0FF@media print › :root:root:root: #0F1E4A 56
--ink-soft#3C4A73#BCCCF0@media print › :root:root:root: #3C4A73 57
--line#DDE4F2#25315C@media print › :root:root:root: #C9D3E6 58
--line-soft#EAEFF9#1B274A@media print › :root:root:root: #DDE4F2 59
--tag-bg#EAF3FF#16234A@media print › :root:root:root: #FFFFFF 60
--warn-bg#EDF4FE#241C0C@media print › :root:root:root: #FFFFFF 61
--warn-line#C3D8F2#4A3A14@media print › :root:root:root: #A9B8D4 62
--warn-ink#2F4468#E8C878@media print › :root:root:root: #2F4468 63
Type — the label face as a custom property, for the handful of rules that need the family from a var() rather than theme(). Resolved from the Tailwind theme so tailwind.config.js stays the single source of truth for the stack.
--font-sanstheme('fontFamily.label')68
Inline link color — a darkened Sky that clears WCAG AA (6.5:1) on the light ground; nav / buttons / footer set their own color and are unaffected.
--link#0B5FA5var(--sky)@media print › :root:root:root: #0B5FA5 72
Accent-as-ink: darker palette steps for when an accent carries text or a focus ring on the light ground (raw Sky is 2.4:1, Lantern 3.0:1). Each clears 4.3:1 on its own 12% wash. Dark theme points them back at the palette.
--sky-ink#1B6AB4var(--sky)@media print › :root:root:root: #1F74C4 77
--seagrass-ink#087A58#1FC493@media print › :root:root:root: #087A58 78
--lantern-ink#A85A00var(--lantern)@media print › :root:root:root: #A85A00 79
--coral-ink#C4140F#FF6A66@media print › :root:root:root: #C4140F 80
--ba-after-bgvar(--seagrass-ink)var(--seagrass)@media print › :root:root:root: var(--seagrass-ink) 81
--ba-after-ink#FFFFFFvar(--midnight)@media print › :root:root:root: #FFFFFF 82
Sticky header height: logo + py-4 (2rem) + 1px border. One source for scroll-padding and every sticky offset; logo height is set from it below.
--logo-h3.25rem@media (min-width: 480px) › :root: 5.46875rem
@media (min-width: 1024px) › :root: 6.25rem 86
--header-hcalc(var(--logo-h) + 2rem + 1px)@media screen › :root[data-text]: calc((var(--logo-h) + 2rem) / var(--text-scale) + 1px) 87
--ringvar(--navy)var(--sky)@media print › :root:root:root: #0F1E4A 89
--focus-on-darkvar(--sky)90
Component roles. Every light/dark difference a component has is a token here, so the two dark blocks below are the only place dark is defined.
--brand-markurl('../logomarks/marks/advocate-beacon-primary.svg')url('../logomarks/marks/advocate-beacon-dark.svg')@media print › :root:root:root: url('../logomarks/marks/advocate-beacon-primary.svg') 94
--brand-inkvar(--navy)#EAF0FF@media print › :root:root:root: var(--navy) 95
--brand-subvar(--sky-ink)#6FC3FF@media print › :root:root:root: var(--sky-ink) 96
Sky itself is 2.4:1 on white
--brand-foot-markurl('../logomarks/marks/advocate-beacon-deep-tone-alt.svg')url('../logomarks/marks/advocate-beacon-reversed.svg')97
--brand-foot-inkvar(--navy)var(--paper)98
--brand-foot-sub#1E2B5Avar(--cyan)99
#2E3F72 was 4.18:1 on the Sky footer
--cta-bgvar(--navy)var(--sky)100
.btn-primary
--cta-inkvar(--paper)var(--navy)101
--cta-hover-bgvar(--midnight)var(--sky)102
--cta-hover-filternonebrightness(1.08)103
--accent-hover-bgvar(--navy)var(--paper)104
Sky-at-rest controls: path CTAs, drawer tab
--accent-hover-inkvar(--paper)var(--navy)105
--nav-currentvar(--navy)var(--sky)106
--path-photo-opacity10.5107
--choice-accentvar(--sky)108
--theme-icon-lightblocknone109
--theme-icon-darknoneblock110
Site footer: a Sky ground with Navy ink in light (Navy 6.6:1, #20305C 5.3:1 on Sky); Midnight with Paper in dark.
--footer-bgvar(--sky)var(--midnight)114
--footer-inkvar(--navy)var(--paper)115
--footer-dim#20305Ccolor-mix(in srgb, var(--paper) 74%, transparent)116
--footer-title#20305Ccolor-mix(in srgb, var(--paper) 80%, transparent)117
--footer-linkvar(--navy)var(--sky)118
--footer-accentvar(--navy)var(--sky)119
--footer-accent-inkvar(--paper)var(--navy)120
--footer-sw-off#20305Ccolor-mix(in srgb, var(--paper) 62%, transparent)121
--footer-edgetransparentvar(--line)122
Contact drawer + mobile nav ground. In light these resolve to the page's own tokens (computed here at :root, so re-pointing them in scope is not circular); dark inverts both panels to a Sky ground (Navy on Sky ≈ 6.6:1).
--panel-bgvar(--bg)var(--sky)127
--panel-surfacevar(--surface)#EAF3FF128
--panel-inkvar(--ink)var(--navy)129
--panel-ink-softvar(--ink-soft)#20305C130
--panel-linevar(--line)#2E3F72131
--panel-line-softvar(--line-soft)#A9D6FF132
--panel-tag-bgvar(--tag-bg)#CFE6FF133
--panel-linkvar(--link)var(--navy)134
--panel-ringvar(--ring)var(--navy)135
--panel-accentvar(--choice-accent)var(--navy)136
--panel-cta-bgvar(--cta-bg)var(--navy)137
--panel-cta-inkvar(--cta-ink)var(--paper)138
--panel-cta-hover-bgvar(--cta-hover-bg)var(--midnight)139
--panel-cta-hover-filtervar(--cta-hover-filter)none140
Set only outside the light :root block.
--text-scale@media screen › :root[data-text="lg"]: 1.125
@media screen › :root[data-text="xl"]: 1.25
@media screen › :root[data-text="xxl"]: 1.5 297
--text-unscale@media screen › :root[data-text="lg"]: 0.8889
@media screen › :root[data-text="xl"]: 0.8
@media screen › :root[data-text="xxl"]: 0.6667 297

Tailwind theme

The utilities in the HTML come from these keys in tailwind.config.js (everything except screens extends Tailwind's defaults). Content scanned: ./site/*.html, ./src/partials/*.html, ./src/js/**/*.js.

Breakpoints screens · sm: md: nav: lg: xl: prefixes

sm480px
md768px
nav930px
lg1024px
xl1200px

Colors colors · bg-* text-* border-*

navy#0F1E4A
sky#4FACFE
cyan#00F2FE
coral#FE1E1A
midnight#0A0F24
paper#F5FBFF
surfacevar(--surface)
inkvar(--ink)
ink-softvar(--ink-soft)
linevar(--line)
line-softvar(--line-soft)

Font families fontFamily · font-*

logo"Tenor Sans", ui-sans-serif, "Gill Sans", "Trebuchet MS", system-ui, sans-serif
labelQuestrial, ui-sans-serif, "Century Gothic", "Avenir Next", system-ui, sans-serif
bodyYsabeau, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif
mono"IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace

Font sizes fontSize · text-*

lede1.3125rem · line-height 1.45
h41.4375rem · line-height 1.3
h3clamp(1.4375rem, 1.15rem + 1.2vw, 1.875rem) · line-height 1.25
h2clamp(1.875rem, 1.4rem + 1.9vw, 2.5rem) · line-height 1.18
h1clamp(2.25rem, 1.4rem + 3.4vw, 3.625rem) · line-height 1.08

Max widths maxWidth · max-w-*

canvas1000px
measure62ch
prose68ch

Gaps gap · gap-*

gutter24px

Radii borderRadius · rounded-*

card12px
tile10px

Shadows boxShadow · shadow-*

card0 1px 2px rgba(10, 15, 36, 0.06), 0 4px 12px rgba(10, 15, 36, 0.06)
lift0 2px 8px rgba(10, 15, 36, 0.08), 0 12px 28px rgba(10, 15, 36, 0.10)

Easing transitionTimingFunction · ease-*

calmcubic-bezier(0.22, 1, 0.36, 1)

Preamble

Coastal Healthcare Advocates — source stylesheet

Build: npm run build (watch while developing: npm run dev)

Output: site/assets/css/site.css — every page except the style guide

site/assets/css/style-guide.css — style-guide.html and the generated CSS reference

Both are git-ignored, generated by scripts/build-css.js from this one file and the

same tailwind.config.js; only the content scanned for used classes differs, which

keeps the style guide's .sg-* reference classes out of the stylesheet visitors load.

5 rules · starts at 1

Self-hosted fonts (latin subset) #

Kept outside @layer so they pass through as-is.

SelectorNoteLine
@font-faceIBM Plex Mono 40015
@font-faceIBM Plex Mono 50016
@font-faceQuestrial 40017
@font-faceTenor Sans 40018
@font-faceYsabeau 400 60019

Base

49 rules · starts at 33

Tokens #

Verbatim from the Coastal Brand Guidelines artifact (§01, §07)

SelectorNoteLine
:root, .sg-theme-light.sg-theme-light rides along so the style guide's light panel can show the light palette while the page itself is dark. Custom properties inherit from the nearest defining ancestor, so the panel's own declarations win inside it.41
:root:not([data-theme="light"])@media (prefers-color-scheme: dark)Dark: explicit toggle wins in both directions; system pref applies when unset.146
:root[data-theme="dark"], .sg-theme-darkSame values as the media block above — keep the two in lockstep. .sg-theme-dark is the style guide's dark panel; it takes these values rather than repeating them, so dark is still defined in only two places.210
:root@media (min-width: 480px)271
:root@media (min-width: 1024px)272
*274
*, ::before, ::afterKeep even Tailwind's Preflight defaults on the style-guide palette.276
::placeholder277
html279
:root[data-text]@media screenText size (Site preferences → data-text on <html>, set by theme.js). Scales the root, so every rem-sized thing grows with the text. At Larger and Largest (xl / xxl) the page also keeps its phone layout and hamburger menu at every width (scripts/postcss/text-layout.js rewrites the width media queries); Default and Large keep the normal responsive layout. The header bar is zoomed back to its usual size, since a 1.5× logo would crowd it; the menu panel inside it is zoomed up again, since it's reading matter. Screen only: print sets its own root size.293
:root[data-text="lg"]@media screen297
:root[data-text="xl"]@media screen298
:root[data-text="xxl"]@media screen299
:root[data-text] .site-header@media screen300
:root[data-text] .site-header .canvas-wide@media screen301
:root[data-text] #mobile-nav@media screen302
:root[data-text] .site-header .header-icon-btn@media screenThe header's round buttons too: a zoomed-down 44px would miss the touch-target minimum, and their icons are read at the chosen size.305
:root[data-text] .brand-lockup-footer@media screenThe footer lockup is a fixed-geometry logo, not text to read.307
:root[data-text] :is(a, code)@media screenLong addresses and paths can't wrap at a space.309
:root[data-text] :is(h1, h2, h3, h4)@media screenSo can a single long word in a big heading ("Responsibility") or a button ("Consultation") on a phone, and the style guide's type specimens.312
:root[data-text] .btn@media screenA button's label is an anonymous flex item, which only shrinks below its longest word with anywhere.315
:root[data-text] .sg-specimen > *@media screen316
:root[data-text] .fineprint-doc@media screen and (max-width: 599px)Phones and small tablets: the sample statement's three-column table can't fit scaled type, so the mock document keeps its usual size there.321
body324
h1, h2, h3, h4§04 Heading Sizes. All headings = Questrial 400 (matches H1).336
h1Each step is at least 1.25x the one below (body 18px, H4 23px, H3 23-30px, H2 30-40px, H1 36-58px), so size alone separates the roles; leading tightens as the size grows.347
h2352
h3353
h4354
.section h2[id$="-h"]::after, .section--tight h2[id$="-h"]::afterSection headings carry a short accent rule beneath them — left-aligned, 8rem, in Sky. Hidden on the centered navy Schedule heading; re-centered under the centered Intake heading.359
#schedule-h::after369
.text-center > h2[id$="-h"]::after370
p372
aInline / prose links read as links: distinct color + underline. Components that are links but shouldn't look like body links (.nav-link, .btn, .link-quiet, .site-footer a) all set their own color + text-decoration.377
a:hover384
.text-link-inlineA JS action (opens the Site preferences dialog) mentioned inline in prose, styled to match the surrounding text link exactly.387
.text-link-inline:hover396
.on-dark a:not([class])Classless links on a dark ground (e.g. the navy Schedule section) need the light-on-dark link color instead of the darkened Sky.399
a[href^="http"]:not([class]):not([href*="coastalhealthcareadvocates.org"])::after@media screenPlain text links that leave the site get a trailing "↗" (e.g. ada.gov). Classed links — buttons, icon circles, the GNA badge — carry their own icon, and absolute links to our own domain aren't external. Drawn as a mask so it takes the link color, in the sprite's 1.75px round stroke. Screen only: print swaps ::after for the URL in brackets.407
a[href^="http"]:not([class]):not([href*="coastalhealthcareadvocates.org"])::after@media screen and (prefers-reduced-motion: no-preference)420
a[href^="http"]:not([class]):not([href*="coastalhealthcareadvocates.org"]):hover::after@media screen and (prefers-reduced-motion: no-preference)423
abbr[title]Abbreviations: a quiet dotted underline in running text signals the title expansion. Inside links and headings it would stack on the link underline or clutter the type, so it's dropped there.431
:where(a, button, h1, h2, h3, h4) abbr[title]438
::selection440
:where(a, button, input, textarea, select, summary, [tabindex]):focus-visible442
.on-dark :where(a, button, input, textarea, select, summary, [tabindex]):focus-visibleOn dark grounds the navy ring vanishes — switch to sky.448
*, *::before, *::after@media (prefers-reduced-motion: reduce)Reduced motion: drop spatial movement and choreography, keep the short color/opacity feedback that tells a visitor an action registered.455
.hero-intro [data-rise], .hero-intro .hero-atmos::before@media (prefers-reduced-motion: reduce)464
html@media (prefers-reduced-motion: no-preference)472

Components

887 rules · starts at 476

General #

SelectorNoteLine
.canvas480
.canvas-wideHeader / footer breathe a little wider than the 1000px reading column.487
.canvas, .canvas-wide@media (min-width: 480px)494
.sectionpadding-block only — the hero puts .section and .canvas on one element, and a padding: X 0 shorthand there would wipe .canvas's padding-inline.499
.section--tight500
.lede§Type: .lede — Ysabeau 21px / 1.45, --ink-soft, max 60ch503
.prose-measure510
.prose-measure > * + *511
.metaSmall supporting text. Body typeface — IBM Plex Mono is reserved for actual figures (see .num, .price-fee, .step-num).515
.numFigures: prices, counts, code values. IBM Plex Mono otherwise runs only in form error messages (.field-error, .error-summary). Phone numbers stay in the body face — Mono's full-width space and brackets read as gaps there.525
codeFile paths and token names have no spaces to wrap at; let them break anywhere rather than push a phone-width page sideways.532

Buttons #

SelectorNoteLine
.btn535
.btn:hoverA hair of lift on hover; the press (:active, below in source so it wins) dips back down. Reduced motion drops the transform via the global rule.559
.btn:active560
.btn-primaryPrimary CTA — Navy fill, Paper text on light. On dark the page ground is near-navy, so the CTA flips to Sky fill / Navy text to stay legible.564
.btn-primary:hover569
.btn-ghost575
.btn-ghost:hover580
.btn-on-dark582
.btn-on-dark:hover587
.btn-ghost-on-dark589
.btn-ghost-on-dark:hover594
.link-quiet599
.link-quiet:hover610
.link-quiet svgA trailing icon (the "→" on "Talk about referrals") nudges in its direction of travel on hover. Leading icons — the PDF marks — are :first-child, so :last-child leaves them alone.614
.link-quiet:hover svg:last-child615
.link-quiet svg.icon-back"← Go back" (secondary.js): a smaller leading arrow that nudges left, its direction of travel. Same specificity as the rule above, so it wins by order.618
.link-quiet:hover svg.icon-back619

Surfaces #

SelectorNoteLine
.card622
.tile628
.tile:hoverHover acknowledges the pointer with the same small lift the how-cards use; under reduced motion the global rule drops the transform and keeps the border + elevation, so the feedback still reads.639
:is(.card, .tile, .how-card-inner) :is(h2, h3, h4)Headings inside a card/tile drop the tracking for readability at small sizes.645
.callout648
.iconSizing only. Stroke vs. fill is set per-symbol in the sprite so the two solid brand glyphs (LinkedIn, Facebook) stay filled.656
.icon-lg657
.icon-pdf658

Wide content scrolls inside its own bordered container #

SelectorNoteLine
.table-wrap662

Pricing choices #

The four ways in, above the detailed table

SelectorNoteLine
.price-choices671
.price-choices@media (min-width: 40rem)677
.price-choice678
.price-choice h3687
.price-choice p688
.price-choice--startThe free call is where everyone should begin: tinted ground and a sky rule.690
.price-choice-kicker694
.price-choice .price-choice-kicker700
.price-choice-fee701
.price-choice-fee--free708
.price-choice-cta713

Pricing table #

SelectorNoteLine
.price-table716
.price-table th, .price-table td722
.price-table thead th728
.price-table thead th:last-child737
.price-col-name738
.price-col-fee739
.price-group741
.price-table tbody:first-of-type .price-group752
.price-table tbody td, .price-table tbody th[scope="row"]754
.price-table th[scope="row"]757
.price-table td763
.price-table .price-feefull-strength ink for older readers765
.price-table td.price-fee--free772
.price-unit778
.price-fee-label"Fee:" label for the stacked layout — a real, visually-shown span (not CSS content), hidden while the Fee column header is present.786
.table-wrap:has(.price-table)@media (max-width: 40rem)Below 40rem three columns can't fit, so each service stacks into its own block instead of forcing a horizontal scroll that hides the fee. The table elements carry explicit ARIA table roles in index.html, so display:block here doesn't strip the row/header relationships from assistive tech.793
.price-table@media (max-width: 40rem)794
.price-table colgroup, .price-table thead@media (max-width: 40rem)795
.price-table tbody, .price-table tr, .price-table th, .price-table td@media (max-width: 40rem)797
.price-table tr:has(th[scope="row"])@media (max-width: 40rem)802
.price-table tr:has(th[scope="row"]) + tr:has(th[scope="row"])@media (max-width: 40rem)803
.price-table tbody th[scope="row"], .price-table tbody td@media (max-width: 40rem)806
.price-table td@media (max-width: 40rem)809
.price-table td.price-fee@media (max-width: 40rem)810
.price-fee-label@media (max-width: 40rem)814

Definition-list rows (bio credentials) #

SelectorNoteLine
.dl-rows825
.dl-rows > div826
.dl-rows > div:first-child832
.dl-rows > div@media (min-width: 768px)834
.dl-rows dt836
.dl-rows dd843

Header #

SelectorNoteLine
.site-header846
.site-header@supports not (backdrop-filter: blur(1px))857
.site-header.scrolledPicks up a soft elevation once the page has scrolled off the top, so the bar separates from the content passing under it (main.js toggles .scrolled). On the near-black dark ground this shadow is invisible — the border carries it there — which is fine.863
.site-header.header-hiddenvisibility (delayed to the end of the slide) takes the links out of the tab order, so keyboard focus can never land on an off-screen nav.867
.brand-lockupLogo — the horizontal lockup rebuilt with live type, so the company name is real text (reader mode, translation, copy, search) rather than an image. Only the mark is a picture, CSS-swapped by theme (no flash, no JS). Every length is in --u, one unit of the outlined SVG's 271.27 × 82 artboard, so the lockup scales from --logo-h and matches logo-horizontal-*.svg: mark at x 8, rule at x 79, wordmark at x 91.1 with baselines 40.22 and 61.22. --u multiplies lengths instead of setting a tiny em base, so a browser minimum-font-size preference can't blow the geometry up.884
.brand-lockup::beforeThe Advocate Beacon: the mark files are 120 × 132, drawn at half size.898
.brand-lockup::afterThe rule between mark and wordmark: wordmark ink at 28%.907
.brand-lockup-nameLine boxes are 1em tall; the offsets put each baseline where the SVG has it (Tenor Sans ascent .92 / descent .25; Ysabeau typo metrics 1.054 / .289).919
.brand-lockup-name > :first-child929
.brand-lockup-name > :last-child936
.brand-lockup-footerFooter colorway: deep-tone-alt on the light footer, reversed on the dark.944
.header-icon-btn951
.header-icon-btn:hover963
.header-icon-btn svg964
#nav-toggleThe hamburger wears the menu panel's tokens so it matches the panel it opens: page colors in light mode, a Sky fill with Navy lines in dark mode.967
#nav-toggle:hover972
#nav-toggle[aria-expanded="true"] svg973
#mobile-nav, #contact-modal:not(.is-lightbox)Mobile menu — a compact panel hung off the toggle button, not a full-width sheet. Its containing block is the .relative wrapper around the button, so it sits right under the hamburger. JS unhides it, then adds .is-open a frame later to run the entrance.979
#mobile-nav1002
#mobile-nav.is-open1023
#mobile-nav .nav-link1024
#mobile-nav .nav-link::after1030
#mobile-nav .nav-link:hover, #mobile-nav .nav-link:focus-visibleno underline-wipe inside the panel1031
#mobile-nav@media (prefers-reduced-motion: reduce)1034
#mobile-nav.is-open@media (prefers-reduced-motion: reduce)1035
nav[aria-label="Primary"]Primary desktop nav — four links (Services, About, How it works, Pricing), sized up from the mobile-panel links' 1rem to 1.125rem since dropping "Who we help" and FAQs left room to size up. It appears at the nav screen (tailwind.config.js); below that the hamburger takes over — re-measure that breakpoint if the links, their size, the icons or the logo change again.1044
nav[aria-label="Primary"] .nav-link1047
.theme-icon-light1051
.theme-icon-dark1052
.nav-link1054
.nav-link::afterunderline wipes in from the left on hover / keyboard focus1065
.nav-link:hover::after, .nav-link:focus-visible::after1077
.nav-link[aria-current="true"]Scrollspy — the link for the section currently under the viewport holds its underline and takes the accent color.1082
.nav-link[aria-current="true"]::after1083

Hero atmosphere #

Faint horizon + beacon glow, all decorative

SelectorNoteLine
.hero-atmos1100
.hero-atmos::before1105

Motion #

One authored moment: the hero steadies as the beacon comes up, once per session. Everything else is quiet feedback and continuity.

SelectorNoteLine
.js.hero-intro .hero-atmos::beforeFocal entrance (JS-gated; content is fully visible without it).1123
.js.hero-intro [data-rise]1126
[data-reveal]@media (prefers-reduced-motion: no-preference)Scroll-in: "coming into the light". Each [data-reveal] element plays once, on its own, as it crosses into view, so a tall section unfolds as the visitor reads down it rather than all at once at its top edge. Headings clear from a faint haze and their Beacon Sky rule sweeps out like a beam; the content under them follows. The value picks the treatment: (bare) the element rises in heading soft focus-in, then the accent rule draws out from its start list direct children rise in turn; their line icons settle after pair two side-by-side cards drift in from their own sides checks list items rise in turn and each tick draws itself left to right rows table rows fade up in turn photo fades in while the picture eases back from a slight zoom main.js arms .reveal-pending (the hidden start state), swaps in .reveal-in to play, then clears it; it also writes --reveal-i on staggered items. Fill mode is backwards, so once an animation ends the element is back on its own styles and hover lifts work immediately. No JS, a failed script, or reduced motion: nothing is armed, nothing is hidden. Opacity starts at 0.01, not 0 — see rise-in.1161
.js :is([data-reveal=""], [data-reveal="heading"], [data-reveal="photo"]).reveal-pending, .js :is([data-reveal="list"], [data-reveal="pair"]).reveal-pending > *, .js [data-reveal="checks"].reveal-pending > li, .js [data-reveal="rows"].reveal-pending tbody tr@media (prefers-reduced-motion: no-preference)1163
.js [data-reveal="heading"].reveal-pending::after@media (prefers-reduced-motion: no-preference)1167
.js [data-reveal=""].reveal-in@media (prefers-reduced-motion: no-preference)1169
.js [data-reveal="heading"].reveal-in@media (prefers-reduced-motion: no-preference)1173
.js [data-reveal="heading"].reveal-in::after@media (prefers-reduced-motion: no-preference)1176
.js .text-center > [data-reveal="heading"].reveal-in::after@media (prefers-reduced-motion: no-preference)1180
.js :is([data-reveal="list"], [data-reveal="pair"]).reveal-in > *, .js [data-reveal="checks"].reveal-in > li@media (prefers-reduced-motion: no-preference)1182
.js [data-reveal="list"].reveal-in > * > :is(.service-icon, .icon-lg, .icon:first-child)@media (prefers-reduced-motion: no-preference)Line icons settle a beat after their row arrives.1188
.js [data-reveal="checks"].reveal-in > li > .icon@media (prefers-reduced-motion: no-preference)1192
.js [data-reveal="pair"].reveal-in > :first-child@media (prefers-reduced-motion: no-preference) › @media (min-width: 640px)The two entry paths come in from their own sides; stacked, they rise.1199
.js [data-reveal="pair"].reveal-in > :last-child@media (prefers-reduced-motion: no-preference) › @media (min-width: 640px)1200
.js .ba-grid[data-reveal="pair"].reveal-in > .ba-panel--after@media (prefers-reduced-motion: no-preference) › @media (min-width: 640px)Before/after: once Before has landed, After slides out from beneath it into its own column, the fix drawn out from under the problem. It waits hidden (visibility, not opacity) so it can't show through Before while Before is still fading in; Before sits above it for the whole move.1206
.js [data-reveal="rows"].reveal-in tbody tr@media (prefers-reduced-motion: no-preference)1211
.js [data-reveal="photo"].reveal-in@media (prefers-reduced-motion: no-preference)1217
.js [data-reveal="photo"].reveal-in img@media (prefers-reduced-motion: no-preference)1220
::view-transition-group(root), ::view-transition-old(root), ::view-transition-new(root)Continuity: light↔dark blends like dusk, but only during an explicit toggle. Where the browser has View Transitions, theme.js runs the switch inside one and these two rules time the cross-fade; the second lets a press through to the page while it runs, so a quick second toggle isn't swallowed. They hold no class on purpose: nothing else on the site starts a view transition.1265
::view-transition1268
.theme-transition, .theme-transition *@media (prefers-reduced-motion: no-preference)The fallback for browsers without them: the .theme-transition class is added for ~320ms by theme.js. Every element animating its own colors is far more work per frame, which is why it is only the fallback. Reduced-motion visitors get the instant swap they had before.1274
.theme-switch-ico@media (prefers-reduced-motion: no-preference)1278
.theme-transition :is(.theme-switch-knob, .theme-orb-ico)@media (prefers-reduced-motion: no-preference)The crossfade above flattens every transition to color-only while it runs, which would snap the switch knob and the header orb's swipe.1281
.theme-switch:active .theme-switch-ico1287
.framed1289
.what-intro::afterSoft-edged photo that sits beside the "what is an advocate" intro copy. The radial mask feathers every edge so it reads as atmosphere behind the text rather than a placed, framed asset. It floats right at every width so the copy wraps around it; on extremely narrow screens there is no room for a readable text column beside it, so it is hidden. .what-intro carries the clearfix.1303
.vignette-figure1304
.vignette-figure imgSizing/loading is handled the hero way — a <picture> with 640w/1024w sources and sizes="(min-width:1024px) 40vw, 100vw", plus .block/.h-auto/ .w-full utilities on the <img>. This rule only adds the vignette skin.1313
.vignette-figure@media (max-width: 400px)Extremely narrow: drop it — a floated image would leave the copy in a column too thin to read.1321
.vignette-figure@media (min-width: 768px)1324
.vignette-figure@media (min-width: 1024px)1327
.what-intro > .calloutFrom 1024px the photo stands 1rem taller than the copy beside it, which left it half the air above the callout that the copy has. The callout clears the float, so the photo's own 2rem bottom margin is what holds it off.1332

Path cards (the two entry paths) #

SelectorNoteLine
.path-card1335
.path-card::beforeCalm full-bleed photo behind the whole card under a heavy surface wash, so navy body copy keeps its contrast. Decorative; images are same-origin.1339
.path-card > *1356
.path-card .btn-ghostPath CTAs read as light blue at rest in both themes, matching the "Get Started" tab; hover deepens to navy in light mode and lifts to white in dark mode.1361
.path-card .btn-ghost:hover1366
.path-card--crisisJPEG is the safe fallback; @supports upgrades to AVIF/WebP where the image-set() type() syntax is understood (Chrome 113+, Safari 17+, FF 89+).1374
.path-card--referrer1375
.path-card--crisis@supports (background-image: image-set(url('x.webp') type('image/webp')))1378
.path-card--referrer@supports (background-image: image-set(url('x.webp') type('image/webp')))1385
.path-card::before1394
.path-card::before@media (prefers-contrast: more)1397

How it works #

Pinned note-cards on ruled paper, joined by a marching-ants dashed thread (adapted from a 21st.dev "how-it-works")

SelectorNoteLine
.step-num1402
.how-flow1411
.how-flow::beforefaint notebook rule behind the cards, faded out at both edges1414
.how-steps1426
.how-steps > :nth-child(1)--how-accent tints pins + borders; --how-ink is the contrast-safe step for the numerals.1436
.how-steps > :nth-child(2)1437
.how-steps > :nth-child(3)1438
.how-steps > :nth-child(4)1439
.how-step1441
.how-cardwhite "photo" frame around a tinted inner panel1444
.how-card-inner1454
.how-card-inner h31463
.how-pinpushpin straddling the top edge of the frame1466
.how-pin svg1478
.how-spineconnector: dashed path stretched over the flow (preserveAspectRatio="none", non-scaling-stroke keeps the dash rhythm even under the non-uniform stretch)1482
.how-spine path1483
.how-weave-lineResting / no-JS state: the marching dashes, exactly as before.1491
.how-weave-traceThe continuous trace is only used for the JS-driven draw-on (below).1493
.how-steps@media (min-width: 768px)1496
.how-step@media (min-width: 768px)1505
.how-step:nth-child(1)@media (min-width: 768px)cards pushed to the outer edges; the thread weaves the gap between them1507
.how-step:nth-child(2)@media (min-width: 768px)1508
.how-step:nth-child(3)@media (min-width: 768px)1509
.how-step:nth-child(4)@media (min-width: 768px)1510
.how-step:nth-child(odd) .how-card@media (min-width: 768px)a loose, hand-pinned tilt that straightens on hover1513
.how-step:nth-child(even) .how-card@media (min-width: 768px)1514
.how-card:hover@media (min-width: 768px)1515
.how-spine@media (min-width: 768px)1517
.how-weave-line@media (prefers-reduced-motion: no-preference)Marching-ants thread + a one-shot lift-in when the section scrolls into view. .js-draw is added by JS only (and never under reduced motion), so with no JS everything shows in its finished state.1533
.how-flow.js-draw .how-weave-line@media (prefers-reduced-motion: no-preference)With JS driving the section, the thread is drawn between the pinned cards as they lift in, then hands over to the marching rhythm. The loop only runs while the flow is on screen — nothing marches out of view.1539
.how-flow.js-draw .how-weave-trace@media (prefers-reduced-motion: no-preference)1540
.how-flow.js-draw.is-drawn .how-weave-trace@media (prefers-reduced-motion: no-preference)1546
.how-flow.js-draw.is-drawn .how-weave-line@media (prefers-reduced-motion: no-preference)1552
.how-flow.js-draw.is-drawn.is-onscreen .how-weave-line@media (prefers-reduced-motion: no-preference)1556
.how-flow.js-draw .how-step@media (prefers-reduced-motion: no-preference)1558
.how-flow.js-draw.is-drawn .how-step@media (prefers-reduced-motion: no-preference)1559
.how-flow.js-draw.is-drawn .how-step:nth-child(2)@media (prefers-reduced-motion: no-preference)1564
.how-flow.js-draw.is-drawn .how-step:nth-child(3)@media (prefers-reduced-motion: no-preference)1565
.how-flow.js-draw.is-drawn .how-step:nth-child(4)@media (prefers-reduced-motion: no-preference)1566

FAQ accordion #

SelectorNoteLine
.faq-layoutTwo-column split: the heading + a "more questions" note held on the left, a single stacked accordion of hairline-divided rows on the right. Heading and question type stay on the standard scale (H2 / H3).1573
.faq-layout@media (min-width: 900px)1575
.faq-aside@media (min-width: 900px)1580
.faq-more1582
.faq-list1584
.faq-group-h1585
.faq-group-h:first-child1594
.faq-itemEach row is a native <details>: the browser shows and hides the answer on the summary's click, Enter or Space, with no script and no animation.1597
.faq-item:last-child1598
.faq-trigger1599
.faq-trigger::-webkit-details-marker1613
.faq-qThe question stays a real h4, so the heading outline keeps it, but takes its color from the summary: the base heading rule would pin it to --ink and lose the hover.1617
.faq-trigger:hover--link, not Sky: Sky text on the light ground is only 2.4:1.1619
.faq-trigger .faq-sign1620
.faq-item:not([open]) .faq-sign-minus, .faq-item[open] .faq-sign-plusThe sign carries both symbols and [open] picks one: a plus closed, a minus open. A half turn on the swap makes the change read as a morph rather than a cut, and a minus is unchanged by 180deg so it still lands flat.1628
.faq-item[open] .faq-sign1630
.faq-panel1632
.faq-panel > *1636

"Get Started" drawer tab #

Fixed to the right edge, vertical text, opens the contact drawer. z-index sits below #contact-backdrop (90), so the drawer's backdrop covers it while the drawer is open.

SelectorNoteLine
.drawer-tab1641
.drawer-tab:hoverLight blue at rest in both themes; hover deepens to navy in light mode and lifts to white in dark mode.1669
.drawer-tab:focus-visible--ring is Navy on light / Sky on dark: ≥3:1 against the page either way.1675
.drawer-tab.is-tucked, .js .drawer-tab.tab-waitTucked away above the Services section and once the in-page intake form (or the closing region) is on screen. .tab-wait holds it tucked from first paint until main.js has measured the page (JS only, so no-JS keeps it).1679
.drawer-tab@media (max-width: 479px)Phones: a right-edge tab would sit over body copy inside the 1.5rem gutter, so the CTA becomes a full-width bar pinned to the bottom of the viewport (the brief's "CTA pinned to the bottom on scroll").1690
.drawer-tab.is-tucked, .js .drawer-tab.tab-wait@media (max-width: 479px)1704
.drawer-tab:focus-visible@media (max-width: 479px)1706
.site-footer@media (max-width: 479px)keep the footer's last lines clear of the bar when it is showing1708

Contact drawer (right-anchored slide-out) #

Opened with dialog.show(), not showModal(): a showModal() dialog (and its ::backdrop) render in the browser's top layer, which paints above *every* normal stacking context regardless of z-index -- and that hid the very- high-z-index challenge overlay Google's reCAPTCHA used to append to <body> for an image challenge. The form now uses Cloudflare Turnstile, whose challenge stays inside the widget, so that constraint is gone; the plain fixed-position dialog + real sibling backdrop element (below) stay because they work and are tested. modal.js handles the inert-background, focus- trap, and Escape/outside-click behavior that showModal() would give for free.

SelectorNoteLine
#contact-modal1722
#contact-backdrop1739
#contact-backdrop[hidden]1746
#contact-modal[open]1747
#contact-backdrop:not([hidden])1748
#contact-modal[open]@media (prefers-reduced-motion: reduce)1755
#contact-backdrop:not([hidden])@media (prefers-reduced-motion: reduce)1756
#contact-modal.is-closing[open]Closing: modal.js adds .is-closing and only closes the dialog once this finishes. The drawer slides back out the edge it came from, quicker than it arrived and accelerating away; the backdrop fades with it and stops catching clicks, since the page behind is already live again.1762
#contact-backdrop.is-closing:not([hidden])1763
#contact-modal.is-closing[open]@media (prefers-reduced-motion: reduce)1773

Lightbox variant #

The same dialog, centered instead of edge-anchored. A data-variant="lightbox" open button adds .is-lightbox (see modal.js).

SelectorNoteLine
#contact-modal.is-lightbox1777
#contact-modal.is-lightbox .modal-scroll1786
#contact-modal.is-lightbox[open]1790
#contact-modal.is-lightbox.is-closing[open]1797
#contact-modal.is-lightbox[open]@media (prefers-reduced-motion: reduce)1805
#contact-modal.is-lightbox.is-closing[open]@media (prefers-reduced-motion: reduce)1806
.modal-scroll1809
.modal-closePinned to the dialog, not the scroller, so it stays put while the content scrolls beneath it. z-index 1 keeps it above that content (a hovered .btn's transform makes its own stacking context and would otherwise paint over it) but under .modal-confirm (z-index 2), which covers it while asking.1814
.modal-close:hover1830

Forms #

SelectorNoteLine
.field1837
.field-label1838
.field-hint1846
.req1847
.input, .textarea, .selectInk, so Coral keeps one meaning (errors)1848
.textarea1861
.input:focus, .textarea:focus, .select:focus1862
.input[aria-invalid="true"], .textarea[aria-invalid="true"], .select[aria-invalid="true"]1863
.field-errorError text stays --ink for AA; the coral border and the leading triangle carry the "error" signal visually. For assistive tech it's aria-invalid on the field plus this element's id joining the field's aria-describedby (contact-form.js), and focus moving to the error summary on a failed submit.1870
.field-error:not([hidden])1871
.field-error:not([hidden])::before1872
.choice1873
.choice input1880
.choice input[type="checkbox"]:not(:checked)An empty checkbox is drawn from the text fields' own tokens. The browser's, under color-scheme: dark, is a flat neutral grey that reads as disabled on Navy. Once ticked it is the native control again, tinted by accent-color.1884
.choice input[type="checkbox"][aria-invalid="true"]:not(:checked)The same 2px Coral border an empty required field gets.1891
fieldset1892
[data-turnstile-field][hidden]Cloudflare's Turnstile widget (contact-form.js renders it "flexible": full width, never under 300px, 65px tall -- or "compact", 150x140, where the card is narrower than 300px). Reserve the height so the card doesn't jump a second time when the iframe arrives, and let it scroll rather than push the card wider if it still doesn't fit. The field is authored hidden and appears on the visitor's first edit; .field's display: block sits in this layer and so outranks Preflight's [hidden], hence the explicit rule.1901
[data-turnstile-field]1902
[data-turnstile]1903
.hp-field1905
.form-note1913
.error-summary1921
.error-summary a1932
.error-summary strong1933

Site preferences dialog #

Theme, text size, animations, reader mode, analytics. Built by prefs.js and opened with showModal(), so the browser supplies the backdrop, the inert page and Escape. Centered like the contact lightbox, on the page's own tokens.

SelectorNoteLine
.prefs-dialog1939
.prefs-dialog::backdrop1951
.prefs-dialog[open]1955
.prefs-dialog[open]::backdrop1956
.prefs-dialog[open]@media (prefers-reduced-motion: reduce)1958
html:has(.prefs-dialog[open])1960
.prefs-body1961
.prefs-body@media (min-width: 480px)1966
.prefs-titleThe heading repeats the gear that opened the dialog.1968
.prefs-title svg1969
.prefs-lede1970
.prefs-group1971
.prefs-label1972
.prefs-hint1979
.prefs-segSegmented radios: the input stays in the tab order (arrow keys move between options) but is drawn by its label.1983
.prefs-seg-option1993
.prefs-seg-option input2007
.prefs-seg-option:hover2014
.prefs-seg-option:has(input:checked)2015
.prefs-seg-option:has(input:focus-visible)2016
.prefs-seg--textText size: 2 × 2 on phones, one row from 480px.2021
.prefs-seg--text@media (min-width: 480px)2022
.prefs-seg--text [data-value="lg"]Each option previews its own step (the root scale on :root[data-text]).2024
.prefs-seg--text [data-value="xl"]2025
.prefs-seg--text [data-value="xxl"]2026
.prefs-rows2028
.prefs-row2029
.prefs-rowWhen the label and switch can't share a line (a narrow phone at the Largest text size), the switch drops below the text instead of squeezing it.2039
.prefs-row-text2040
.prefs-row .prefs-label2041
.prefs-switchOn/off switch: a native checkbox (role="switch") drawn as a pill, in the same track + knob shape as the footer's theme switch.2045
.prefs-switch::before2058
.prefs-switch:checked2070
.prefs-switch:checked::before2071
.prefs-switch:disabled2072
.prefs-switch:focus-visible2073
.prefs-actions2075
.prefs-actions .btn2082
.prefs-actions .btn@media (min-width: 480px)2083

Reader mode (Site preferences → data-reader="on" on <html>) #

One plain column for reading: every layout grid in <main> collapses to a single track in a narrower measure, decoration (mazes, the How-it-works thread and pins, the lit beacon, the hero glow) is removed, tinted section grounds and card shadows go flat, and the leading opens up. Content photos are hidden too, same as the aria-hidden treatment they get for Safari's built-in Reader View — this mode should read like a plain HTML page, text only. The navy Schedule band keeps its ground, since its type is set for it. The header, footer and pinned CTA are untouched.

SelectorNoteLine
:root[data-reader="on"] main :is(.framed, .vignette-figure, .hero-byline-frame)2094
:root[data-reader="on"] main2097
:root[data-reader="on"] main :is(.canvas, .canvas-wide)2098
:root[data-reader="on"] main .sg-shellThe style guide's shell: one column in the same measure, contents on top.2100
:root[data-reader="on"] main .sg-toc2101
:root[data-reader="on"] main :is(.grid, .grid-9, .service-rows, .who-list, .price-choices, .how-steps, .faq-layout, .fineprint-body, .ba-grid)2102
:root[data-reader="on"] main .ba-panel--before .ba-descSame phone treatment as the ba-grid's own narrow breakpoint: Before shows titles only, so the stacked panels reach the answer sooner, and the two panels drop the side-by-side subgrid row alignment (a >=640px-only rule that would otherwise still apply once the columns above collapse to one).2110
:root[data-reader="on"] main :is(.ba-panel, .ba-list)2111
:root[data-reader="on"] main :is(.grid, .grid-9, .how-steps, .who-list) > *2115
:root[data-reader="on"] main :is(.maze-hero, .lost-maze, .maze-cross, .how-spine, .how-pin, .beacon-lit)2120
:root[data-reader="on"] main :is(.hero-atmos, .how-flow, .trust-stack)::before2123
:root[data-reader="on"] main .trust-stack :is(.trust-stack-title, .trust-item strong, .trust-item p)2124
:root[data-reader="on"] main .how-step2127
:root[data-reader="on"] main .service-row2128
:root[data-reader="on"] main .path-card::beforeThe path cards' photo wash is decoration.2130
:root[data-reader="on"] #top > .canvas.sectionThe hero keeps extra bottom room for its glow; use the normal rhythm.2132
:root[data-reader="on"] main .vignette-figureThe "What" photo floats beside the copy, and the value list (its own flex box) squeezes into the leftover strip. Stack it above the text instead.2135
:root[data-reader="on"] main .faq-more2141
:root[data-reader="on"] main :is(.about-rail, .faq-aside)Sticky side rails would slide over the stacked copy below them.2143
:root[data-reader="on"] main :is(.faq-layout, .fineprint-body)Their desktop column gaps become row gaps once stacked.2145
:root[data-reader="on"] main .section:not(.on-dark)2146
:root[data-reader="on"] main :is(.card, .tile, .path-card, .how-card, .how-card-inner, .price-choice, .framed)2150
:root[data-reader="on"] main .faq-aside2155
:root[data-reader="on"] main .ba-list::before2156
:root[data-reader="on"] main :is(p, li)2157

Hero actions #

One primary button + the phone number, on the first screen

SelectorNoteLine
.hero-actions2415
.hero-taglineTagline: a quieter second lede under the main one.2422
.hero-cta-noteCTA note: sits under the button, not beside it, inside the wrapping flex row.2424
.hero-credsCredentials beside the CTA: plain text, not a link.2431
.trust-stackTrust stack: five reasons in a row under the hero; two columns on tablets, one list on phones. Each item gets a brand accent. No rule above it: the hero maze runs underneath and a full-width line cut across it. The maze is knocked back instead of ruled off — a soft wash of the page ground fades it under the strip, and every glyph carries a small halo of the same ground, so walls stop at the letterforms rather than crossing them. Both are page-ground colored, so they follow the theme.2445
.trust-stack::beforePaints over .maze-hero: both sit at z-index -1 in the hero's stacking context (.hero-atmos isolates), and this comes later in tree order.2452
.trust-stack :is(.trust-stack-title, .trust-item strong, .trust-item p)2461
.trust-stack-title2464
.trust-list2472
.trust-list@media (min-width: 640px)2479
.trust-list@media (min-width: 1024px)2480
.trust-item2481
.trust-item strong2490
.trust-item--years2497
.trust-item--sky2498
.trust-item--seagrass2499
.trust-item--lantern2500
.trust-item--navy2501
.trust-ico2502
.hero-bylineByline: Lindsey's face on the first screen, linking to her full bio. At every width, so the text-only phone hero still has a person in it.2511
.hero-byline-frameCircular mask framed on her face: the <picture> is the circle and the portrait inside is scaled up and shifted so hair-to-chin fills it.2521
.hero-byline-photo2532
.hero-byline-text2540
.hero-byline-name2541
.hero-byline:hover .hero-byline-name2548
.hero-byline-meta2549
.path-notePlain-sentence reassurance inside the crisis path card.2552

Services as rows, not a tile grid #

Icon in its own column, two columns of rows from md up. Each row takes a palette accent (Seagrass, Lantern, Sky, repeating): a tinted icon tile, a soft glow behind the icon and matching bullet markers. --svc tints marks and washes; --svc-ink is the contrast-safe step.

SelectorNoteLine
.service-rows2563
.service-rows@media (min-width: 768px)2564
.service-rows > :nth-child(3n+1)2565
.service-rows > :nth-child(3n+2)2566
.service-rows > :nth-child(3n)2567
.service-row2568
.service-row > .service-icon2577
.service-row > .service-icon .icon-lg2585
.service-row > :not(.service-icon)2586
.service-row p2587

Who we help #

A photo card per audience on a Sky-tinted band. One column on phones, two from 640px, four in a row from 1024px (below). Photos: Unsplash, credited on about-this-site.html; responsive derivatives from scripts/fetch-photos.sh.

SelectorNoteLine
.who-band2593
.who-listSpacing matches the For Professionals "Who refers" list (gap-y-10 gap-x-6, h3 mt-4, p mt-2).2598
.who-list@media (min-width: 640px)2599
.who-item2600
.who-photo2601
.who-photo img2602
.who-item p2603

About #

The page's showcase. Large portrait, editorial rail.

SelectorNoteLine
.about-portrait2606
.about-portrait img2607
.fee-promiseFee agreement promise, placed beside the numbers it reassures.2610
.fee-promise .icon2617

What is a healthcare advocate #

The four value items as a quiet list

SelectorNoteLine
.value-list2620
.value-list > li2621
.value-list > li > .icon2622
.value-list > li:nth-child(1) > .icon2623
.value-list > li:nth-child(2) > .icon2624
.value-list > li:nth-child(3) > .icon2625
.value-list > li:nth-child(4) > .icon2626
.value-list p2627
.value-list > li > .icon@media (min-width: 480px)2629
.ba-gridBefore/after: two framed panels, a header band over a white list, stacked on phones and side by side from 640px. Side by side, the panels share row tracks (subgrid) so each "before" row lines up with its "after" row.2635
.ba-panel2640
.ba-panel--after2649
.ba-head2650
.ba-head h32659
.ba-head p2660
.ba-list2661
.ba-panel--after .ba-listThe one-colour Beacon as a faint green watermark filling the After list, painted under the rows (isolation keeps z-index -1 above the white fill).2671
.ba-panel--after .ba-list::before2672
.ba-list > liNarrow: the marker leads the title and the description takes the full width. From 640px the marker gets its own column beside both.2685
.ba-list > li:first-child2693
.ba-desc2694
.ba-markRed cross / green tick discs; the palette hues clear 3:1 behind white.2696
.ba-mark--yes2705
.ba-mark svg2706
.ba-title2715
.ba-desc2716
.ba-panel--before .ba-desc@media screen and (max-width: 639.98px)Phones (and Larger / Largest text): Before shows its titles only, so the stacked panels reach the answer sooner.2720
.ba-grid@media (min-width: 640px)2723
.ba-panel@media (min-width: 640px)2728
.ba-panel--before@media (min-width: 640px)Before stays on top so After can slide out from beneath it.2730
.ba-list@media (min-width: 640px)2731
.ba-head@media (min-width: 640px)2732
.ba-list@media (min-width: 640px)2733
.ba-list > li@media (min-width: 640px)2734
.ba-list > li > .ba-desc@media (min-width: 640px)2735
.ba-mark@media (min-width: 640px)2736
.ba-mark@media (min-width: 640px)2737
.field-countNon-breaking spaces (contact-form.js) keep "n of 600" together; the count can still wrap before "characters" in a very narrow column.2750
.field-count.is-near2751
.field-count.is-over2752
.modal-quickbookContact dialog: book or call first, message form as the alternative2759
.modal-quickbook[hidden]2760
.modal-quickbook-lede2761
.modal-quickbook-actions2762
.modal-quickbook-actions .btnLabels stay on one line (so the Calendly mark sits level with the text); when both don't fit side by side, the buttons stack instead.2765
.modal-divider2766
.modal-divider::before, .modal-divider::after2774
.modal-confirmDiscard check: dims the dialog's own content (made inert by modal.js) and centers a small card over it while the visitor decides.2779
.modal-confirm[hidden]2788
.modal-confirm-card2789
.modal-confirm-actions2799
.modal-confirm-actions .btn2800

"The light comes on" #

The one delight moment — When a message reaches Lindsey, the Advocate Beacon mark appears in the success panel and its light comes up behind it: a soft Beacon Sky glow blooms, one slow beam sweeps round, and it settles to a steady glow. The mark itself is never altered (brand geometry); the light is drawn behind it. Runs when the panel is unhidden (display:none -> shown restarts the keyframes), once per send, never loops. A consultation booked in the inline Calendly embed is the same moment and gets the same light ([data-calendly-booked], with the Reversed mark where it sits on the navy booking band). Reduced motion: the glow fades in, no sweep, no scale.

SelectorNoteLine
.beacon-lit2812
.beacon-lit-mark2821
.beacon-lit::beforesteady glow2828
.beacon-lit::afterthe sweeping beam: a soft wedge of light, feathered by a radial mask2841
:is([data-contact-success], [data-calendly-booked]):not([hidden]) .beacon-lit::before@media (prefers-reduced-motion: no-preference)2858
:is([data-contact-success], [data-calendly-booked]):not([hidden]) .beacon-lit::after@media (prefers-reduced-motion: no-preference)2861
:is([data-contact-success], [data-calendly-booked]):not([hidden]) .beacon-lit-mark@media (prefers-reduced-motion: no-preference)2864
:is([data-contact-success], [data-calendly-booked]):not([hidden]) .beacon-lit::before@media (prefers-reduced-motion: reduce)2869

Hero maze #

A generated maze (#maze-hero) behind the whole hero, anchored to its bottom-right corner. It fades in toward the right, so the copy column stays clean and the maze runs around and under the illustration. Phones get the same right-hand fade. Walls only, static; non-scaling strokes hold 1.75px.

SelectorNoteLine
.maze-hero2897
.maze-hero use2912
#top > .canvas.section2913

Fine print, read #

A sample statement beside three plain notes. The statement is a paper document (Surface, hairline, Lift); flagged lines take a Sand highlighter wash, and the reviewer's marks are Sky Ink pen strokes (an underline, a ring) drawn in the sprite's round caps. Numbered pins tie each mark to its note. Everything is visible without JS or motion.

SelectorNoteLine
.fineprint-head p2920
.fineprint-body2921
.fineprint-body@media (min-width: 820px)2929
.fineprint-notes p.fineprint-note2931
.fineprint-doc2938
.fineprint-doc@media (min-width: 480px)2947
.fineprint-doc-top2949
.fineprint-doc-title2955
.fineprint-stamp2962
.fineprint-doc-meta2975
.fineprint-table2985
.fineprint-table thead th2991
.fineprint-table td3001
.fineprint-table td:first-child3007
.fineprint-table td:first-child@media (min-width: 480px)3013
.fineprint-table .fp-amt3014
.fineprint-table thead .fp-amt3021
.fineprint-table tfoot th, .fineprint-table tfoot td3022
.fineprint-table tfoot th3028
.fineprint-doc@container fineprint (max-width: 320px)The narrowest phones: with 320px or less to work in (a 320–360px screen, or wider ones at the larger text sizes), tighter tracking, padding and figures keep all three columns inside the document, and at a larger text size it borrows a little of the page gutter. A container query, so it follows the space actually left at every text size (text-layout.js only rewrites @media).3040
.fineprint-table thead th@container fineprint (max-width: 320px)3041
.fineprint-table :is(thead, tbody, tfoot) :is(th, td)@container fineprint (max-width: 320px)3042
.fineprint-table td:first-child@container fineprint (max-width: 320px)3043
.fineprint-table .fp-amt@container fineprint (max-width: 320px)3044
:root[data-text] .fineprint-doc@container fineprint (max-width: 320px)3045
.fineprint-table tr.is-flagged tdSand highlighter behind a flagged line. Each cell paints its own slice of one row-wide gradient (main.js sets --fp-row-w and each cell's --fp-x), so a single --fp-sweep on the row moves one edge across all three cells in one stroke. Without the measurements each cell just carries the full wash.3052
.fineprint [data-note="1"]3059
.fineprint [data-note="2"]3060
.fineprint [data-note="3"]3061
.fp-pin3063
.fineprint-table .fp-pin3077
.fineprint-table .fp-pin@media (min-width: 480px)3078
.fp-mark3080
.fp-ink3081
.fp-ink path3082
.fp-ink--under3090
.fp-ink--ring3091
.fineprint-notes3093
.fineprint-notes > li3094
.fineprint-notes .fp-pin3099
.fineprint-notes h33100
.fineprint-notes p3101
.fineprint-notes@media (min-width: 820px)3102
.fineprint[data-active="1"] [data-note="1"] .fp-pin, .fineprint[data-active="2"] [data-note="2"] .fp-pin, .fineprint[data-active="3"] [data-note="3"] .fp-pinPointing at a note (or its line) lights the pair.3105
.fineprint[data-active="1"] li[data-note="1"] h3, .fineprint[data-active="2"] li[data-note="2"] h3, .fineprint[data-active="3"] li[data-note="3"] h33108
.js [data-reveal="marks"].reveal-pending@media (prefers-reduced-motion: no-preference)3113
.js [data-reveal="marks"].reveal-pending .fp-ink path@media (prefers-reduced-motion: no-preference)3114
.js [data-reveal="marks"].reveal-pending tr.is-flagged@media (prefers-reduced-motion: no-preference)3115
.js [data-reveal="marks"].reveal-pending .fineprint-table .fp-pin@media (prefers-reduced-motion: no-preference)3116
.js [data-reveal="marks"].reveal-in@media (prefers-reduced-motion: no-preference)3118
.js [data-reveal="marks"].reveal-in tr.is-flagged@media (prefers-reduced-motion: no-preference)One stroke per row: a quick start, an even pull across, a soft lift at the end.3122
.js [data-reveal="marks"].reveal-in .fineprint-table .fp-pin@media (prefers-reduced-motion: no-preference)3126
.js [data-reveal="marks"].reveal-in .fp-ink path@media (prefers-reduced-motion: no-preference)3130

Who we help #

From 1024px the four audiences stand in a row.

SelectorNoteLine
.who-list@media (min-width: 1024px)3146

Credentials #

As the rows arrive, a Sky rule runs along each one, in reading order, and fades back to the hairline.

SelectorNoteLine
.dl-rows > div3151
.js .dl-rows.reveal-in > div::after@media (prefers-reduced-motion: no-preference)3153

Booking band #

The maze, solved, full width along the bottom of the navy ground. On the index it has a strip of its own under the buttons (rule below), so no copy crosses a wall; the style guide's masthead still uses it as a background, anchored to the band's bottom edge and fading out upward, so it takes no space of its own. It scales with the page, so both walls always show; a smaller maze stands in on phones so the cells stay readable. A Beacon Sky ring waits outside the entrance in the left wall; the route crosses to the exit in the right wall and ends at a dot. main.js adds .route-pending, then .is-routed once the footer comes into view: ring, route, dot. No JS: all shown. Reduced motion: shown without the draw (global rule).

SelectorNoteLine
.schedule-band3184
.maze-cross3185
.maze-cross--lg3203
.maze-cross--sm3204
.maze-cross--lg@media (max-width: 640px)3206
.maze-cross--sm@media (max-width: 640px)3207
#schedule .maze-crossOn the index the maze is the last thing in the band, in the flow: it takes the room its aspect ratio asks for, so the copy and buttons end above it instead of running across the walls. The negative bottom margin lands it flush with the band's edge, in the padding .section leaves under the buttons (the clamp repeats .section's), and goes with the maze where the maze is hidden (reader mode, print), leaving that padding as the band's ordinary bottom.3215
.maze-cross use3219
.maze-route3220
.maze-cross--sm .maze-route3221
.maze-route-start3222
.maze-route-end3223
.maze-cross--lg .maze-route@media (max-width: 1023px)Between phones and desktops the wide maze scales down to about half.3226
.maze-cross--lg .maze-route-start@media (max-width: 1023px)3227
.maze-cross.route-pending .maze-route3229
.maze-cross.route-pending :is(.maze-route-start, .maze-route-end)3230
.maze-cross.route-pending.is-routed .maze-route3231
.maze-cross.route-pending.is-routed .maze-route-start3235
.maze-cross.route-pending.is-routed .maze-route-end3239

Calendly, inline #

The scheduler framed in the page by calendly.js, in the booking band and in a panel of the contact dialog. Until the frame is in, a mount holds only its authored fallback, a plain link out; .is-live swaps that for the frame plus a one-line way out for a visitor whose browser refuses it. Calendly's page is transparent around its own white card, so the mount adds no card of its own. Its colors need a paid plan: the card is white in both themes. The mount's width picks Calendly's layout, and with it the size (its breakpoints are px widths of the frame, measured 2026-09-18 with the event details hidden): - under 650px, the phone layout: the page fills the frame, 602px tall, and a picked day opens its times as a second view; - 650 to 999px, a stacked card: 711px, growing past 1300px when a day is picked. Avoided: it is the largest of the three; - 1000px and up, the desktop layout: a 680px card in a transparent page, 660px tall and fixed, because the times scroll inside the card. So the mount is capped at 640px, and the booking band widens it to 1040px where the viewport has room (clear of the 1000px edge; the band clips any overflow).

SelectorNoteLine
.calendly-embed3262
#schedule .calendly-embed@media (min-width: 1072px)1040px + a 1rem gutter each side. The Larger / Largest text sizes keep the phone layout (text-layout.js rewrites this query), and so the 640px mount.3266
#schedule .calendly-embed.is-live@media (min-width: 1072px)The desktop layout brings a 66px top margin of its own.3272
#schedule .calendly-frame@media (min-width: 1072px)3273
.calendly-frameThe height is only a start, matched to the layout's first report so nothing jumps: calendly.js sets the real one from the frame's own reports, step by step. color-scheme matches Calendly's page; without it the dark theme makes the browser paint an opaque backdrop behind the frame.3279
.calendly-loadingSits where Calendly's card will land and is covered by it; calendly.js removes it when the frame has loaded.3291
.calendly-alt3297
.on-dark .calendly-alt3298
.calendly-embed.is-live .calendly-alt3299
.calendly-embed.is-live .calendly-fallback3300
.calendly-embed.is-booked .calendly-fallbackBooked: calendly.js has taken the frame out and unhidden the authored thanks (.calendly-booked), which is all the mount shows. It is the same moment as a sent message, so the beacon's light comes on the same way (rules below, "The light comes on"). In the dialog, modal.js marks the booking block too, and everything in it but the mount steps aside.3306
.calendly-booked[hidden]3307
.calendly-booked:focus3308
.modal-quickbook.is-booked > :not(.calendly-embed)3309
:root[data-booked] :is([data-calendly]:not(.is-booked), [data-calendly-toggle], [data-calendly-offer])@media screenOnce booked, not asked again: calendly.js remembers a booking (localStorage) and sets data-booked on <html>, and this takes the invitations to book away: every mount but the one showing its thanks, every toggle, and whatever is marked data-calendly-offer (the success panel's "Book time with us"). Copy marked data-calendly-after is authored hidden and stands in where a sentence needs rewording. The footer's Calendly icon is left alone, and following it brings all of this back. A rule and not script, so a dialog modal.js imports later needs no setup. Screen only: paper keeps the band's fallback link.3319
:root[data-booked] [data-calendly-after]@media screen3320
.calendly-embed.calendly-embed--panelIn the dialog: a gap under the buttons. The drawer is 30rem wide, so this is always Calendly's phone layout.3324
.calendly-embed--panel .calendly-loading3325

"Get Started" tab #

The first time it comes out it leans a little further than its resting place and settles back, once per page view.

SelectorNoteLine
.drawer-tab3329
.drawer-tab@media (max-width: 479px)3335
.drawer-tab.is-arriving@media (prefers-reduced-motion: no-preference)3342

404 #

A small maze with its route drawn through to the way out

SelectorNoteLine
.lost-maze3351
.lost-maze path3352
.lost-maze-walls3353
.lost-maze-route3354
.lost-maze-end3355
.lost-maze-route@media (prefers-reduced-motion: no-preference)3357
.lost-maze-end@media (prefers-reduced-motion: no-preference)3358
.maze-hero, .maze-cross@media print3366
.trust-stack::before@media print3367
.trust-stack :is(.trust-stack-title, .trust-item strong, .trust-item p)@media print3368

Style guide #

The bar. The internal pages' own bar: the mark, the page's name, links between the guide and the two code references, and the theme and Site preferences buttons. Deliberately not the site header, so nobody takes these pages for public ones. Navy in both themes, like the booking band, so it borrows the dark palette (.sg-theme-dark on the element) and the header's round buttons work on it unchanged. Not sticky: a reference page is scrolled, not navigated from the top.

SelectorNoteLine
.sg-bar3467
.sg-bar@media (min-width: 480px)The bar's content keeps the shell's edges: 2rem of padding, or whatever centers it on the same 1116px once the window is wider than the shell.3479
.sg-bar-home3480
.sg-bar-home img3481
.sg-bar-title3482
.sg-bar-nav3488
.sg-bar .sg-bar-nav aLabel-voice links. The current page holds the nav's 2px Sky underline. Two classes, to outrank .on-dark's Sky for classless links.3491
.sg-bar .sg-bar-nav a:hover, .sg-bar .sg-bar-nav a[aria-current]3499
.sg-bar-nav a[aria-current]::after3500
.sg-bar-tools3508
.sg-bar@media (max-width: 479px)On a phone the links and the two buttons share the second row.3511
.sg-bar-nav@media (max-width: 479px)3512
.sg-bar-btnThe header sets its theme and gear buttons at 2.25rem; here there is room for the full 44px. A class of its own rather than a descendant selector: Tailwind keeps a rule when any class in it is used, and .header-icon-btn is, on every page.3518
:root[data-text] .sg-bar@media screenAt the larger text sizes the bar holds a fixed-geometry mark and round buttons, so it is zoomed back like the site header, and its buttons up again.3526
:root[data-text] .sg-bar-btn@media screen3527

Style guide #

The masthead. The guide opens on a real component: the booking band's navy ground with its maze, solved, along the bottom edge (.schedule-band and .maze-cross, unchanged), and the title block above it. main.js isn't loaded here, so the route is simply shown drawn.

SelectorNoteLine
.sg-mast3534
.sg-mast@media (min-width: 480px)3539
.sg-mast-copy3540
.sg-mast h13541
.sg-mast-lede3542
.sg-mast-key3543

Style guide #

Shell and contents. A sticky contents list beside one reading column. The list is numbered because the numbers are how sections get referred to ("see 9, Buttons"); the headings carry no numeral. Below 1024px, and at the Larger and Largest text sizes, it is one column and the contents becomes a two-column block at the top.

SelectorNoteLine
.sg-shell3557
.sg-shell@media (min-width: 480px)3566
.sg-shell@media (min-width: 1024px)3568
.sg-toc-title3570
.sg-toc ol3579
.sg-toc li3580
.sg-toc li:last-child3581
.sg-toc a3582
.sg-toc a::beforeOut of flow, so the current link's underline stops at its words.3592
.sg-toc a:hover3602
.sg-toc a[aria-current]The section being read, set by style-guide.js: full-strength ink and the 2px Sky underline the site nav gives its current link.3605
.sg-toc ol@media (min-width: 560px)3612
.sg-toc@media (min-width: 1024px)3614
.sg-toc ol@media (min-width: 1024px)3622
:root:has(.sg-shell)The bar isn't sticky, so an in-page jump needs none of the site header's scroll padding.3626

Style guide #

Sections and prose. One hairline and a generous gap above each section, more space above a heading than below it. The prose rules carry no specificity and skip any element with a class, so a real component shown as a specimen is never restyled by the page around it.

SelectorNoteLine
.sg-main3632
.sg-main > section3633
.sg-main > section > h2::afterEvery section heading carries the site's 8rem Sky rule.3639
:where(.sg-main) h3:not([class])3648
:where(.sg-main) h4:not([class])3649
:where(.sg-main) p:not([class])3650
.sg-ledeA section's one opening sentence: .lede's size and measure, in full-strength ink.3652
:where(.sg-main) :is(ul, ol):not([class])3653
:where(.sg-main) :is(ul, ol):not([class]) > li + li3660
.sg-main codeCode is code: the mono face, on a quiet chip. A chip moves to the next line whole, and only breaks inside when it is longer than the line.3663
.sg-main pre3675
.sg-main pre code3687
.sg-note3688
.sg-foot3689

Style guide #

Named rules. A short list that closes a section: each rule's name in the heading face over its sentence. Names are sentence case, not tracked capitals, because several run to a full clause.

SelectorNoteLine
.sg-rules3700
.sg-rules > li3701
.sg-rules > li > b3702

Style guide #

Ledgers. Reference tables are ledgers, not grids: label-voice column heads over hairline rows, no cell borders, no zebra. .sg-scroll wraps any ledger that may be wider than a phone, so the page itself never scrolls sideways.

SelectorNoteLine
.sg-scroll3716
.sg-table3717
.sg-scroll > .sg-table3718
.sg-table th, .sg-table td3719
.sg-table :is(th, td):last-child3725
.sg-table td3726
.sg-table tbody th3727
.sg-table thead th3728
.sg-table tbody tr:last-child > *3741
.sg-table tbody th > codeA token or selector in a ledger's narrow first column never breaks at its hyphens.3743
.sg-table tr.sg-group thA group heading inside a long ledger (the token table's families).3745
.sg-table--tokens3754
.sg-table--tokens td:last-child3755
.sg-table--tokens td:has(> .sg-chip)A value cell keeps its chip and its hex on one line.3757
.sg-chipA small square of a color beside its name or value.3759
.sg-tagMarks a palette color that appears in the mark itself.3769
.sg-ratioA measured contrast ratio, with the level it clears printed after it in words, never by color.3784
.sg-ratio > span3785
.sg-ratio[data-grade^="Below"] > spanscripts/build-docs.js writes the grade onto the cell as data-grade.3794
.sg-pairA contrast sample: real ink on a real ground. Each one sits in a .sg-theme-light or .sg-theme-dark scope and paints with var(), inline because every row is a different pair, so a sample can't drift from its tokens.3798
.sg-pair b3808
.sg-pair--edge3809
.sg-ringboxA drawn sample of the 3px focus ring, for the contrast ledger.3811

Style guide #

Palette strips. Each color family is a contiguous strip of steps over a ledger that names every one. The name and value sit under the color in ordinary ink, so no caption depends on the color above it. The grounds are the brand's fixed hex values, theme-independent.

SelectorNoteLine
.sg-tints3825
.sg-tint3826
.sg-tints@media (min-width: 640px)3827
.sg-tint@media (min-width: 640px)3827
.sg-tint > i3828
.sg-tint > i@media (min-width: 640px)3829
.sg-tint:first-child > i3830
.sg-tint:last-child > i3831
.sg-tint:only-child > i3832
.sg-tint--edge > iA step too close to the page ground to show its own edge.3834
.sg-tint > b3835
.sg-main .sg-tint > code3845
.sg-chip-navyThe palette tokens never change with the theme, so these point at them rather than repeating the hex. Deep-tone·alt has no token: its value is the README's.3849
.sg-chip-sky3850
.sg-chip-cyan3851
.sg-chip-coral3852
.sg-chip-midnight3853
.sg-chip-paper3854
.sg-chip-seagrass3855
.sg-chip-lantern3856
.sg-chip-sand3857
.sg-chip-deep-tone3858
.sg-ground-paper3859
.sg-ground-midnight3860
.sg-ground-navy3861
.sg-favicon-dark3862

Style guide #

Specimen plates. A specimen sits on the ground it ships on. The base plate is Surface with a hairline; the navy plate is the booking band's ground and takes .on-dark beside it for the Sky focus ring. .sg-theme-light and .sg-theme-dark (defined with the tokens) re-point a plate at one palette, whatever theme the page is in.

SelectorNoteLine
.sg-plate3869
.sg-plate--navy3877
.sg-plate--navy :is(h3, h4)3878
.sg-plate-cap3879
.sg-row3880
.sg-ring-demoA forced focus ring, for showing the ring without asking the reader to tab.3882
.sg-plate--navy .sg-ring-demo3883
.sg-inertA specimen that must not act: the button keeps its look and loses its pointer.3885
.sg-tileMark and lockup tiles are fixed to their documented ground per assets/logomarks/README.txt, so they stay correct in either theme.3889
.sg-tile img3900
.sg-tile p3901
.sg-tile .sg-tile-hex3909
.sg-tiles3910
.sg-tiles--5@media (min-width: 768px)3911
.sg-tiles--2@media (max-width: 479px)3912

Style guide #

Type specimens. The faces at poster size, then each role set live from its own token beside its name and numbers.

SelectorNoteLine
.sg-faces3916
.sg-faces@media (min-width: 640px)3917
.sg-face3918
.sg-face-big3919
.sg-face-name3920
.sg-face-note3921
.sg-face-alpha3922
.sg-face--logo :is(.sg-face-big, .sg-face-alpha)3923
.sg-face--label :is(.sg-face-big, .sg-face-alpha)3924
.sg-face--mono :is(.sg-face-big, .sg-face-alpha)3925
.sg-specimens3927
.sg-specimen3928
.sg-specimen:last-child3935
.sg-specimen@media (min-width: 768px)3937
.sg-specimen-meta3939
.sg-specimen-meta > b3940
.sg-sample3950
.sg-spec-h13951
.sg-spec-h23952
.sg-spec-h33953
.sg-spec-h43954
.sg-spec-row3955
.sg-spec-caption3956
.sg-spec-label-sm3957
.sg-spec-overline3958

Style guide #

Depth and shape specimens. One card per shadow or radius, drawn with the real value.

SelectorNoteLine
.sg-depths3962
.sg-depths@media (min-width: 768px)3963
.sg-depth3964
.sg-depth--rest3976
.sg-depth--lift3977
.sg-depth--float3978
.sg-depth--tab3979
.sg-depth--r123980
.sg-depth--r103981
.sg-depth--r83982
.sg-depth--round3983

Grid demo #

The real .grid-9 tracks at the real gutter, so the strip is the thing it documents rather than a picture of it. It sits in a .table-wrap with a min-width, which is how the guide handles anything too wide to fold.

SelectorNoteLine
.sg-grid-demo3988
.sg-grid-demo + .sg-grid-demo3993
.sg-grid-demo > *3994
.sg-grid-demo > .sg-grid-fillThe two filled spans borrow token pairs that are already contrast-checked in both themes. The *-ink accents point back at the raw palette in dark, so Paper on them would land near 2.3:1 — these pairs put dark ink on the bright ground instead, the way the primary button and the Before/After panel do.4010
.sg-grid-demo > .sg-grid-fill--alt4014
.sg-grid-demo > *The demo documents the >=1024px behavior, so it keeps its nine tracks at every width — .grid-9's own collapse rule would otherwise flatten it here and the spec would illustrate nothing.4021
.sg-grid-demo > .sg-span-34022
.sg-grid-demo > .sg-span-44023
.sg-grid-demo > .sg-span-54024
.sg-grid-demo > .sg-span-64025
.sg-grid-demo > .sg-span-94026
.sg-grid-demo > .sg-grid-ghostUnused columns: outlined, not filled, so white space reads as space.4028
.sg-grid-label, .sg-wire-frames > div > pAnnotations over a diagram, in the label voice. The wireframes caption every frame the same way, so their captions take this by position rather than carrying the class on 42 elements.4036
.sg-grid-label:first-child, .sg-wire-frames > div > p4046

Theme panels #

The same card rendered in each palette, side by side, so light and dark can be compared without toggling the page.

SelectorNoteLine
.sg-theme-pair4051
.sg-theme-pair@media (min-width: 640px)4052
.sg-theme-panel4053
.sg-theme-panel .sg-theme-name4060
.sg-theme-swatches4067
.sg-theme-swatches > span4068

Layout wireframes #

One schematic per layout the site actually uses. Plain boxes on the real tokens — a picture of the tracks, not a copy of the section, so a diagram stays true when the copy inside it changes. Each frame sets its own tracks through the .sg-wf-row* classes rather than Tailwind's grid, which keeps reader mode's flatten-everything rule off the diagrams: a wireframe collapsed to one column illustrates nothing. The vocabulary is flat, not BEM: every block and every row variant carries the shared base through the :is() lists below, so markup writes class="sg-wf-card sg-wf-tall" rather than repeating a base class on 230 elements. Order matters — each base rule comes before the variants that override it.

SelectorNoteLine
.sg-wire4087
.sg-wire:first-child4088
.sg-wire-head4089
.sg-wire-head h4The layout's name and, beside it, where it lives.4091
.sg-wire-where4092
.sg-wire-framesThe wide frame with the phone frame beside it. Both stack on a narrow screen, and the phone frame keeps its width there — it reads as a phone because it is narrow, so letting it fill the column would lose the point. Widths are in rem, so the whole diagram grows with the text-size setting instead of being zoomed back down.4103
.sg-wire-frames@media (min-width: 640px)4105
.sg-wire-frames--trio@media (min-width: 640px)4106
.sg-wire-frames--duo@media (min-width: 640px)4107
.sg-wire-frame4109
.sg-wire-frame--phone4118
.sg-wf-key-listThe key: each block style beside what it stands for.4121
.sg-wf-key-list@media (min-width: 560px)4122
.sg-wf-key-list@media (min-width: 900px)4123
.sg-wf-key-list > li4124
.sg-wf-canvasThe reading column drawn inside the viewport: dashed, because the canvas is a width limit, not a thing you can see. The inset stands in for the cap plus the side padding — a proportion, not a measurement.4136
.sg-wf-canvas--wide4145
.sg-wf-bandA band whose ground runs the full width with only its content in the canvas — the closing booking panel. It re-points the block tokens rather than reaching in with descendant selectors, so a CTA on the navy ground still reads as a CTA, the way .btn-primary does on dark.4150
.sg-wf-band--dark4156
.sg-wf-stackRows and stacks inside a frame. A row that has real proportions — the hero's 4:5, the fine print's 1.45:1 — states them; the rest split evenly.4167
:is(.sg-wf-row, .sg-wf-row-4, .sg-wf-row-5, .sg-wf-row-hero, .sg-wf-row-rail, .sg-wf-row-aside, .sg-wf-row-doc, .sg-wf-row-icon, .sg-wf-row-bar)4168
.sg-wf-row-44176
.sg-wf-row-54177
.sg-wf-row-hero4178
.sg-wf-row-rail4179
.sg-wf-row-aside4180
.sg-wf-row-doc4181
.sg-wf-row-icon4182
.sg-wf-row-bar4183
.sg-wf-tightCloses a stack or a row up; reads the same on either.4185
.sg-wf-gapThe empty middle track of a bar row: the space between the two ends, with nothing in it to draw.4188
.sg-wf-measureA block held narrower than the column it sits in — the intake card.4190
.sg-wf-zigHow it works: the same four grid areas the real steps take, cards pinned to the outer edges of a two-column block with the thread weaving the gap.4194
.sg-wf-zig::before4200
.sg-wf-zig > *width, not max-width: these are flex containers, and justify-self would otherwise shrink them to their label.4210
.sg-wf-zig > :nth-child(1)4211
.sg-wf-zig > :nth-child(2)4212
.sg-wf-zig > :nth-child(3)4213
.sg-wf-zig > :nth-child(4)4214
.sg-wf-float::afterFloat demos: the vignette photo, and About between 450 and 699px.4217
.sg-wf-float > .sg-wf-floated4218
.sg-wf-float > :not(.sg-wf-floated)4219
:is(.sg-wf-b, .sg-wf-xs, .sg-wf-tall, .sg-wf-xl, .sg-wf-card, .sg-wf-photo, .sg-wf-key, .sg-wf-alt, .sg-wf-ghost, .sg-wf-bleed, .sg-wf-floated)The blocks themselves. Labels are set small and centered; a block that runs out of room clips rather than pushing the diagram out of shape. The two tokens let a band re-point every block inside it in one place.4224
.sg-wf-xs4241
.sg-wf-tall4242
.sg-wf-xl4243
.sg-wf-keyThe filled blocks borrow token pairs already contrast-checked in both themes, same as the grid demo's spans.4246
.sg-wf-alt4247
.sg-wf-card4248
.sg-wf-ghostDashed = space the layout leaves empty, or a block that isn't there at this width. Never a thing you can see on the page.4251
.sg-wf-photoPhotography, hatched so it doesn't read as another panel.4253
.sg-wf-bleedA block that bleeds off the right edge of the frame — the hero photo.4258
.sg-wf-overlayOverlay frame: the contact drawer over a dimmed page, plus the tab that opens it. The scrim and panel are absolutely placed the way the real ones are fixed, so the diagram keeps the drawer's proportion (min(30rem, 100vw) against the viewport).4264
.sg-wf-scrim4265
.sg-wf-drawer4271
.sg-wf-tab4283

Style guide on paper #

Kept here, inside the layer, rather than in the PRINT section at the end of the file. That section is outside @layer and so is never purged; these rules name only the guide's classes, and in here they land in style-guide.css alone. They follow the furniture above, so they still win on source order. Every selector must carry a class: one rule without one (an attribute selector, say) and Tailwind keeps the whole @media block in both stylesheets.

SelectorNoteLine
.sg-bar@media printThe bar is navigation, so it goes; the masthead's navy becomes plain type, and the contents folds to two columns.4304
.sg-toc a[aria-current]@media print4305
.sg-mast@media print4306
.sg-mast h1, .sg-mast-lede@media print4307
.sg-mast-maze, .sg-mast-key@media print4308
.sg-shell@media print4309
.sg-toc@media print4310
.sg-toc ol@media print4311
.sg-main > section@media print4312
.sg-main :is(h2, h3, h4)@media print4313
.sg-scroll@media print4314
.sg-scroll > .sg-table@media print4315
.sg-table tr, .sg-specimen, .sg-rules > li, .sg-plate, .sg-tile, .sg-theme-panel@media print4316
.sg-tint > i, .sg-chip, .sg-pair, .sg-plate--navy, .sg-theme-panel, .sg-theme-swatches > span, .sg-grid-demo > *, .sg-tile, .sg-favicon-dark@media printColor is the content of these, so they print.4318
.sg-wf-b, .sg-wf-xs, .sg-wf-tall, .sg-wf-xl, .sg-wf-photo, .sg-wf-key, .sg-wf-alt, .sg-wf-bleed, .sg-wf-floated, .sg-wf-band, .sg-wf-tab, .sg-wf-scrim@media printSo are the wireframes. Their blocks are filled shapes, and a printer that drops the fills leaves a page of empty outlines.4332
.sg-wire@media printThe wide frame and the phone frame side by side, so a layout and its phone form stay on one line of the printed page.4341
.sg-wire-frames@media print4342
.sg-wire-frames--trio@media print4343
.sg-wire-frames--duo@media print4344

Utilities

25 rules · starts at 4348

General #

SelectorNoteLine
.on-dark4352
.grid-94353
.grid-9@media (max-width: 1023px)4355
.grid-9 > *@media (max-width: 1023px)4356
#top .grid-9@media (max-width: 1023px)Hero: keep the framed photo beside the heading below the lg breakpoint too, mirroring the desktop split, instead of dropping it full-width under the copy. Tablet range keeps the whole illustration at its natural ratio.4362
#top .grid-9 > div:nth-of-type(2)@media (max-width: 1023px)4363
#top .grid-9@media (max-width: 499px)Below 500px the hero image itself is hidden (index.html), so collapse back to a single column instead of reserving the 38% track for nothing.4368
#top .grid-9 > div:nth-of-type(2)@media (max-width: 767px)Below the md breakpoint the column is too narrow for the full 4:3 illustration, so the figure stretches to the copy's height and the photo covers it. The focus sits just left of the figures (~78%): they stay in frame at every width, and the maze on their left fills more of the strip as the screen widens, less as it narrows.4376
#top .grid-9 > div:nth-of-type(2) .framed img@media (max-width: 767px)4377
#top .grid-9 > div:nth-of-type(2)@media (min-width: 768px)Wherever the hero photo shows, run it flush to the browser's right edge: pull it across .canvas's 2rem padding plus the gutter that opens once .canvas hits its 1000px cap. Square off the right side so it reads as bleeding off-screen. The people sit on the right, so they stay in view.4390
#top .grid-9 > div:nth-of-type(2) .framed@media (min-width: 768px)4391
#top .grid-9 > div:nth-of-type(2) .framed img@media (min-width: 768px)4392
#top .grid-9 > div:nth-of-type(2)@media (min-width: 500px)4399
#top .grid-9 > div:nth-of-type(2) .framed@media (min-width: 500px)4402
#about .grid-9@media (min-width: 700px)About: at >=700px the left column (heading + portrait) is one sticky rail beside the scrolling bio. The rail only needs room for the ~200px portrait, so it gets a fixed track rather than a 4/9 share that would strand a gap before the bio. Between 450 and 699 the rail goes full-width and the portrait floats right so the bio wraps around it. Below 450 everything stacks.4413
#about .about-rail@media (min-width: 700px)4414
#about .grid-9@media (min-width: 450px) and (max-width: 699px)4417
#about .grid-9::after@media (min-width: 450px) and (max-width: 699px)4418
#about .about-rail > .meta@media (min-width: 450px) and (max-width: 699px)4419
#about .about-rail > figure@media (min-width: 450px) and (max-width: 699px)4420
#about .grid-9@media (min-width: 1024px)4423
.on-dark-softPaper text at reduced strength, for copy on the navy/midnight grounds.4426
.rise-0Stagger index for [data-rise] entrance animations.4428
.rise-14429
.rise-34430

Print

Unlayered, so it follows Tailwind's utilities and wins on source order. Written for the browser default of "background graphics" off: anything that has to reach paper is carried by text color or a border, never a fill. Margin boxes are Chromium-only; other engines fall back to the footer URL.

107 rules · starts at 4433

General #

SelectorNoteLine
@page4440
:root:root:root@media printPaper is always the light theme, whatever the screen was set to. The triple :root outranks both dark blocks ((0,2,0) each).4460
html@media printEvery rem shrinks together: 18px body copy lands at ~11pt.4486
body@media print4487
*, *::before, *::after@media print4489
.reveal-pending, .reveal-pending > *, .reveal-pending tr, [data-rise], .how-flow .how-step@media printEntrance start-states hold content invisible until it scrolls into view.4496
.reveal-pending::after@media print4501
.table-wrap, [data-turnstile-field]@media printScroll containers don't fragment across pages; let them flow.4504

Screen-only chrome and interactions #

SelectorNoteLine
body::after, .skip-link, .site-header nav, #nav-toggle, .drawer-tab, #contact-backdrop, #contact-modal, #intake, .path-card ul, p:has(> button[data-open-contact]), button[data-open-contact], p:has(> a[href$=".pdf"]), p:has(> a.link-quiet[href="index.html"]), .hero-atmos::before, .path-card::before, .how-flow::before, .how-spine, .how-pin, .ba-list::before, .faq-sign, .theme-switch, .theme-orb, .site-footer nav, .site-footer .brand-lockup, .site-footer div:has(> a > .icon)@media print4508

Type and rhythm #

SelectorNoteLine
h1, h2, h3, h4@media print4535
p, li, dd@media print4536
img, svg, figure, tr, .card, .callout, .ba-list > li, .dl-rows > div@media print4537
a[href^="#"], a[href^="tel:"], a[href^="mailto:"]@media print4539
main a[href^="http"]:not(.btn):not([href^="https://coastalhealthcareadvocates.org"])::after@media print4542
.canvas, .canvas-wide@media print4549
main > section@media print4551
.section, .section--tight@media print4552
.ba-mark@media printThe before/after marks are a colored disc with a white glyph; print drops background fills, so redraw them as an ink glyph inside a thin ring.4557
.ba-mark--yes@media print4562
.ba-mark svg@media print4563
.section h2[id$="-h"]::after, .section--tight h2[id$="-h"]::after@media printThe Sky accent rule under section headings is a fill; redraw it as a border.4566

Header #

The logo alone, once, at the top of page one

SelectorNoteLine
.site-header@media print4576
.site-header > .canvas-wide@media print4585
.brand-lockup@media print4586

Hero #

The photo is atmosphere, not content — drop it and let the heading column take the full width.

SelectorNoteLine
#top .grid-9@media print4594
#top .grid-9 > div:nth-of-type(2)@media print4595
.hero-actions, .hero-byline@media print4596

Two entry paths #

SelectorNoteLine
.path-card@media print4600

What is an advocate #

The vignette photo is atmosphere, not content

SelectorNoteLine
.vignette-figure@media print4603
.callout.p-7@media print4604

Services #

Two columns of rows

SelectorNoteLine
.service-rows@media print4607
.service-rows.mt-10@media print4608
.service-row@media print4609
.service-row > .service-icon@media print4616
.service-row > .service-icon .icon-lg@media print4617
.who-photo@media print4618

Who we help #

SelectorNoteLine
.who-list@media print4621
.who-list.mt-10@media print4622
.who-item@media print4623

About #

The portrait is a people photo, not content

SelectorNoteLine
#about .grid-9@media print4626
#about .about-rail@media print4627
#about .about-rail > figure@media print4628
.about-bio .mt-12@media print4629
.dl-rows.mt-8@media print4630
.dl-rows > div@media print4631

How it works #

A flat 2x2 of cards, each keyed by its step color

SelectorNoteLine
.how-flow@media print4634
.how-steps@media print4635
.how-step@media print4641
.how-card@media print4647
.how-card-inner@media print4655
.step-num@media print4656

Pricing #

SelectorNoteLine
.price-choices@media print4659
.price-choice@media print4660
.price-choice-fee@media print4661
.price-choice-cta@media print4662
.price-choice--start@media print4663
.price-table@media print4664
.price-table thead@media print4665
.price-table th, .price-table td@media print4666
.price-group@media print4668

FAQs #

Every answer open

SelectorNoteLine
.faq-layout@media print4671
.faq-aside@media print4672
.faq-more@media print4673
.faq-item@media print4674
.faq-trigger@media print4675
.faq-panel > *@media print4676
.faq-item::details-content@media printA closed <details> keeps its answer in ::details-content, which the browser hides with content-visibility. Kept in a rule of its own: a browser that doesn't know the pseudo-element (before Chrome 131, Safari 18.4 and Firefox 143) drops the whole rule, and prints only the questions.4681

Closing booking panel #

Navy fill becomes a navy outline

SelectorNoteLine
main > #schedule@media print4684
#schedule :is(h2, p, .text-paper, .on-dark-soft)@media print4692
#schedule .schedule-actions@media print4693
#schedule .btn@media print4694
#schedule .btn .icon@media print4707
#schedule a.btn[href*="calendly.com"]::after@media print4708
.calendly-frame, .calendly-loading, .calendly-embed.is-live .calendly-alt@media printThe inline scheduler doesn't print: the fallback link and its address do.4712
.calendly-embed.is-live .calendly-fallback@media print4715

Keyframes

NameNoteLine
beacon-settle1130
rise-inStarts at 0.01, not 0: Chrome skips opacity-0 paints when choosing the Largest Contentful Paint, so a fully transparent hero left Lighthouse with no LCP candidate on slow loads (NO_LCP). 0.01 looks the same.1137
reveal-rise1224
ba-slide-outStarts exactly under Before: one column width plus the 1rem gap.1229
reveal-focus1233
reveal-rule1237
reveal-settle1241
reveal-tick1245
reveal-fade1249
reveal-zoom1253
how-march1534
drawer-in1749
backdrop-in1753
drawer-out1767
backdrop-out1771
lightbox-in1793
lightbox-out1800
beacon-bloom2873
beacon-sweep2878
beacon-arrive2884
beacon-glow-fade2888
fp-highlight3134
fp-draw3138
dl-read3166
tab-arrive3343
maze-route3359

Annotated source

src/input.css as authored, before Tailwind. Line numbers above link here.

/* ============================================================================
   Coastal Healthcare Advocates — source stylesheet
   Build:  npm run build      (watch while developing: npm run dev)
   Output: site/assets/css/site.css        — every page except the style guide
           site/assets/css/style-guide.css — style-guide.html and the generated CSS reference
   Both are git-ignored, generated by scripts/build-css.js from this one file and the
   same tailwind.config.js; only the content scanned for used classes differs, which
   keeps the style guide's .sg-* reference classes out of the stylesheet visitors load.
   ========================================================================== */

/* --- Self-hosted fonts (latin subset). Kept outside @layer so they pass through as-is. --- */
/* Ysabeau is a variable font, so it is declared once with a weight range and serves
   400-600 from a single file. Every other family here is static and single-weight
   (IBM Plex Mono ships 400 and 500 as two separate files). */
@font-face { font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url('../fonts/ibm-plex-mono-400.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: 'IBM Plex Mono'; font-style: normal; font-weight: 500; font-display: swap; src: url('../fonts/ibm-plex-mono-500.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: 'Questrial'; font-style: normal; font-weight: 400; font-display: swap; src: url('../fonts/questrial-400.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: 'Tenor Sans'; font-style: normal; font-weight: 400; font-display: swap; src: url('../fonts/tenor-sans-400.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: 'Ysabeau'; font-style: normal; font-weight: 400 600; font-display: swap; src: url('../fonts/ysabeau-400.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; }

@tailwind base;
@tailwind components;
@tailwind utilities;

/* How far the Sand highlighter has swept across a flagged sample-statement row.
   Registered so it animates smoothly (an unregistered custom property only flips). */
@property --fp-sweep {
  syntax: '<percentage>';
  inherits: true;
  initial-value: 100%;
}

/* ============================================================================
   BASE
   ========================================================================== */
@layer base {
  /* ---- Tokens: verbatim from the Coastal Brand Guidelines artifact (§01, §07) ---- */
  /* .sg-theme-light rides along so the style guide's light panel can show the
     light palette while the page itself is dark. Custom properties inherit from
     the nearest defining ancestor, so the panel's own declarations win inside it. */
  :root, .sg-theme-light {
    /* Palette — identity, never changes */
    --navy: #0F1E4A;
    --sky: #4FACFE;
    --cyan: #00F2FE;
    --seagrass: #0BA678;
    --coral: #FE1E1A;
    --lantern: #E07A00;
    --sand: #FFC93C;
    --midnight: #0A0F24;
    --paper: #F5FBFF;

    /* Semantic — light */
    --bg: #FEFEFE;
    --surface: #FDFEFF;
    --ink: #0F1E4A;
    --ink-soft: #3C4A73;
    --line: #DDE4F2;
    --line-soft: #EAEFF9;
    --tag-bg: #EAF3FF;
    --warn-bg: #EDF4FE;
    --warn-line: #C3D8F2;
    --warn-ink: #2F4468;

    /* Type — the label face as a custom property, for the handful of rules that
       need the family from a var() rather than theme(). Resolved from the Tailwind
       theme so tailwind.config.js stays the single source of truth for the stack. */
    --font-sans: theme('fontFamily.label');

    /* Inline link color — a darkened Sky that clears WCAG AA (6.5:1) on the
       light ground; nav / buttons / footer set their own color and are unaffected. */
    --link: #0B5FA5;

    /* Accent-as-ink: darker palette steps for when an accent carries text or a
       focus ring on the light ground (raw Sky is 2.4:1, Lantern 3.0:1). Each
       clears 4.3:1 on its own 12% wash. Dark theme points them back at the palette. */
    --sky-ink: #1B6AB4;
    --seagrass-ink: #087A58;
    --lantern-ink: #A85A00;
    --coral-ink: #C4140F;
    --ba-after-bg: var(--seagrass-ink);
    --ba-after-ink: #FFFFFF;

    /* Sticky header height: logo + py-4 (2rem) + 1px border. One source for
       scroll-padding and every sticky offset; logo height is set from it below. */
    --logo-h: 3.25rem;
    --header-h: calc(var(--logo-h) + 2rem + 1px);

    --ring: var(--navy);
    --focus-on-dark: var(--sky);

    /* Component roles. Every light/dark difference a component has is a token
       here, so the two dark blocks below are the only place dark is defined. */
    --brand-mark: url('../logomarks/marks/advocate-beacon-primary.svg');
    --brand-ink: var(--navy);
    --brand-sub: var(--sky-ink); /* Sky itself is 2.4:1 on white */
    --brand-foot-mark: url('../logomarks/marks/advocate-beacon-deep-tone-alt.svg');
    --brand-foot-ink: var(--navy);
    --brand-foot-sub: #1E2B5A; /* #2E3F72 was 4.18:1 on the Sky footer */
    --cta-bg: var(--navy);            /* .btn-primary */
    --cta-ink: var(--paper);
    --cta-hover-bg: var(--midnight);
    --cta-hover-filter: none;
    --accent-hover-bg: var(--navy);   /* Sky-at-rest controls: path CTAs, drawer tab */
    --accent-hover-ink: var(--paper);
    --nav-current: var(--navy);
    --path-photo-opacity: 1;
    --choice-accent: var(--sky);
    --theme-icon-light: block;
    --theme-icon-dark: none;

    /* Site footer: a Sky ground with Navy ink in light (Navy 6.6:1, #20305C 5.3:1
       on Sky); Midnight with Paper in dark. */
    --footer-bg: var(--sky);
    --footer-ink: var(--navy);
    --footer-dim: #20305C;
    --footer-title: #20305C;
    --footer-link: var(--navy);
    --footer-accent: var(--navy);
    --footer-accent-ink: var(--paper);
    --footer-sw-off: #20305C;
    --footer-edge: transparent;

    /* Contact drawer + mobile nav ground. In light these resolve to the page's own
       tokens (computed here at :root, so re-pointing them in scope is not circular);
       dark inverts both panels to a Sky ground (Navy on Sky ≈ 6.6:1). */
    --panel-bg: var(--bg);
    --panel-surface: var(--surface);
    --panel-ink: var(--ink);
    --panel-ink-soft: var(--ink-soft);
    --panel-line: var(--line);
    --panel-line-soft: var(--line-soft);
    --panel-tag-bg: var(--tag-bg);
    --panel-link: var(--link);
    --panel-ring: var(--ring);
    --panel-accent: var(--choice-accent);
    --panel-cta-bg: var(--cta-bg);
    --panel-cta-ink: var(--cta-ink);
    --panel-cta-hover-bg: var(--cta-hover-bg);
    --panel-cta-hover-filter: var(--cta-hover-filter);
    color-scheme: light;
  }

  /* Dark: explicit toggle wins in both directions; system pref applies when unset. */
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
      --bg: #0A0F24;
      --surface: #101A3A;
      --ink: #EAF0FF;
      --ink-soft: #BCCCF0;
      --line: #25315C;
      --line-soft: #1B274A;
      --tag-bg: #16234A;
      --warn-bg: #241C0C;
      --warn-line: #4A3A14;
      --warn-ink: #E8C878;
      --link: var(--sky);
      --ring: var(--sky);
      --sky-ink: var(--sky);
      --seagrass-ink: #1FC493;
      --lantern-ink: var(--lantern);
      --coral-ink: #FF6A66;
      --ba-after-bg: var(--seagrass);
      --ba-after-ink: var(--midnight);
      --brand-mark: url('../logomarks/marks/advocate-beacon-dark.svg');
      --brand-ink: #EAF0FF;
      --brand-sub: #6FC3FF;
      --brand-foot-mark: url('../logomarks/marks/advocate-beacon-reversed.svg');
      --brand-foot-ink: var(--paper);
      --brand-foot-sub: var(--cyan);
      --cta-bg: var(--sky);
      --cta-ink: var(--navy);
      --cta-hover-bg: var(--sky);
      --cta-hover-filter: brightness(1.08);
      --accent-hover-bg: var(--paper);
      --accent-hover-ink: var(--navy);
      --nav-current: var(--sky);
      --path-photo-opacity: 0.5;
      --theme-icon-light: none;
      --theme-icon-dark: block;
      --footer-bg: var(--midnight);
      --footer-ink: var(--paper);
      --footer-dim: color-mix(in srgb, var(--paper) 74%, transparent);
      --footer-title: color-mix(in srgb, var(--paper) 80%, transparent);
      --footer-link: var(--sky);
      --footer-accent: var(--sky);
      --footer-accent-ink: var(--navy);
      --footer-sw-off: color-mix(in srgb, var(--paper) 62%, transparent);
      --footer-edge: var(--line);
      --panel-bg: var(--sky);
      --panel-surface: #EAF3FF;
      --panel-ink: var(--navy);
      --panel-ink-soft: #20305C;
      --panel-line: #2E3F72;
      --panel-line-soft: #A9D6FF;
      --panel-tag-bg: #CFE6FF;
      --panel-link: var(--navy);
      --panel-ring: var(--navy);
      --panel-accent: var(--navy);
      --panel-cta-bg: var(--navy);
      --panel-cta-ink: var(--paper);
      --panel-cta-hover-bg: var(--midnight);
      --panel-cta-hover-filter: none;
      color-scheme: dark;
    }
  }
  /* Same values as the media block above — keep the two in lockstep.
     .sg-theme-dark is the style guide's dark panel; it takes these values rather
     than repeating them, so dark is still defined in only two places. */
  :root[data-theme="dark"], .sg-theme-dark {
    --bg: #0A0F24;
    --surface: #101A3A;
    --ink: #EAF0FF;
    --ink-soft: #BCCCF0;
    --line: #25315C;
    --line-soft: #1B274A;
    --tag-bg: #16234A;
    --warn-bg: #241C0C;
    --warn-line: #4A3A14;
    --warn-ink: #E8C878;
    --link: var(--sky);
    --ring: var(--sky);
    --sky-ink: var(--sky);
    --seagrass-ink: #1FC493;
    --lantern-ink: var(--lantern);
    --coral-ink: #FF6A66;
    --ba-after-bg: var(--seagrass);
    --ba-after-ink: var(--midnight);
    --brand-mark: url('../logomarks/marks/advocate-beacon-dark.svg');
    --brand-ink: #EAF0FF;
    --brand-sub: #6FC3FF;
    --brand-foot-mark: url('../logomarks/marks/advocate-beacon-reversed.svg');
    --brand-foot-ink: var(--paper);
    --brand-foot-sub: var(--cyan);
    --cta-bg: var(--sky);
    --cta-ink: var(--navy);
    --cta-hover-bg: var(--sky);
    --cta-hover-filter: brightness(1.08);
    --accent-hover-bg: var(--paper);
    --accent-hover-ink: var(--navy);
    --nav-current: var(--sky);
    --path-photo-opacity: 0.5;
    --theme-icon-light: none;
    --theme-icon-dark: block;
    --footer-bg: var(--midnight);
    --footer-ink: var(--paper);
    --footer-dim: color-mix(in srgb, var(--paper) 74%, transparent);
    --footer-title: color-mix(in srgb, var(--paper) 80%, transparent);
    --footer-link: var(--sky);
    --footer-accent: var(--sky);
    --footer-accent-ink: var(--navy);
    --footer-sw-off: color-mix(in srgb, var(--paper) 62%, transparent);
    --footer-edge: var(--line);
    --panel-bg: var(--sky);
    --panel-surface: #EAF3FF;
    --panel-ink: var(--navy);
    --panel-ink-soft: #20305C;
    --panel-line: #2E3F72;
    --panel-line-soft: #A9D6FF;
    --panel-tag-bg: #CFE6FF;
    --panel-link: var(--navy);
    --panel-ring: var(--navy);
    --panel-accent: var(--navy);
    --panel-cta-bg: var(--navy);
    --panel-cta-ink: var(--paper);
    --panel-cta-hover-bg: var(--midnight);
    --panel-cta-hover-filter: none;
    color-scheme: dark;
  }

  @media (min-width: 480px) { :root { --logo-h: 5.46875rem; } }
  @media (min-width: 1024px) { :root { --logo-h: 6.25rem; } }

  * { min-width: 0; }
  /* Keep even Tailwind's Preflight defaults on the style-guide palette. */
  *, ::before, ::after { border-color: var(--line); }
  ::placeholder { color: var(--ink-soft); opacity: 1; }

  html {
    scroll-padding-top: calc(var(--header-h) + 1rem);
    -webkit-text-size-adjust: 100%;
  }

  /* Text size (Site preferences → data-text on <html>, set by theme.js). Scales
     the root, so every rem-sized thing grows with the text. At Larger and
     Largest (xl / xxl) the page also keeps its phone layout and hamburger menu
     at every width (scripts/postcss/text-layout.js rewrites the width media
     queries); Default and Large keep the normal responsive layout.
     The header bar is zoomed back to its usual size, since a 1.5× logo would
     crowd it; the menu panel inside it is zoomed up again, since it's reading
     matter. Screen only: print sets its own root size. */
  @media screen {
    :root[data-text] {
      font-size: calc(var(--text-scale) * 100%);
      --header-h: calc((var(--logo-h) + 2rem) / var(--text-scale) + 1px);
    }
    :root[data-text="lg"]  { --text-scale: 1.125; --text-unscale: 0.8889; }
    :root[data-text="xl"]  { --text-scale: 1.25;  --text-unscale: 0.8; }
    :root[data-text="xxl"] { --text-scale: 1.5;   --text-unscale: 0.6667; }
    :root[data-text] .site-header { zoom: var(--text-unscale); }
    :root[data-text] .site-header .canvas-wide { max-width: calc(1180px * var(--text-scale)); }
    :root[data-text] #mobile-nav { zoom: var(--text-scale); }
    /* The header's round buttons too: a zoomed-down 44px would miss the
       touch-target minimum, and their icons are read at the chosen size. */
    :root[data-text] .site-header .header-icon-btn { zoom: var(--text-scale); }
    /* The footer lockup is a fixed-geometry logo, not text to read. */
    :root[data-text] .brand-lockup-footer { zoom: var(--text-unscale); }
    /* Long addresses and paths can't wrap at a space. */
    :root[data-text] :is(a, code) { overflow-wrap: anywhere; }
    /* So can a single long word in a big heading ("Responsibility") or a button
       ("Consultation") on a phone, and the style guide's type specimens. */
    :root[data-text] :is(h1, h2, h3, h4) { overflow-wrap: break-word; hyphens: auto; }
    /* A button's label is an anonymous flex item, which only shrinks below its
       longest word with `anywhere`. */
    :root[data-text] .btn { overflow-wrap: anywhere; hyphens: auto; }
    :root[data-text] .sg-specimen > * { overflow-wrap: anywhere; }
  }
  /* Phones and small tablets: the sample statement's three-column table can't
     fit scaled type, so the mock document keeps its usual size there. */
  @media screen and (max-width: 599px) {
    :root[data-text] .fineprint-doc { zoom: var(--text-unscale); }
  }

  body {
    background: var(--bg);
    color: var(--ink);
    font-family: theme('fontFamily.body');
    font-size: 1.125rem;      /* Ysabeau, 16–20px range — 18px for older readers */
    line-height: 1.6;
    font-weight: 400;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
  }

  /* §04 Heading Sizes. All headings = Questrial 400 (matches H1). */
  h1, h2, h3, h4 {
    color: var(--ink);
    font-family: theme('fontFamily.label');
    font-weight: 400;
    letter-spacing: 0.005em;
    text-wrap: balance;
    margin: 0;
  }
  /* Each step is at least 1.25x the one below (body 18px, H4 23px, H3 23-30px,
     H2 30-40px, H1 36-58px), so size alone separates the roles; leading
     tightens as the size grows. */
  h1 {
    font-size: theme('fontSize.h1');
    line-height: 1.08;
    letter-spacing: -0.005em;
  }
  h2 { font-size: theme('fontSize.h2'); line-height: 1.18; }
  h3 { font-size: theme('fontSize.h3'); line-height: 1.25; }
  h4 { font-size: theme('fontSize.h4'); line-height: 1.3; }

  /* Section headings carry a short accent rule beneath them — left-aligned,
     8rem, in Sky. Hidden on the centered navy Schedule heading; re-centered
     under the centered Intake heading. */
  .section h2[id$="-h"]::after,
  .section--tight h2[id$="-h"]::after {
    content: "";
    display: block;
    width: 8rem;
    height: 2px;
    margin-block: 1.5rem;
    background: var(--sky);
    border-radius: 1px;
  }
  #schedule-h::after { display: none; }
  .text-center > h2[id$="-h"]::after { margin-inline: auto; }

  p { text-wrap: pretty; }

  /* Inline / prose links read as links: distinct color + underline. Components
     that are links but shouldn't look like body links (.nav-link, .btn, .link-quiet,
     .site-footer a) all set their own color + text-decoration. */
  a {
    color: var(--link);
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
    text-underline-offset: 0.18em;
    text-decoration-thickness: from-font;
  }
  a:hover { text-decoration-color: currentColor; }
  /* A JS action (opens the Site preferences dialog) mentioned inline in prose,
     styled to match the surrounding text link exactly. */
  .text-link-inline {
    all: unset;
    cursor: pointer;
    color: var(--link);
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
    text-underline-offset: 0.18em;
    text-decoration-thickness: from-font;
  }
  .text-link-inline:hover { text-decoration-color: currentColor; }
  /* Classless links on a dark ground (e.g. the navy Schedule section) need the
     light-on-dark link color instead of the darkened Sky. */
  .on-dark a:not([class]) { color: var(--sky); }

  /* Plain text links that leave the site get a trailing "↗" (e.g. ada.gov).
     Classed links — buttons, icon circles, the GNA badge — carry their own
     icon, and absolute links to our own domain aren't external. Drawn as a
     mask so it takes the link color, in the sprite's 1.75px round stroke.
     Screen only: print swaps ::after for the URL in brackets. */
  @media screen {
    a[href^="http"]:not([class]):not([href*="coastalhealthcareadvocates.org"])::after {
      content: "";
      display: inline-block;
      width: 0.7em;
      height: 0.7em;
      margin-left: 0.2em;
      vertical-align: 0.05em;
      background-color: currentColor;
      -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 18 18 6M8.5 6H18v9.5'/%3E%3C/svg%3E") center / contain no-repeat;
              mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 18 18 6M8.5 6H18v9.5'/%3E%3C/svg%3E") center / contain no-repeat;
    }
  }
  @media screen and (prefers-reduced-motion: no-preference) {
    a[href^="http"]:not([class]):not([href*="coastalhealthcareadvocates.org"])::after {
      transition: transform 180ms cubic-bezier(0.16, 1, 0.3, 1);
    }
    a[href^="http"]:not([class]):not([href*="coastalhealthcareadvocates.org"]):hover::after {
      transform: translate(1px, -1px);
    }
  }

  /* Abbreviations: a quiet dotted underline in running text signals the title
     expansion. Inside links and headings it would stack on the link underline or
     clutter the type, so it's dropped there. */
  abbr[title] {
    text-decoration: underline dotted;
    text-decoration-color: color-mix(in srgb, currentColor 55%, transparent);
    text-underline-offset: 0.18em;
    text-decoration-thickness: from-font;
    cursor: help;
  }
  :where(a, button, h1, h2, h3, h4) abbr[title] { text-decoration: none; cursor: inherit; }

  ::selection { background: rgba(79, 172, 254, 0.28); }

  :where(a, button, input, textarea, select, summary, [tabindex]):focus-visible {
    outline: 3px solid var(--ring);
    outline-offset: 2px;
    border-radius: 3px;
  }
  /* On dark grounds the navy ring vanishes — switch to sky. */
  .on-dark :where(a, button, input, textarea, select, summary, [tabindex]):focus-visible {
    outline-color: var(--focus-on-dark);
  }

  /* Reduced motion: drop spatial movement and choreography, keep the short
     color/opacity feedback that tells a visitor an action registered. */
  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: 0.001ms !important;
      animation-iteration-count: 1 !important;
      animation-delay: 0ms !important;
      transition-duration: 120ms !important;
      transition-property: opacity, color, background-color, border-color,
        outline-color, box-shadow, text-decoration-color, fill, stroke !important;
      scroll-behavior: auto !important;
    }
    .hero-intro [data-rise],
    .hero-intro .hero-atmos::before {
      opacity: 1 !important;
      transform: none !important;
    }
  }

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

/* ============================================================================
   COMPONENTS
   ========================================================================== */
@layer components {
  .canvas {
    width: 100%;
    max-width: theme('maxWidth.canvas');
    margin-inline: auto;
    padding-inline: 1.5rem;
  }
  /* Header / footer breathe a little wider than the 1000px reading column. */
  .canvas-wide {
    width: 100%;
    max-width: 1180px;
    margin-inline: auto;
    padding-inline: 1.5rem;
  }
  @media (min-width: 480px) {
    .canvas, .canvas-wide { padding-inline: 2rem; }
  }

  /* padding-block only — the hero puts .section and .canvas on one element, and a
     `padding: X 0` shorthand there would wipe .canvas's padding-inline. */
  .section { padding-block: clamp(2.25rem, 1.5rem + 3.25vw, 4rem); }
  .section--tight { padding-block: clamp(2rem, 1.4rem + 2vw, 3rem); }

  /* §Type: .lede — Ysabeau 21px / 1.45, --ink-soft, max 60ch */
  .lede {
    font-size: theme('fontSize.lede');
    line-height: 1.45;
    color: var(--ink-soft);
    max-width: 60ch;
  }

  .prose-measure { max-width: theme('maxWidth.measure'); }
  .prose-measure > * + * { margin-top: 1.1em; }

  /* Small supporting text. Body typeface — IBM Plex Mono is reserved for
     actual figures (see .num, .price-fee, .step-num). */
  .meta {
    font-family: theme('fontFamily.body');
    font-size: 0.9375rem;
    letter-spacing: 0.01em;
    color: var(--ink-soft);
  }

  /* Figures: prices, counts, code values. IBM Plex Mono otherwise runs only in
     form error messages (.field-error, .error-summary). Phone numbers stay in
     the body face — Mono's full-width space and brackets read as gaps there. */
  .num {
    font-family: theme('fontFamily.mono');
    font-feature-settings: 'tnum' 1;
    letter-spacing: 0.01em;
  }
  /* File paths and token names have no spaces to wrap at; let them break
     anywhere rather than push a phone-width page sideways. */
  code { overflow-wrap: anywhere; }

  /* --- Buttons --- */
  .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    min-height: 48px;
    /* A label stays on one line wherever it fits; on a narrow phone or at the
       larger text sizes it wraps inside the button instead of spilling past it. */
    text-align: center;
    text-wrap: balance;
    padding: 0.75rem 1.4rem;
    font-family: theme('fontFamily.label');
    font-size: 0.9375rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    text-decoration: none;
    border-radius: theme('borderRadius.card');
    border: 1px solid transparent;
    cursor: pointer;
    transition: transform 0.18s theme('transitionTimingFunction.calm'),
                background-color 0.18s ease, border-color 0.18s ease, color 0.18s ease;
  }
  /* A hair of lift on hover; the press (:active, below in source so it wins) dips
     back down. Reduced motion drops the transform via the global rule. */
  .btn:hover { transform: translateY(-1px); }
  .btn:active { transform: translateY(1px); }

  /* Primary CTA — Navy fill, Paper text on light. On dark the page ground is
     near-navy, so the CTA flips to Sky fill / Navy text to stay legible. */
  .btn-primary {
    background: var(--cta-bg);
    color: var(--cta-ink);
    border-color: var(--cta-bg);
  }
  .btn-primary:hover {
    background: var(--cta-hover-bg);
    border-color: var(--cta-hover-bg);
    filter: var(--cta-hover-filter);
  }

  .btn-ghost {
    background: transparent;
    color: var(--ink);
    border-color: var(--ink);
  }
  .btn-ghost:hover { background: var(--tag-bg); }

  .btn-on-dark {
    background: var(--paper);
    color: var(--navy);
    border-color: var(--paper);
  }
  .btn-on-dark:hover { filter: brightness(1.05); }

  .btn-ghost-on-dark {
    background: transparent;
    color: var(--paper);
    border-color: color-mix(in srgb, var(--paper) 55%, transparent);
  }
  .btn-ghost-on-dark:hover {
    background: color-mix(in srgb, var(--paper) 14%, transparent);
    border-color: var(--paper);
  }

  .link-quiet {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-family: theme('fontFamily.label');
    font-size: 0.9375rem;
    letter-spacing: 0.02em;
    color: var(--ink);
    text-decoration: underline;
    text-decoration-color: var(--ink-soft);
  }
  .link-quiet:hover { text-decoration-color: currentColor; }
  /* A trailing icon (the "→" on "Talk about referrals")
     nudges in its direction of travel on hover. Leading icons — the PDF marks —
     are :first-child, so :last-child leaves them alone. */
  .link-quiet svg { transition: transform 180ms cubic-bezier(0.16, 1, 0.3, 1); }
  .link-quiet:hover svg:last-child { transform: translateX(2px); }
  /* "← Go back" (secondary.js): a smaller leading arrow that nudges left, its
     direction of travel. Same specificity as the rule above, so it wins by order. */
  .link-quiet svg.icon-back { width: 1rem; height: 1rem; }
  .link-quiet:hover svg.icon-back { transform: translateX(-2px); }

  /* --- Surfaces --- */
  .card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: theme('borderRadius.card');
    box-shadow: theme('boxShadow.card');
  }
  .tile {
    background: var(--tag-bg);
    border: 1px solid var(--line);
    border-radius: theme('borderRadius.tile');
    transition: border-color 0.2s ease,
                transform 0.2s cubic-bezier(0.16, 1, 0.3, 1),
                box-shadow 0.2s ease;
  }
  /* Hover acknowledges the pointer with the same small lift the how-cards use;
     under reduced motion the global rule drops the transform and keeps the
     border + elevation, so the feedback still reads. */
  .tile:hover {
    border-color: var(--sky);
    transform: translateY(-3px);
    box-shadow: theme('boxShadow.lift');
  }
  /* Headings inside a card/tile drop the tracking for readability at small sizes. */
  :is(.card, .tile, .how-card-inner) :is(h2, h3, h4) {
    letter-spacing: 0;
  }
  .callout {
    background: var(--tag-bg);
    border: 1px solid var(--line);
    border-radius: theme('borderRadius.card');
  }

  /* Sizing only. Stroke vs. fill is set per-symbol in the sprite so the two
     solid brand glyphs (LinkedIn, Facebook) stay filled. */
  .icon { width: 1.5rem; height: 1.5rem; flex: none; }
  .icon-lg { width: 2rem; height: 2rem; flex: none; }
  .icon-pdf { color: var(--coral-ink); }


  /* --- Wide content scrolls inside its own bordered container --- */
  .table-wrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    border: 1px solid var(--line);
    border-radius: theme('borderRadius.card');
    background: var(--surface);
  }

  /* --- Pricing choices: the four ways in, above the detailed table --- */
  .price-choices {
    display: grid;
    gap: 1rem;
    list-style: none;
    padding: 0;
  }
  @media (min-width: 40rem) { .price-choices { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
  .price-choice {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 1.35rem 1.25rem 1.4rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: theme('borderRadius.card');
  }
  .price-choice h3 { letter-spacing: 0; }
  .price-choice p { color: var(--ink); }
  /* The free call is where everyone should begin: tinted ground and a sky rule. */
  .price-choice--start {
    background: var(--tag-bg);
    border-top: 3px solid var(--sky);
  }
  .price-choice-kicker {
    font-family: theme('fontFamily.label');
    font-size: 0.75rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
  }
  .price-choice .price-choice-kicker { color: var(--ink-soft); }
  .price-choice-fee {
    font-family: theme('fontFamily.mono');
    font-feature-settings: 'tnum' 1;
    font-size: 1.5rem;
    line-height: 1.2;
    margin-block: 0.15rem 0.1rem;
  }
  .price-choice-fee--free {
    font-family: theme('fontFamily.label');
    letter-spacing: 0.06em;
    text-transform: uppercase;
  }
  .price-choice-cta { margin-top: auto; align-self: flex-start; }

  /* --- Pricing table --- */
  .price-table {
    width: 100%;
    min-width: 32rem;
    border-collapse: collapse;
    font-size: 1rem;
  }
  .price-table th,
  .price-table td {
    text-align: left;
    padding: 0.95rem 1.15rem;
    vertical-align: top;
  }
  .price-table thead th {
    font-family: theme('fontFamily.label');
    font-size: 0.8125rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-soft);
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
  }
  .price-table thead th:last-child { text-align: right; }
  .price-col-name { width: 24%; }
  .price-col-fee { width: 8.5rem; }

  .price-group {
    font-family: theme('fontFamily.label');
    font-size: 0.75rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-soft);
    background: var(--tag-bg);
    padding-top: 0.6rem;
    padding-bottom: 0.6rem;
    border-top: 1px solid var(--line);
  }
  .price-table tbody:first-of-type .price-group { border-top: 0; }

  .price-table tbody td,
  .price-table tbody th[scope="row"] { border-top: 1px solid var(--line-soft); }

  .price-table th[scope="row"] {
    font-family: theme('fontFamily.label');
    font-weight: 400;
    font-size: 1.0625rem;
    color: var(--ink);
  }
  .price-table td { color: var(--ink); font-size: 1rem; } /* full-strength ink for older readers */

  .price-table .price-fee {
    font-family: theme('fontFamily.mono');
    font-feature-settings: 'tnum' 1;
    color: var(--ink);
    white-space: nowrap;
    text-align: right;
  }
  .price-table td.price-fee--free {
    font-family: theme('fontFamily.label');
    font-weight: 400; /* Questrial ships 400 only — no faux bold */
    letter-spacing: 0.06em;
    text-transform: uppercase;
  }
  .price-unit {
    font-family: theme('fontFamily.body');
    font-size: 0.875rem;
    color: var(--ink-soft);
  }

  /* "Fee:" label for the stacked layout — a real, visually-shown span (not CSS
     content), hidden while the Fee column header is present. */
  .price-fee-label { display: none; }

  /* Below 40rem three columns can't fit, so each service stacks into its own
     block instead of forcing a horizontal scroll that hides the fee. The table
     elements carry explicit ARIA table roles in index.html, so display:block
     here doesn't strip the row/header relationships from assistive tech. */
  @media (max-width: 40rem) {
    .table-wrap:has(.price-table) { overflow-x: visible; }
    .price-table { min-width: 0; }
    .price-table colgroup,
    .price-table thead { display: none; }
    .price-table tbody,
    .price-table tr,
    .price-table th,
    .price-table td { display: block; }

    .price-table tr:has(th[scope="row"]) { padding: 0.85rem 1.15rem; }
    .price-table tr:has(th[scope="row"]) + tr:has(th[scope="row"]) {
      border-top: 1px solid var(--line-soft);
    }
    .price-table tbody th[scope="row"],
    .price-table tbody td { border-top: 0; padding: 0; }

    .price-table td { margin-top: 0.4rem; }
    .price-table td.price-fee {
      text-align: left;
      margin-top: 0.6rem;
    }
    .price-fee-label {
      display: inline;
      font-family: theme('fontFamily.label');
      font-size: 0.75rem;
      letter-spacing: 0.1em;
      text-transform: uppercase;
      color: var(--ink-soft);
    }
  }

  /* --- Definition-list rows (bio credentials) --- */
  .dl-rows { border-top: 1px solid var(--line); }
  .dl-rows > div {
    display: grid;
    gap: 0.15rem 1.5rem;
    padding: 0.95rem 0;
    border-bottom: 1px solid var(--line);
  }
  .dl-rows > div:first-child { padding-top: 1.1rem; }
  @media (min-width: 768px) {
    .dl-rows > div { grid-template-columns: 8.5rem 1fr; }
  }
  .dl-rows dt {
    font-family: theme('fontFamily.label');
    font-size: 0.8125rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-soft);
  }
  .dl-rows dd { margin: 0; }

  /* --- Header --- */
  .site-header {
    position: sticky;
    top: 0;
    z-index: 40;
    background: color-mix(in srgb, var(--bg) 92%, transparent);
    border-bottom: 1px solid var(--line);
    backdrop-filter: saturate(1.4) blur(8px);
    transition: transform 0.32s theme('transitionTimingFunction.calm'),
                opacity 0.28s ease, box-shadow 0.28s ease;
  }
  @supports not (backdrop-filter: blur(1px)) {
    .site-header { background: var(--bg); }
  }
  /* Picks up a soft elevation once the page has scrolled off the top, so the
     bar separates from the content passing under it (main.js toggles .scrolled).
     On the near-black dark ground this shadow is invisible — the border carries
     it there — which is fine. */
  .site-header.scrolled { box-shadow: 0 4px 16px rgba(10, 15, 36, 0.07); }
  /* Retreats as the visitor nears the closing CTA / footer (main.js). */
  /* visibility (delayed to the end of the slide) takes the links out of the tab
     order, so keyboard focus can never land on an off-screen nav. */
  .site-header.header-hidden {
    transform: translateY(-100%);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: transform 0.32s theme('transitionTimingFunction.calm'),
                opacity 0.28s ease, box-shadow 0.28s ease, visibility 0s linear 0.32s;
  }

  /* Logo — the horizontal lockup rebuilt with live type, so the company name is
     real text (reader mode, translation, copy, search) rather than an image.
     Only the mark is a picture, CSS-swapped by theme (no flash, no JS). Every
     length is in --u, one unit of the outlined SVG's 271.27 × 82 artboard, so
     the lockup scales from --logo-h and matches logo-horizontal-*.svg: mark at
     x 8, rule at x 79, wordmark at x 91.1 with baselines 40.22 and 61.22.
     --u multiplies lengths instead of setting a tiny em base, so a browser
     minimum-font-size preference can't blow the geometry up. */
  .brand-lockup {
    --u: calc(var(--logo-h) / 82); /* --logo-h: 3.25rem on phones, stepped up at 480 / 1024 (:root) */
    --lk-mark: var(--brand-mark);
    --lk-ink: var(--brand-ink);
    --lk-sub: var(--brand-sub);
    display: flex;
    align-items: flex-start;
    height: var(--logo-h);
    width: calc(271.27 * var(--u));
    padding-left: calc(8 * var(--u));
    white-space: nowrap;
    text-decoration: none;
  }
  /* The Advocate Beacon: the mark files are 120 × 132, drawn at half size. */
  .brand-lockup::before {
    content: "";
    flex: none;
    width: calc(60 * var(--u));
    height: calc(66 * var(--u));
    margin-top: calc(8 * var(--u));
    background: var(--lk-mark) center / contain no-repeat;
  }
  /* The rule between mark and wordmark: wordmark ink at 28%. */
  .brand-lockup::after {
    content: "";
    order: 1;
    flex: none;
    width: calc(1.1 * var(--u));
    height: calc(58.08 * var(--u));
    margin: calc(11.96 * var(--u)) 0 0 calc(11 * var(--u));
    background: var(--lk-ink);
    opacity: 0.28;
  }
  /* Line boxes are 1em tall; the offsets put each baseline where the SVG has it
     (Tenor Sans ascent .92 / descent .25; Ysabeau typo metrics 1.054 / .289). */
  .brand-lockup-name {
    order: 2;
    display: flex;
    flex-direction: column;
    margin-left: calc(11 * var(--u));
    padding-top: calc(17.675 * var(--u));
    line-height: 1;
    text-transform: uppercase;
    font-kerning: none; /* the outlined wordmark is set unkerned */
  }
  .brand-lockup-name > :first-child {
    font-family: theme('fontFamily.logo');
    font-weight: 400;
    font-size: calc(27 * var(--u));
    letter-spacing: 0.13em;
    color: var(--lk-ink);
  }
  .brand-lockup-name > :last-child {
    margin-top: calc(4.631 * var(--u));
    font-family: theme('fontFamily.body');
    font-weight: 500;
    font-size: calc(13.5 * var(--u));
    color: var(--lk-sub);
  }
  /* Footer colorway: deep-tone-alt on the light footer, reversed on the dark. */
  .brand-lockup-footer {
    --logo-h: 5rem;
    --lk-mark: var(--brand-foot-mark);
    --lk-ink: var(--brand-foot-ink);
    --lk-sub: var(--brand-foot-sub);
  }

  .header-icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: 999px;
    border: 1px solid var(--line);
    background: transparent;
    color: var(--ink);
    cursor: pointer;
  }
  .header-icon-btn:hover { background: var(--tag-bg); }
  .header-icon-btn svg { transition: transform 220ms cubic-bezier(0.16, 1, 0.3, 1); }
  /* The hamburger wears the menu panel's tokens so it matches the panel it opens:
     page colors in light mode, a Sky fill with Navy lines in dark mode. */
  #nav-toggle {
    background: var(--panel-bg);
    border-color: var(--panel-line);
    color: var(--panel-ink);
  }
  #nav-toggle:hover { background: var(--panel-tag-bg); }
  #nav-toggle[aria-expanded="true"] svg { transform: rotate(90deg); }

  /* Mobile menu — a compact panel hung off the toggle button, not a full-width
     sheet. Its containing block is the .relative wrapper around the button, so
     it sits right under the hamburger. JS unhides it, then adds .is-open a
     frame later to run the entrance. */
  #mobile-nav,
  #contact-modal:not(.is-lightbox) {
    --bg: var(--panel-bg);
    --surface: var(--panel-surface);
    --ink: var(--panel-ink);
    --ink-soft: var(--panel-ink-soft);
    /* Scrollspy's "current" color (--nav-current) is a page-level token that
       tracks --bg's contrast pair, not this panel's inverted one — in dark
       theme it resolves to Sky, same as --panel-bg, so the current link's
       text matched its own background and vanished. Re-point it at the ink
       color already proven readable on --panel-bg. */
    --nav-current: var(--panel-ink);
    --line: var(--panel-line);
    --line-soft: var(--panel-line-soft);
    --tag-bg: var(--panel-tag-bg);
    --link: var(--panel-link);
    --ring: var(--panel-ring);
    --choice-accent: var(--panel-accent);
    --cta-bg: var(--panel-cta-bg);
    --cta-ink: var(--panel-cta-ink);
    --cta-hover-bg: var(--panel-cta-hover-bg);
    --cta-hover-filter: var(--panel-cta-hover-filter);
  }
  #mobile-nav {
    position: absolute;
    top: 100%;
    right: 0;
    z-index: 50;
    width: max-content;
    min-width: 11rem;
    max-width: calc(100vw - 3rem);
    margin-top: 0.4rem;
    padding: 0.4rem;
    max-height: calc(100dvh - 6rem);
    overflow-y: auto;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: 15px; /* rounded.panel-inner */
    box-shadow: 0 18px 44px -14px rgba(10, 15, 36, 0.32);
    transform-origin: top right;
    opacity: 0;
    transform: translateY(-8px) scale(0.94);
    transition: opacity 160ms ease, transform 260ms cubic-bezier(0.16, 1, 0.3, 1);
  }
  #mobile-nav.is-open { opacity: 1; transform: translateY(0) scale(1); }
  #mobile-nav .nav-link {
    display: block;
    padding: 0.6rem 0.85rem;
    border-radius: 8px; /* rounded.field */
    white-space: nowrap;
  }
  #mobile-nav .nav-link::after { content: none; } /* no underline-wipe inside the panel */
  #mobile-nav .nav-link:hover,
  #mobile-nav .nav-link:focus-visible { background: var(--tag-bg); }
  @media (prefers-reduced-motion: reduce) {
    #mobile-nav { transition: opacity 120ms ease; transform: none; }
    #mobile-nav.is-open { transform: none; }
  }

  /* Primary desktop nav — four links (Services, About, How it works,
     Pricing), sized up from the mobile-panel links' 1rem to 1.125rem since
     dropping "Who we help" and FAQs left room to size up. It appears at the
     `nav` screen (tailwind.config.js); below that the hamburger takes over —
     re-measure that breakpoint if the links, their size, the icons or the
     logo change again. */
  nav[aria-label="Primary"] {
    gap: 1.5rem;
  }
  nav[aria-label="Primary"] .nav-link {
    font-size: 1.125rem;
  }

  .theme-icon-light { display: var(--theme-icon-light); }
  .theme-icon-dark { display: var(--theme-icon-dark); }

  .nav-link {
    position: relative;
    font-family: theme('fontFamily.label');
    font-size: 1rem;
    letter-spacing: 0.02em;
    color: var(--ink);
    text-decoration: none;
    padding: 0.5rem 0.15rem 0.55rem;
    white-space: nowrap;
  }
  /* underline wipes in from the left on hover / keyboard focus */
  .nav-link::after {
    content: "";
    position: absolute;
    left: 0.15rem;
    right: 0.15rem;
    bottom: 0.35rem;
    height: 2px;
    background: var(--sky);
    transform: scaleX(0);
    transform-origin: left;
    transition: transform 220ms cubic-bezier(0.16, 1, 0.3, 1);
  }
  .nav-link:hover::after,
  .nav-link:focus-visible::after { transform: scaleX(1); }

  /* Scrollspy — the link for the section currently under the viewport
     holds its underline and takes the accent color. */
  .nav-link[aria-current="true"] { color: var(--nav-current); }
  .nav-link[aria-current="true"]::after { transform: scaleX(1); }

  /* --- Skip link --- */
  .skip-link {
    position: absolute;
    left: 1rem;
    top: -3.5rem;
    z-index: 60;
    background: var(--navy);
    color: var(--paper);
    padding: 0.75rem 1rem;
    border-radius: 0 0 0.5rem 0.5rem;
    transition: top 0.15s ease;
  }
  .skip-link:focus { top: 0; }

  /* --- Hero atmosphere: faint horizon + beacon glow, all decorative --- */
  .hero-atmos {
    position: relative;
    isolation: isolate;
    overflow: clip;
  }
  .hero-atmos::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    transform-origin: 78% 12%;
    background:
      radial-gradient(60% 55% at 78% 18%, rgba(79, 172, 254, 0.16), transparent 70%),
      radial-gradient(45% 40% at 12% 0%, rgba(0, 242, 254, 0.10), transparent 70%),
      linear-gradient(180deg, transparent 62%, var(--tag-bg) 100%);
  }

  /* ==========================================================================
     MOTION — one authored moment: the hero steadies as the beacon comes up,
     once per session. Everything else is quiet feedback and continuity.
     ======================================================================== */

  /* Focal entrance (JS-gated; content is fully visible without it). */
  .js.hero-intro .hero-atmos::before {
    animation: beacon-settle 1200ms cubic-bezier(0.16, 1, 0.3, 1) both;
  }
  .js.hero-intro [data-rise] {
    animation: rise-in 640ms cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: calc(var(--i, 0) * 90ms + 60ms);
  }
  @keyframes beacon-settle {
    from { opacity: 0; transform: scale(1.05); }
    to   { opacity: 1; transform: none; }
  }
  /* Starts at 0.01, not 0: Chrome skips opacity-0 paints when choosing the
     Largest Contentful Paint, so a fully transparent hero left Lighthouse with
     no LCP candidate on slow loads (NO_LCP). 0.01 looks the same. */
  @keyframes rise-in {
    from { opacity: 0.01; transform: translateY(32px); }
    to   { opacity: 1; transform: none; }
  }

  /* Scroll-in: "coming into the light". Each [data-reveal] element plays once,
     on its own, as it crosses into view, so a tall section unfolds as the
     visitor reads down it rather than all at once at its top edge. Headings
     clear from a faint haze and their Beacon Sky rule sweeps out like a beam;
     the content under them follows. The value picks the treatment:
       (bare)   the element rises in
       heading  soft focus-in, then the accent rule draws out from its start
       list     direct children rise in turn; their line icons settle after
       pair     two side-by-side cards drift in from their own sides
       checks   list items rise in turn and each tick draws itself left to right
       rows     table rows fade up in turn
       photo    fades in while the picture eases back from a slight zoom
     main.js arms .reveal-pending (the hidden start state), swaps in .reveal-in
     to play, then clears it; it also writes --reveal-i on staggered items.
     Fill mode is `backwards`, so once an animation ends the element is back on
     its own styles and hover lifts work immediately. No JS, a failed script, or
     reduced motion: nothing is armed, nothing is hidden. Opacity starts at 0.01,
     not 0 — see rise-in. */
  @media (prefers-reduced-motion: no-preference) {
    [data-reveal] { --reveal-ease: cubic-bezier(0.16, 1, 0.3, 1); }

    .js :is([data-reveal=""], [data-reveal="heading"], [data-reveal="photo"]).reveal-pending,
    .js :is([data-reveal="list"], [data-reveal="pair"]).reveal-pending > *,
    .js [data-reveal="checks"].reveal-pending > li,
    .js [data-reveal="rows"].reveal-pending tbody tr { opacity: 0.01; }
    .js [data-reveal="heading"].reveal-pending::after { transform: scaleX(0); }

    .js [data-reveal=""].reveal-in {
      animation: reveal-rise 720ms var(--reveal-ease) 60ms backwards;
    }

    .js [data-reveal="heading"].reveal-in {
      animation: reveal-focus 820ms var(--reveal-ease) backwards;
    }
    .js [data-reveal="heading"].reveal-in::after {
      transform-origin: left center;
      animation: reveal-rule 1000ms var(--reveal-ease) 260ms backwards;
    }
    .js .text-center > [data-reveal="heading"].reveal-in::after { transform-origin: center; }

    .js :is([data-reveal="list"], [data-reveal="pair"]).reveal-in > *,
    .js [data-reveal="checks"].reveal-in > li {
      animation: reveal-rise 720ms var(--reveal-ease) backwards;
      animation-delay: calc(var(--reveal-i, 0) * 75ms);
    }
    /* Line icons settle a beat after their row arrives. */
    .js [data-reveal="list"].reveal-in > * > :is(.service-icon, .icon-lg, .icon:first-child) {
      animation: reveal-settle 640ms var(--reveal-ease) backwards;
      animation-delay: calc(var(--reveal-i, 0) * 75ms + 180ms);
    }
    .js [data-reveal="checks"].reveal-in > li > .icon {
      animation: reveal-tick 520ms var(--reveal-ease) backwards;
      animation-delay: calc(var(--reveal-i, 0) * 75ms + 280ms);
    }

    /* The two entry paths come in from their own sides; stacked, they rise. */
    @media (min-width: 640px) {
      .js [data-reveal="pair"].reveal-in > :first-child { --reveal-x: -28px; --reveal-y: 0px; }
      .js [data-reveal="pair"].reveal-in > :last-child  { --reveal-x: 28px;  --reveal-y: 0px; }

      /* Before/after: once Before has landed, After slides out from beneath it
         into its own column, the fix drawn out from under the problem. It waits
         hidden (visibility, not opacity) so it can't show through Before while
         Before is still fading in; Before sits above it for the whole move. */
      .js .ba-grid[data-reveal="pair"].reveal-in > .ba-panel--after {
        animation: ba-slide-out 980ms var(--reveal-ease) 420ms backwards;
      }
    }

    .js [data-reveal="rows"].reveal-in tbody tr {
      animation: reveal-rise 560ms var(--reveal-ease) backwards;
      animation-delay: calc(var(--reveal-i, 0) * 55ms + 120ms);
      --reveal-y: 6px;
    }

    .js [data-reveal="photo"].reveal-in {
      animation: reveal-fade 900ms ease-out backwards;
    }
    .js [data-reveal="photo"].reveal-in img {
      animation: reveal-zoom 1400ms var(--reveal-ease) backwards;
    }

    @keyframes reveal-rise {
      from { opacity: 0.01; transform: translate(var(--reveal-x, 0px), var(--reveal-y, 18px)); }
      to   { opacity: 1; transform: none; }
    }
    /* Starts exactly under Before: one column width plus the 1rem gap. */
    @keyframes ba-slide-out {
      from { visibility: hidden; transform: translateX(calc(-100% - 1rem)); }
      to   { visibility: visible; transform: none; }
    }
    @keyframes reveal-focus {
      from { opacity: 0.01; transform: translateY(8px); filter: blur(5px); }
      to   { opacity: 1; transform: none; filter: none; }
    }
    @keyframes reveal-rule {
      from { transform: scaleX(0); }
      to   { transform: none; }
    }
    @keyframes reveal-settle {
      from { opacity: 0.01; transform: scale(0.8); }
      to   { opacity: 1; transform: none; }
    }
    @keyframes reveal-tick {
      from { clip-path: inset(0 100% 0 0); }
      to   { clip-path: inset(0); }
    }
    @keyframes reveal-fade {
      from { opacity: 0.01; }
      to   { opacity: 1; }
    }
    @keyframes reveal-zoom {
      from { transform: scale(1.06); }
      to   { transform: none; }
    }
  }

  /* Continuity: light↔dark blends like dusk, but only during an explicit
     toggle. Where the browser has View Transitions, theme.js runs the switch
     inside one and these two rules time the cross-fade; the second lets a
     press through to the page while it runs, so a quick second toggle isn't
     swallowed. They hold no class on purpose: nothing else on the site starts
     a view transition. */
  ::view-transition-group(root),
  ::view-transition-old(root),
  ::view-transition-new(root) { animation-duration: 280ms; }
  ::view-transition { pointer-events: none; }
  /* The fallback for browsers without them: the .theme-transition class is
     added for ~320ms by theme.js. Every element animating its own colors is
     far more work per frame, which is why it is only the fallback.
     Reduced-motion visitors get the instant swap they had before. */
  @media (prefers-reduced-motion: no-preference) {
    .theme-transition, .theme-transition * {
      transition: background-color 280ms ease, border-color 280ms ease,
                  color 280ms ease !important;
    }
    .theme-switch-ico { transition: color 0.2s ease, transform 260ms cubic-bezier(0.16, 1, 0.3, 1); }
    /* The crossfade above flattens every transition to color-only while it
       runs, which would snap the switch knob and the header orb's swipe. */
    .theme-transition :is(.theme-switch-knob, .theme-orb-ico) {
      transition: left 0.24s cubic-bezier(0.16, 1, 0.3, 1),
                  transform 320ms cubic-bezier(0.16, 1, 0.3, 1),
                  background-color 280ms ease, color 280ms ease !important;
    }
  }
  .theme-switch:active .theme-switch-ico { transform: rotate(-20deg); }

  .framed {
    border-radius: theme('borderRadius.card');
    border: 1px solid var(--line);
    box-shadow: theme('boxShadow.lift');
    overflow: hidden;
    background: var(--surface);
  }

  /* Soft-edged photo that sits beside the "what is an advocate" intro copy.
     The radial mask feathers every edge so it reads as atmosphere behind the
     text rather than a placed, framed asset. It floats right at every width so
     the copy wraps around it; on extremely narrow screens there is no room for
     a readable text column beside it, so it is hidden. .what-intro carries the
     clearfix. */
  .what-intro::after { content: ""; display: block; clear: both; }
  .vignette-figure {
    float: right;
    width: 44%;
    max-width: 288px;
    margin: 0.25rem 0 1rem 1.25rem;
  }
  /* Sizing/loading is handled the hero way — a <picture> with 640w/1024w
     sources and sizes="(min-width:1024px) 40vw, 100vw", plus .block/.h-auto/
     .w-full utilities on the <img>. This rule only adds the vignette skin. */
  .vignette-figure img {
    border-radius: theme('borderRadius.card');
    -webkit-mask-image: radial-gradient(82% 96% at 52% 44%, #000 38%, transparent 100%);
            mask-image: radial-gradient(82% 96% at 52% 44%, #000 38%, transparent 100%);
  }
  /* Extremely narrow: drop it — a floated image would leave the copy in a
     column too thin to read. */
  @media (max-width: 400px) {
    .vignette-figure { display: none; }
  }
  @media (min-width: 768px) {
    .vignette-figure { width: 288px; margin: 0.25rem 0 1rem 2rem; }
  }
  @media (min-width: 1024px) {
    .vignette-figure { width: 340px; max-width: none; margin-left: 2.5rem; margin-bottom: 2rem; }
  }
  /* From 1024px the photo stands 1rem taller than the copy beside it, which left
     it half the air above the callout that the copy has. The callout clears the
     float, so the photo's own 2rem bottom margin is what holds it off. */
  .what-intro > .callout { clear: both; }

  /* --- Path cards (the two entry paths) --- */
  .path-card { display: flex; flex-direction: column; gap: 1rem; padding: 1.75rem; height: 100%; position: relative; }

  /* Calm full-bleed photo behind the whole card under a heavy surface wash,
     so navy body copy keeps its contrast. Decorative; images are same-origin. */
  .path-card::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: theme('borderRadius.card');
    background:
      linear-gradient(
        to bottom,
        color-mix(in srgb, var(--surface) 68%, transparent) 0%,
        color-mix(in srgb, var(--surface) 80%, transparent) 42%,
        color-mix(in srgb, var(--surface) 90%, transparent) 100%
      ),
      var(--path-card-bg, none) center / cover no-repeat;
    z-index: 0;
    pointer-events: none;
  }

  .path-card > * { position: relative; z-index: 1; }

  /* Path CTAs read as light blue at rest in both themes, matching the
     "Get Started" tab; hover deepens to navy in light mode and lifts to
     white in dark mode. */
  .path-card .btn-ghost {
    background: var(--sky);
    color: var(--navy);
    border-color: var(--sky);
  }
  .path-card .btn-ghost:hover {
    background: var(--accent-hover-bg);
    color: var(--accent-hover-ink);
    border-color: var(--accent-hover-bg);
  }

  /* JPEG is the safe fallback; @supports upgrades to AVIF/WebP where the
     image-set() type() syntax is understood (Chrome 113+, Safari 17+, FF 89+). */
  .path-card--crisis   { --path-card-bg: url('../img/path-crisis-960.jpg'); }
  .path-card--referrer { --path-card-bg: url('../img/path-referrer-960.jpg'); }

  @supports (background-image: image-set(url('x.webp') type('image/webp'))) {
    .path-card--crisis {
      --path-card-bg: image-set(
        url('../img/path-crisis-960.avif') type('image/avif'),
        url('../img/path-crisis-960.webp') type('image/webp'),
        url('../img/path-crisis-960.jpg') type('image/jpeg')
      );
    }
    .path-card--referrer {
      --path-card-bg: image-set(
        url('../img/path-referrer-960.avif') type('image/avif'),
        url('../img/path-referrer-960.webp') type('image/webp'),
        url('../img/path-referrer-960.jpg') type('image/jpeg')
      );
    }
  }

  .path-card::before { opacity: var(--path-photo-opacity); }

  @media (prefers-contrast: more) {
    .path-card::before { display: none; }
  }

  /* --- How it works: pinned note-cards on ruled paper, joined by a
     marching-ants dashed thread (adapted from a 21st.dev "how-it-works") --- */
  .step-num {
    font-family: theme('fontFamily.logo');
    font-size: 2.75rem;
    line-height: 1;
    font-weight: 400;
    letter-spacing: 0.02em;
    color: var(--how-ink, var(--sky-ink));
  }

  .how-flow { position: relative; margin-top: 2.5rem; }

  /* faint notebook rule behind the cards, faded out at both edges */
  .how-flow::before {
    content: "";
    position: absolute;
    inset: -1.5rem 0;
    background-image: linear-gradient(var(--ink) 1px, transparent 1px);
    background-size: 100% 2rem;
    opacity: 0.05;
    pointer-events: none;
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
            mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
  }

  .how-steps {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1.75rem;
  }
  /* per-step accent + inner-panel wash: palette tints mixed 9–12% into the surface */
  /* --how-accent tints pins + borders; --how-ink is the contrast-safe step for the numerals. */
  .how-steps > :nth-child(1) { --how-accent: var(--sky);      --how-ink: var(--sky-ink);      --how-wash: color-mix(in srgb, var(--sky)      12%, var(--surface)); }
  .how-steps > :nth-child(2) { --how-accent: var(--seagrass); --how-ink: var(--seagrass-ink); --how-wash: color-mix(in srgb, var(--seagrass) 12%, var(--surface)); }
  .how-steps > :nth-child(3) { --how-accent: var(--lantern);  --how-ink: var(--lantern-ink);  --how-wash: color-mix(in srgb, var(--lantern)  12%, var(--surface)); }
  .how-steps > :nth-child(4) { --how-accent: var(--coral);    --how-ink: var(--coral-ink);    --how-wash: color-mix(in srgb, var(--coral)     9%, var(--surface)); }

  .how-step { position: relative; width: 100%; max-width: 22rem; }

  /* white "photo" frame around a tinted inner panel */
  .how-card {
    position: relative;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 22px;
    padding: 0.5rem;
    box-shadow: theme('boxShadow.lift');
    transition: rotate 0.25s cubic-bezier(0.16, 1, 0.3, 1),
                scale 0.25s cubic-bezier(0.16, 1, 0.3, 1);
  }
  .how-card-inner {
    background: var(--how-wash, var(--tag-bg));
    border: 1px solid color-mix(in srgb, var(--how-accent, var(--sky)) 30%, var(--line));
    border-radius: 15px;
    padding: 1.15rem 1.25rem 1.35rem;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
  }
  .how-card-inner h3 { margin-top: 0.15rem; }

  /* pushpin straddling the top edge of the frame */
  .how-pin {
    position: absolute;
    top: -0.85rem;
    left: 50%;
    transform: translateX(-50%);
    width: 2rem;
    height: 2rem;
    display: grid;
    place-items: center;
    color: var(--how-accent, var(--sky));
    filter: drop-shadow(0 2px 3px color-mix(in srgb, var(--ink) 22%, transparent));
  }
  .how-pin svg { width: 100%; height: 100%; }

  /* connector: dashed path stretched over the flow (preserveAspectRatio="none",
     non-scaling-stroke keeps the dash rhythm even under the non-uniform stretch) */
  .how-spine { display: none; }
  .how-spine path {
    fill: none;
    stroke: var(--sky);
    stroke-width: 2.5;
    stroke-linecap: round;
    vector-effect: non-scaling-stroke;
  }
  /* Resting / no-JS state: the marching dashes, exactly as before. */
  .how-weave-line { stroke-dasharray: 7 7; opacity: 0.45; }
  /* The continuous trace is only used for the JS-driven draw-on (below). */
  .how-weave-trace { display: none; }

  @media (min-width: 768px) {
    .how-steps {
      display: grid;
      grid-template-columns: 1fr 1fr;
      column-gap: 2.5rem;
      row-gap: 2rem;
      align-items: start;
      max-width: 40rem;
      margin-inline: auto;
    }
    .how-step { max-width: 21rem; }
    /* cards pushed to the outer edges; the thread weaves the gap between them */
    .how-step:nth-child(1) { grid-area: 1 / 1; justify-self: start; }
    .how-step:nth-child(2) { grid-area: 2 / 2; justify-self: end; }
    .how-step:nth-child(3) { grid-area: 3 / 1; justify-self: start; }
    .how-step:nth-child(4) { grid-area: 4 / 2; justify-self: end; }

    /* a loose, hand-pinned tilt that straightens on hover */
    .how-step:nth-child(odd)  .how-card { rotate: -2.5deg; }
    .how-step:nth-child(even) .how-card { rotate: 2.5deg; }
    .how-card:hover { rotate: 0deg; scale: 1.03; z-index: 3; }

    .how-spine {
      display: block;
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
      overflow: visible;
      z-index: 0;
      pointer-events: none;
    }
  }

  /* Marching-ants thread + a one-shot lift-in when the section scrolls into
     view. .js-draw is added by JS only (and never under reduced motion), so
     with no JS everything shows in its finished state. */
  @media (prefers-reduced-motion: no-preference) {
    .how-weave-line { animation: how-march 2.6s linear infinite; }
    @keyframes how-march { to { stroke-dashoffset: -56; } }

    /* With JS driving the section, the thread is drawn between the pinned cards
       as they lift in, then hands over to the marching rhythm. The loop only
       runs while the flow is on screen — nothing marches out of view. */
    .how-flow.js-draw .how-weave-line { opacity: 0; animation-play-state: paused; }
    .how-flow.js-draw .how-weave-trace {
      display: block;
      opacity: 0.5;
      stroke-dasharray: 100;
      stroke-dashoffset: 100;
    }
    .how-flow.js-draw.is-drawn .how-weave-trace {
      stroke-dashoffset: 0;
      opacity: 0;
      transition: stroke-dashoffset 1100ms cubic-bezier(0.16, 1, 0.3, 1),
                  opacity 450ms ease 1050ms;
    }
    .how-flow.js-draw.is-drawn .how-weave-line {
      opacity: 0.45;
      transition: opacity 500ms ease 900ms;
    }
    .how-flow.js-draw.is-drawn.is-onscreen .how-weave-line { animation-play-state: running; }

    .how-flow.js-draw .how-step { opacity: 0; transform: translateY(14px); }
    .how-flow.js-draw.is-drawn .how-step {
      opacity: 1;
      transform: none;
      transition: opacity 0.5s ease, transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
    }
    .how-flow.js-draw.is-drawn .how-step:nth-child(2) { transition-delay: 0.1s; }
    .how-flow.js-draw.is-drawn .how-step:nth-child(3) { transition-delay: 0.2s; }
    .how-flow.js-draw.is-drawn .how-step:nth-child(4) { transition-delay: 0.3s; }
  }

  /* --- FAQ accordion --- */
  /* Two-column split: the heading + a "more questions" note held on the left,
     a single stacked accordion of hairline-divided rows on the right. Heading
     and question type stay on the standard scale (H2 / H3). */
  .faq-layout { display: grid; gap: 2rem; }
  @media (min-width: 900px) {
    .faq-layout {
      grid-template-columns: minmax(0, 16rem) minmax(0, 1fr);
      gap: clamp(2.5rem, 1rem + 8vw, 7rem);
      align-items: start;
    }
    .faq-aside { position: sticky; top: calc(var(--header-h) + 1.5rem); }
  }
  .faq-more { margin-top: 1rem; max-width: 32ch; color: var(--ink-soft); }

  .faq-list { display: flex; flex-direction: column; }
  .faq-group-h {
    margin: 2rem 0 0.25rem;
    font-family: var(--font-sans);
    font-size: 0.8rem;
    font-weight: 400; /* Questrial ships 400 only — no faux bold */
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-soft);
  }
  .faq-group-h:first-child { margin-top: 0; }
  /* Each row is a native <details>: the browser shows and hides the answer on
     the summary's click, Enter or Space, with no script and no animation. */
  .faq-item { border-top: 1px solid var(--line); }
  .faq-item:last-child { border-bottom: 1px solid var(--line); }
  .faq-trigger {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1.25rem;
    padding: 1.25rem 0;
    font-family: theme('fontFamily.label');
    font-size: theme('fontSize.h4');
    letter-spacing: 0.005em;
    line-height: 1.3;
    color: var(--ink);
    cursor: pointer;
    list-style: none; /* no disclosure triangle: display: flex already drops it in Chromium and Firefox */
  }
  .faq-trigger::-webkit-details-marker { display: none; } /* Safari draws it separately */
  /* The question stays a real h4, so the heading outline keeps it, but takes
     its color from the summary: the base heading rule would pin it to --ink
     and lose the hover. */
  .faq-q { color: inherit; }
  /* --link, not Sky: Sky text on the light ground is only 2.4:1. */
  .faq-trigger:hover { color: var(--link); }
  .faq-trigger .faq-sign {
    flex: none;
    margin-top: 0.1rem;
    transition: transform 0.22s cubic-bezier(0.16, 1, 0.3, 1);
  }
  /* The sign carries both symbols and [open] picks one: a plus closed, a minus
     open. A half turn on the swap makes the change read as a morph rather than
     a cut, and a minus is unchanged by 180deg so it still lands flat. */
  .faq-item:not([open]) .faq-sign-minus,
  .faq-item[open] .faq-sign-plus { display: none; }
  .faq-item[open] .faq-sign { transform: rotate(180deg); }

  .faq-panel {
    max-width: theme('maxWidth.prose');
    color: var(--ink-soft);
  }
  .faq-panel > * { padding-bottom: 1.5rem; }

  /* --- "Get Started" drawer tab: fixed to the right edge, vertical text,
     opens the contact drawer. z-index sits below #contact-backdrop (90), so
     the drawer's backdrop covers it while the drawer is open. --- */
  .drawer-tab {
    position: fixed;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    z-index: 34;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.1rem 0.55rem;
    border: 1px solid var(--sky);
    border-right: 0;
    border-radius: theme('borderRadius.card') 0 0 theme('borderRadius.card');
    background: var(--sky);
    color: var(--navy);
    font-family: theme('fontFamily.label');
    font-size: 0.9375rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    writing-mode: vertical-rl;
    cursor: pointer;
    box-shadow: -2px 2px 16px rgba(10, 15, 36, 0.22);
    transition: transform 0.22s theme('transitionTimingFunction.calm'),
                opacity 0.22s ease, visibility 0.22s,
                background-color 0.18s ease, color 0.18s ease;
  }
  /* Light blue at rest in both themes; hover deepens to navy in light mode
     and lifts to white in dark mode. */
  .drawer-tab:hover {
    background: var(--accent-hover-bg);
    color: var(--accent-hover-ink);
    border-color: var(--accent-hover-bg);
  }
  /* --ring is Navy on light / Sky on dark: ≥3:1 against the page either way. */
  .drawer-tab:focus-visible { outline: 3px solid var(--ring); outline-offset: 3px; }
  /* Tucked away above the Services section and once the in-page intake
     form (or the closing region) is on screen. `.tab-wait` holds it tucked from
     first paint until main.js has measured the page (JS only, so no-JS keeps it). */
  .drawer-tab.is-tucked,
  .js .drawer-tab.tab-wait {
    transform: translateY(-50%) translateX(110%);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
  }
  /* Phones: a right-edge tab would sit over body copy inside the 1.5rem gutter,
     so the CTA becomes a full-width bar pinned to the bottom of the viewport
     (the brief's "CTA pinned to the bottom on scroll"). */
  @media (max-width: 479px) {
    .drawer-tab {
      top: auto;
      bottom: 0;
      left: 0;
      transform: none;
      writing-mode: horizontal-tb;
      min-height: 3.25rem;
      padding: 0.85rem 1rem calc(0.85rem + env(safe-area-inset-bottom));
      border: 0;
      border-top: 1px solid color-mix(in srgb, var(--navy) 25%, var(--sky));
      border-radius: 0;
      font-size: 0.9375rem;
      box-shadow: 0 -4px 16px rgba(10, 15, 36, 0.14);
    }
    .drawer-tab.is-tucked,
    .js .drawer-tab.tab-wait { transform: translateY(110%); }
    .drawer-tab:focus-visible { outline-offset: -5px; }
    /* keep the footer's last lines clear of the bar when it is showing */
    .site-footer { padding-bottom: 3.25rem; }
  }

  /* --- Contact drawer (right-anchored slide-out) ---
     Opened with dialog.show(), not showModal(): a showModal() dialog (and its
     ::backdrop) render in the browser's top layer, which paints above *every*
     normal stacking context regardless of z-index -- and that hid the very-
     high-z-index challenge overlay Google's reCAPTCHA used to append to <body>
     for an image challenge. The form now uses Cloudflare Turnstile, whose
     challenge stays inside the widget, so that constraint is gone; the plain
     fixed-position dialog + real sibling backdrop element (below) stay because
     they work and are tested. modal.js handles the inert-background, focus-
     trap, and Escape/outside-click behavior that showModal() would give for
     free. */
  #contact-modal {
    position: fixed;
    z-index: 91;
    inset: 0 0 0 auto;
    width: min(30rem, 100vw);
    max-width: 100vw;
    height: 100dvh;
    max-height: 100dvh;
    margin: 0;
    padding: 0;
    border: 0;
    border-left: 1px solid var(--line);
    border-radius: 0;
    background: var(--bg);
    color: var(--ink);
    overflow: hidden;
  }
  #contact-backdrop {
    position: fixed;
    inset: 0;
    z-index: 90;
    background: rgba(10, 15, 36, 0.55);
    backdrop-filter: blur(2px);
  }
  #contact-backdrop[hidden] { display: none; }
  #contact-modal[open] { animation: drawer-in 300ms cubic-bezier(0.16, 1, 0.3, 1) both; }
  #contact-backdrop:not([hidden]) { animation: backdrop-in 220ms ease both; }
  @keyframes drawer-in {
    from { opacity: 0; transform: translateX(100%); }
    to   { opacity: 1; transform: none; }
  }
  @keyframes backdrop-in { from { opacity: 0; } to { opacity: 1; } }
  @media (prefers-reduced-motion: reduce) {
    #contact-modal[open] { animation: backdrop-in 180ms ease both; }
    #contact-backdrop:not([hidden]) { animation-name: backdrop-in; }
  }
  /* Closing: modal.js adds .is-closing and only closes the dialog once this
     finishes. The drawer slides back out the edge it came from, quicker than it
     arrived and accelerating away; the backdrop fades with it and stops catching
     clicks, since the page behind is already live again. */
  #contact-modal.is-closing[open] { animation: drawer-out 220ms cubic-bezier(0.7, 0, 0.84, 0) both; }
  #contact-backdrop.is-closing:not([hidden]) {
    animation: backdrop-out 220ms ease both;
    pointer-events: none;
  }
  @keyframes drawer-out {
    from { opacity: 1; transform: none; }
    to   { opacity: 0; transform: translateX(100%); }
  }
  @keyframes backdrop-out { from { opacity: 1; } to { opacity: 0; } }
  @media (prefers-reduced-motion: reduce) {
    #contact-modal.is-closing[open] { animation: backdrop-out 150ms ease both; }
  }
  /* --- Lightbox variant: the same dialog, centered instead of edge-anchored.
     A `data-variant="lightbox"` open button adds `.is-lightbox` (see modal.js). --- */
  #contact-modal.is-lightbox {
    inset: 0;
    margin: auto;
    width: min(34rem, 100vw - 2rem);
    height: max-content;
    max-height: calc(100dvh - 2rem);
    border: 1px solid var(--line);
    border-radius: theme('borderRadius.card');
  }
  #contact-modal.is-lightbox .modal-scroll {
    height: auto;
    max-height: calc(100dvh - 2rem);
  }
  #contact-modal.is-lightbox[open] {
    animation: lightbox-in 240ms cubic-bezier(0.16, 1, 0.3, 1) both;
  }
  @keyframes lightbox-in {
    from { opacity: 0; transform: translateY(8px) scale(0.98); }
    to   { opacity: 1; transform: none; }
  }
  #contact-modal.is-lightbox.is-closing[open] {
    animation: lightbox-out 180ms cubic-bezier(0.7, 0, 0.84, 0) both;
  }
  @keyframes lightbox-out {
    from { opacity: 1; transform: none; }
    to   { opacity: 0; transform: translateY(8px) scale(0.98); }
  }
  @media (prefers-reduced-motion: reduce) {
    #contact-modal.is-lightbox[open] { animation: backdrop-in 180ms ease both; }
    #contact-modal.is-lightbox.is-closing[open] { animation: backdrop-out 150ms ease both; }
  }

  .modal-scroll { overflow-y: auto; height: 100dvh; max-height: 100dvh; padding: 1.75rem; }
  /* Pinned to the dialog, not the scroller, so it stays put while the content
     scrolls beneath it. z-index 1 keeps it above that content (a hovered .btn's
     transform makes its own stacking context and would otherwise paint over it)
     but under .modal-confirm (z-index 2), which covers it while asking. */
  .modal-close {
    position: absolute;
    z-index: 1;
    top: 0.75rem;
    right: 0.75rem;
    box-shadow: 0 1px 6px rgba(10, 15, 36, 0.16);
    background: var(--tag-bg);
    border: 1px solid var(--line);
    border-radius: 999px;
    width: 2.75rem;
    height: 2.75rem;
    display: grid;
    place-items: center;
    cursor: pointer;
    color: var(--ink);
  }
  .modal-close:hover { background: var(--line-soft); }

  /* The contact drawer (not the centered lightbox) and the mobile nav re-point
     the semantic tokens to the --panel-* set (see the #mobile-nav rule); in dark
     that inverts both to a Sky ground with a Navy CTA. */

  /* --- Forms --- */
  .field { display: block; margin-top: 1rem; }
  .field-label {
    display: block;
    font-family: theme('fontFamily.label');
    font-size: 0.9375rem;
    letter-spacing: 0.01em;
    color: var(--ink);
    margin-bottom: 0.35rem;
  }
  .field-hint { display: block; font-size: 0.875rem; color: var(--ink-soft); margin-top: 0.3rem; }
  .req { color: var(--ink); font-weight: 600; } /* Ink, so Coral keeps one meaning (errors) */
  .input,
  .textarea,
  .select {
    width: 100%;
    font: inherit;
    font-size: 1rem;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--ink-soft);
    border-radius: 0.5rem;
    padding: 0.7rem 0.8rem;
    min-height: 48px;
  }
  .textarea { min-height: 7rem; resize: vertical; }
  .input:focus, .textarea:focus, .select:focus { border-color: var(--ink); }
  .input[aria-invalid="true"],
  .textarea[aria-invalid="true"],
  .select[aria-invalid="true"] { border-color: var(--coral); border-width: 2px; }
  /* Error text stays --ink for AA; the coral border and the leading triangle carry
     the "error" signal visually. For assistive tech it's aria-invalid on the field
     plus this element's id joining the field's aria-describedby (contact-form.js),
     and focus moving to the error summary on a failed submit. */
  .field-error { display: none; color: var(--ink); font-family: theme('fontFamily.mono'); font-size: 0.875rem; margin-top: 0.3rem; font-weight: 500; }
  .field-error:not([hidden]) { display: block; }
  .field-error:not([hidden])::before { content: "\25B2\00A0"; color: var(--coral); }
  .choice {
    display: flex;
    gap: 0.6rem;
    align-items: flex-start;
    padding: 0.5rem 0;
    font-size: 1rem;
  }
  .choice input { margin-top: 0.2rem; width: 1.15rem; height: 1.15rem; flex: none; accent-color: var(--choice-accent); }
  /* An empty checkbox is drawn from the text fields' own tokens. The browser's,
     under color-scheme: dark, is a flat neutral grey that reads as disabled on
     Navy. Once ticked it is the native control again, tinted by accent-color. */
  .choice input[type="checkbox"]:not(:checked) {
    appearance: none;
    background: var(--surface);
    border: 1px solid var(--ink-soft);
    border-radius: 3px;
  }
  /* The same 2px Coral border an empty required field gets. */
  .choice input[type="checkbox"][aria-invalid="true"]:not(:checked) { border: 2px solid var(--coral); }
  fieldset { border: 0; padding: 0; margin: 0; }

  /* Cloudflare's Turnstile widget (contact-form.js renders it "flexible":
     full width, never under 300px, 65px tall -- or "compact", 150x140, where
     the card is narrower than 300px). Reserve the height so the card doesn't
     jump a second time when the iframe arrives, and let it scroll rather than
     push the card wider if it still doesn't fit. The field is authored hidden
     and appears on the visitor's first edit; .field's display: block sits in
     this layer and so outranks Preflight's [hidden], hence the explicit rule. */
  [data-turnstile-field][hidden] { display: none; }
  [data-turnstile-field] { overflow-x: auto; }
  [data-turnstile] { min-height: 65px; }

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

  .form-note {
    font-size: 0.875rem;
    color: var(--ink-soft);
    background: var(--tag-bg);
    border: 1px solid var(--line);
    border-radius: 0.5rem;
    padding: 0.75rem 0.9rem;
  }
  .error-summary {
    border: 2px solid var(--coral);
    background: color-mix(in srgb, var(--coral) 10%, var(--surface));
    color: var(--ink);
    font-family: theme('fontFamily.mono');
    font-size: 0.875rem; /* Caption size, same as .field-error — the Readable-First floor */
    border-radius: 0.5rem;
    padding: 0.9rem 1rem;
    margin-top: 1.25rem;
    margin-bottom: 1rem;
  }
  .error-summary a { color: var(--ink); }
  .error-summary strong { font-weight: 500; } /* Plex Mono ships 400/500 — no faux bold */

  /* --- Site preferences dialog: theme, text size, animations, reader mode,
     analytics. Built by prefs.js and opened with showModal(), so the browser
     supplies the backdrop, the inert page and Escape. Centered like the
     contact lightbox, on the page's own tokens. --- */
  .prefs-dialog {
    width: min(32rem, 100vw - 2rem);
    max-height: calc(100dvh - 2rem);
    margin: auto;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: theme('borderRadius.card');
    background: var(--bg);
    color: var(--ink);
    box-shadow: 0 18px 44px -14px rgba(10, 15, 36, 0.32);
    overflow: hidden;
  }
  .prefs-dialog::backdrop {
    background: rgba(10, 15, 36, 0.55);
    backdrop-filter: blur(2px);
  }
  .prefs-dialog[open] { animation: lightbox-in 240ms cubic-bezier(0.16, 1, 0.3, 1) both; }
  .prefs-dialog[open]::backdrop { animation: backdrop-in 220ms ease both; }
  @media (prefers-reduced-motion: reduce) {
    .prefs-dialog[open] { animation: backdrop-in 180ms ease both; }
  }
  html:has(.prefs-dialog[open]) { overflow: hidden; }
  .prefs-body {
    max-height: calc(100dvh - 2rem);
    overflow-y: auto;
    padding: 1.25rem;
  }
  @media (min-width: 480px) { .prefs-body { padding: 1.75rem; } }
  /* The heading repeats the gear that opened the dialog. */
  .prefs-title { display: flex; align-items: center; gap: 0.6rem; }
  .prefs-title svg { flex: none; width: 1.1em; height: 1.1em; color: var(--sky-ink); }
  .prefs-lede { margin-top: 0.5rem; font-size: 1rem; color: var(--ink-soft); }
  .prefs-group { margin-top: 1.5rem; }
  .prefs-label {
    display: block;
    font-family: theme('fontFamily.label');
    font-size: 1rem;
    letter-spacing: 0.02em;
    color: var(--ink);
  }
  .prefs-hint { margin-top: 0.15rem; font-size: 0.9375rem; line-height: 1.45; color: var(--ink-soft); }

  /* Segmented radios: the input stays in the tab order (arrow keys move
     between options) but is drawn by its label. */
  .prefs-seg {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.25rem;
    margin-top: 0.6rem;
    padding: 0.25rem;
    border: 1px solid var(--line);
    border-radius: theme('borderRadius.card');
    background: var(--surface);
  }
  .prefs-seg-option {
    position: relative;
    display: grid;
    place-items: center;
    min-height: 44px;
    padding: 0.4rem 0.75rem;
    border-radius: 8px;
    font-size: 1rem;
    text-align: center;
    overflow-wrap: anywhere;
    color: var(--ink);
    cursor: pointer;
    transition: background-color 0.18s ease, color 0.18s ease;
  }
  .prefs-seg-option input {
    position: absolute;
    inset: 0;
    opacity: 0;
    margin: 0;
    cursor: pointer;
  }
  .prefs-seg-option:hover { background: var(--tag-bg); }
  .prefs-seg-option:has(input:checked) { background: var(--cta-bg); color: var(--cta-ink); }
  .prefs-seg-option:has(input:focus-visible) {
    outline: 3px solid var(--ring);
    outline-offset: 2px;
  }
  /* Text size: 2 × 2 on phones, one row from 480px. */
  .prefs-seg--text { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  @media (min-width: 480px) { .prefs-seg--text { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
  /* Each option previews its own step (the root scale on :root[data-text]). */
  .prefs-seg--text [data-value="lg"] { font-size: 1.125rem; }
  .prefs-seg--text [data-value="xl"] { font-size: 1.25rem; }
  .prefs-seg--text [data-value="xxl"] { font-size: 1.5rem; line-height: 1.2; }

  .prefs-rows { margin-top: 1.25rem; border-top: 1px solid var(--line-soft); }
  .prefs-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding-block: 0.9rem;
    border-bottom: 1px solid var(--line-soft);
  }
  /* When the label and switch can't share a line (a narrow phone at the Largest
     text size), the switch drops below the text instead of squeezing it. */
  .prefs-row { flex-wrap: wrap; }
  .prefs-row-text { flex: 1 1 10rem; }
  .prefs-row .prefs-label { cursor: pointer; }

  /* On/off switch: a native checkbox (role="switch") drawn as a pill, in the
     same track + knob shape as the footer's theme switch. */
  .prefs-switch {
    appearance: none;
    flex: none;
    position: relative;
    width: 3.25rem;
    height: 1.9rem;
    margin: 0;
    border: 1px solid var(--ink-soft);
    border-radius: 999px;
    background: var(--line-soft);
    cursor: pointer;
    transition: background-color 0.18s ease, border-color 0.18s ease;
  }
  .prefs-switch::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 0.2rem;
    width: 1.35rem;
    height: 1.35rem;
    border-radius: 999px;
    background: var(--ink-soft);
    transform: translateY(-50%);
    transition: left 220ms cubic-bezier(0.16, 1, 0.3, 1), background-color 0.18s ease;
  }
  .prefs-switch:checked { background: var(--cta-bg); border-color: var(--cta-bg); }
  .prefs-switch:checked::before { left: calc(100% - 1.35rem - 0.2rem); background: var(--cta-ink); }
  .prefs-switch:disabled { opacity: 0.55; cursor: not-allowed; }
  .prefs-switch:focus-visible { outline: 3px solid var(--ring); outline-offset: 2px; }

  .prefs-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 0.75rem;
    margin-top: 1.5rem;
  }
  .prefs-actions .btn { flex: 1 1 auto; }
  @media (min-width: 480px) { .prefs-actions .btn { flex: 0 0 auto; } }

  /* --- Reader mode (Site preferences → data-reader="on" on <html>) ---
     One plain column for reading: every layout grid in <main> collapses to a
     single track in a narrower measure, decoration (mazes, the How-it-works
     thread and pins, the lit beacon, the hero glow) is removed, tinted section
     grounds and card shadows go flat, and the leading opens up. Content photos
     are hidden too, same as the aria-hidden treatment they get for Safari's
     built-in Reader View — this mode should read like a plain HTML page, text
     only. The navy Schedule band keeps its ground, since its type is set for
     it. The header, footer and pinned CTA are untouched. */
  :root[data-reader="on"] main :is(.framed, .vignette-figure, .hero-byline-frame) {
    display: none !important;
  }
  :root[data-reader="on"] main { line-height: 1.75; }
  :root[data-reader="on"] main :is(.canvas, .canvas-wide) { max-width: 44rem; }
  /* The style guide's shell: one column in the same measure, contents on top. */
  :root[data-reader="on"] main .sg-shell { grid-template-columns: minmax(0, 1fr) !important; max-width: 44rem; }
  :root[data-reader="on"] main .sg-toc { position: static; max-height: none; overflow: visible; }
  :root[data-reader="on"] main :is(.grid, .grid-9, .service-rows, .who-list, .price-choices,
    .how-steps, .faq-layout, .fineprint-body, .ba-grid) {
    grid-template-columns: minmax(0, 1fr) !important;
  }
  /* Same phone treatment as the ba-grid's own narrow breakpoint: Before shows
     titles only, so the stacked panels reach the answer sooner, and the two
     panels drop the side-by-side subgrid row alignment (a >=640px-only rule
     that would otherwise still apply once the columns above collapse to one). */
  :root[data-reader="on"] main .ba-panel--before .ba-desc { display: none; }
  :root[data-reader="on"] main :is(.ba-panel, .ba-list) {
    grid-row: auto !important;
    grid-template-rows: none !important;
  }
  :root[data-reader="on"] main :is(.grid, .grid-9, .how-steps, .who-list) > * {
    grid-column: auto !important;
    grid-row: auto !important;
    justify-self: stretch !important;
  }
  :root[data-reader="on"] main :is(.maze-hero, .lost-maze, .maze-cross, .how-spine, .how-pin, .beacon-lit) {
    display: none !important;
  }
  :root[data-reader="on"] main :is(.hero-atmos, .how-flow, .trust-stack)::before { content: none; }
  :root[data-reader="on"] main .trust-stack :is(.trust-stack-title, .trust-item strong, .trust-item p) {
    text-shadow: none;
  }
  :root[data-reader="on"] main .how-step { max-width: none; }
  :root[data-reader="on"] main .service-row { background: none; }
  /* The path cards' photo wash is decoration. */
  :root[data-reader="on"] main .path-card::before { background: var(--surface); }
  /* The hero keeps extra bottom room for its glow; use the normal rhythm. */
  :root[data-reader="on"] #top > .canvas.section { padding-bottom: clamp(2.25rem, 1.5rem + 3.25vw, 4rem); }
  /* The "What" photo floats beside the copy, and the value list (its own flex
     box) squeezes into the leftover strip. Stack it above the text instead. */
  :root[data-reader="on"] main .vignette-figure {
    float: none;
    width: 100%;
    max-width: 20rem;
    margin: 0 0 1.5rem;
  }
  :root[data-reader="on"] main .faq-more { max-width: none; }
  /* Sticky side rails would slide over the stacked copy below them. */
  :root[data-reader="on"] main :is(.about-rail, .faq-aside) { position: static !important; }
  /* Their desktop column gaps become row gaps once stacked. */
  :root[data-reader="on"] main :is(.faq-layout, .fineprint-body) { gap: 2rem; }
  :root[data-reader="on"] main .section:not(.on-dark) {
    background: var(--bg) !important;
    background-image: none !important;
  }
  :root[data-reader="on"] main :is(.card, .tile, .path-card, .how-card, .how-card-inner, .price-choice, .framed) {
    box-shadow: none !important;
    transform: none !important;
    rotate: none !important;
  }
  :root[data-reader="on"] main .faq-aside { position: static; }
  :root[data-reader="on"] main .ba-list::before { display: none; }
  :root[data-reader="on"] main :is(p, li) { text-align: left; }

  /* --- Footer — Sky ground with Navy ink in light, Midnight with Paper in dark
     (the --footer-* tokens). Dark adds a divider so it separates from the
     near-identical page background. Logo and GNA badge ship in both colorways,
     toggled with .theme-icon-light / .theme-icon-dark. --- */
  .site-footer { background: var(--footer-bg); color: var(--footer-ink); }
  .site-footer { border-top: 1px solid var(--footer-edge); }
  .site-footer a { color: var(--footer-ink); text-decoration: none; }
  .site-footer a:not([class]) { color: var(--footer-link); }
  .site-footer a:hover:not(.brand-lockup) { text-decoration: underline; }
  /* The page's Sky-tint selection vanishes on the Sky ground and barely shows on
     Midnight, so the footer selects in its accent pair: Navy with Paper in light,
     Sky with Navy in dark. */
  .site-footer::selection,
  .site-footer ::selection { background: var(--footer-accent); color: var(--footer-accent-ink); }
  /* The builder credit is the one footer link underlined at rest. */
  .site-footer .footer-credit a {
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
    text-underline-offset: 0.18em;
  }
  .site-footer .footer-credit a:hover { text-decoration-color: currentColor; }
  .footer-col-title {
    font-family: theme('fontFamily.label');
    font-size: 0.8125rem;
    line-height: inherit;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--footer-title);
    margin-bottom: 0.75rem;
  }
  .footer-dim { color: var(--footer-dim); }
  .footer-rule { border-color: color-mix(in srgb, var(--footer-ink) 15%, transparent); }
  .footer-social { border-color: color-mix(in srgb, var(--footer-ink) 20%, transparent); }
  .footer-social:hover { border-color: color-mix(in srgb, var(--footer-ink) 60%, transparent); }
  /* QR code to the site — paper only (see @media print). A display:none <img>
     still downloads, so it's ready when the print dialog opens. */
  .footer-qr { display: none; }
  /* GNA member badge. Uses single-color lockups derived from
     assets/gna-logo-w-tagline.svg — gna-logo-navy.svg on the light Sky footer,
     gna-logo-white.svg on the dark Midnight one. */
  .gna-badge {
    display: inline-flex;
    align-items: center;
    max-width: 100%;
  }
  /* Print swaps in the primary mark and the full-color GNA badge. Browsers
     don't wait for images first requested by print styles, so warm both here. */
  body::after {
    content: url('../logomarks/marks/advocate-beacon-primary.svg') url('../img/gna-logo-w-tagline.svg');
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    opacity: 0;
    pointer-events: none;
  }

  /* The footer's Site preferences gear (prefs.js) sits beside the theme switch
     in the social row, drawn like the social circles but with the switch
     track's tint so the two controls read as one pair. Needs JS, like the dialog. */
  .footer-gear {
    color: var(--footer-ink);
    background: color-mix(in srgb, var(--footer-ink) 14%, transparent);
    cursor: pointer;
  }
  html:not(.js) .footer-gear { display: none; }
  @media (prefers-reduced-motion: no-preference) {
    .footer-gear svg { transition: transform 420ms cubic-bezier(0.16, 1, 0.3, 1); }
    .footer-gear:hover svg { transform: rotate(60deg); }
  }

  /* Light/dark switch — pill track + sliding knob, in the footer and the mobile
     menu. Sun (left) and moon (right) both stay on the track; the knob slides
     under the active one. State is the button's aria-pressed (set by theme.js),
     so the switch stays hidden until JS is on. Colors and sizes are local --sw-*
     props: the footer's (--footer-* tokens) here, the menu panel's in
     .theme-switch--menu, and the footer's 44px size in .theme-switch--footer. */
  .theme-switch {
    --sw-w: 3.5rem;
    --sw-h: 1.95rem;
    --sw-knob-size: 1.5rem;
    --sw-inset: 0.22rem;
    --sw-ico: 0.95rem;
    --sw-track: color-mix(in srgb, var(--footer-ink) 14%, transparent);
    --sw-track-on: color-mix(in srgb, var(--footer-accent) 26%, transparent);
    --sw-border: color-mix(in srgb, var(--footer-ink) 22%, transparent);
    --sw-border-hover: color-mix(in srgb, var(--footer-ink) 45%, transparent);
    --sw-knob: var(--footer-accent);
    --sw-on: var(--footer-accent-ink);
    --sw-off: var(--footer-sw-off);
    display: inline-flex;
    align-items: center;
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  html:not(.js) :is(.theme-switch, .mobile-nav-settings, .theme-orb) { display: none; }
  .theme-switch-track {
    position: relative;
    display: block;
    flex-shrink: 0;
    width: var(--sw-w);
    height: var(--sw-h);
    border-radius: 999px;
    background: var(--sw-track);
    border: 1px solid var(--sw-border);
    transition: background-color 0.2s ease, border-color 0.2s ease;
  }
  .theme-switch[aria-pressed="true"] .theme-switch-track { background: var(--sw-track-on); }
  .theme-switch:hover .theme-switch-track { border-color: var(--sw-border-hover); }
  .theme-switch-knob {
    position: absolute;
    top: 50%;
    left: var(--sw-inset);
    transform: translateY(-50%);
    width: var(--sw-knob-size);
    height: var(--sw-knob-size);
    border-radius: 999px;
    background: var(--sw-knob);
    box-shadow: 0 1px 4px rgba(10, 15, 36, 0.45);
    transition: left 0.24s cubic-bezier(0.16, 1, 0.3, 1), background 0.2s ease;
  }
  .theme-switch[aria-pressed="true"] .theme-switch-knob { left: calc(100% - var(--sw-knob-size) - var(--sw-inset)); }
  .theme-switch-ico {
    position: absolute;
    top: 50%;
    width: var(--sw-ico);
    height: var(--sw-ico);
    margin-top: calc(var(--sw-ico) / -2);
    pointer-events: none;
    transition: color 0.2s ease;
  }
  /* Centered on the knob's two resting spots. */
  .theme-switch-ico--sun { left: calc(var(--sw-inset) + (var(--sw-knob-size) - var(--sw-ico)) / 2); color: var(--sw-on); }
  .theme-switch-ico--moon { right: calc(var(--sw-inset) + (var(--sw-knob-size) - var(--sw-ico)) / 2); color: var(--sw-off); }
  /* Footer: as tall as the 44px social circles and gear beside it, with the
     same hairline border color. */
  .theme-switch--footer {
    --sw-w: 4.9rem;
    --sw-h: 2.75rem;
    --sw-knob-size: 2.15rem;
    --sw-inset: 0.26rem;
    --sw-ico: 1.15rem;
    --sw-border: color-mix(in srgb, var(--footer-ink) 20%, transparent);
    --sw-border-hover: color-mix(in srgb, var(--footer-ink) 60%, transparent);
  }
  .theme-switch[aria-pressed="true"] .theme-switch-ico--sun { color: var(--sw-off); }
  .theme-switch[aria-pressed="true"] .theme-switch-ico--moon { color: var(--sw-on); }
  @media (prefers-reduced-motion: reduce) {
    .theme-switch-knob { transition-duration: 120ms; }
  }

  /* In the mobile menu: a full-width settings block under the links — the
     theme switch, then the Site preferences row (the header's gear is
     desktop-only; mobile gets this instead). Built from the panel's own
     tokens, so it reads on both panel grounds (page ground in light, Sky in
     dark). */
  .mobile-nav-settings {
    margin-top: 0.4rem;
    padding-top: 0.4rem;
    border-top: 1px solid var(--line-soft);
  }
  .theme-switch--menu {
    --sw-track: color-mix(in srgb, var(--ink) 8%, transparent);
    --sw-track-on: var(--sw-track);
    --sw-border: var(--line);
    --sw-border-hover: color-mix(in srgb, var(--ink) 45%, transparent);
    --sw-knob: var(--ink);
    --sw-on: var(--bg);
    --sw-off: var(--ink-soft);
    width: 100%;
    justify-content: space-between;
    gap: 1.25rem;
    padding: 0.4rem 0.45rem 0.4rem 0.85rem;
    border-radius: 8px; /* rounded.field */
    font-family: theme('fontFamily.label');
    font-size: 1rem;
    letter-spacing: 0.02em;
    color: var(--ink);
  }
  .theme-switch--menu:hover,
  .theme-switch--menu:focus-visible { background: var(--tag-bg); }
  /* Site preferences row: gear + label, the same row treatment as the theme
     switch above it (full width, 44px target, hover/focus background). */
  .mobile-nav-prefs {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    min-height: 44px;
    margin-top: 0.15rem;
    padding: 0.4rem 0.45rem 0.4rem 0.85rem;
    border: 0;
    border-radius: 8px; /* rounded.field */
    font: inherit;
    font-family: theme('fontFamily.label');
    font-size: 1rem;
    letter-spacing: 0.02em;
    color: var(--ink);
    background: none;
    cursor: pointer;
    text-align: left;
  }
  .mobile-nav-prefs svg { width: 1.15rem; height: 1.15rem; flex-shrink: 0; }
  .mobile-nav-prefs:hover,
  .mobile-nav-prefs:focus-visible { background: var(--tag-bg); }
  html:not(.js) .mobile-nav-prefs { display: none; }
  @media (prefers-reduced-motion: no-preference) {
    .mobile-nav-prefs svg { transition: transform 420ms cubic-bezier(0.16, 1, 0.3, 1); }
    .mobile-nav-prefs:hover svg { transform: rotate(60deg); }
  }

  /* Desktop header: the same toggle as a single circle (36px, a notch under the
     44px header buttons, so the six links still clear the logo at the nav screen). Sun and
     moon share the circle; on toggle the sun swipes out left as the moon comes in
     from the right, and back. Mobile gets the menu row instead. */
  .theme-orb {
    position: relative;
    overflow: hidden;
    flex-shrink: 0;
    width: 2.25rem;
    height: 2.25rem;
    -webkit-tap-highlight-color: transparent;
  }
  .theme-orb .theme-orb-ico {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 1.15rem;
    height: 1.15rem;
    margin: -0.575rem 0 0 -0.575rem;
    transition: transform 320ms cubic-bezier(0.16, 1, 0.3, 1);
  }
  .theme-orb .theme-orb-ico--moon { transform: translateX(170%); }
  .theme-orb[aria-pressed="true"] .theme-orb-ico--sun { transform: translateX(-170%); }
  .theme-orb[aria-pressed="true"] .theme-orb-ico--moon { transform: none; }
  @media (prefers-reduced-motion: reduce) {
    .theme-orb .theme-orb-ico { transition: none; }
  }

  /* Gear: opens Site preferences (prefs.js). Desktop only, orb-sized beside
     the theme orb (the `hidden nav:inline-flex` classes on the button do the
     breakpoint switch); mobile gets a row in the nav panel instead
     (.mobile-nav-prefs). Needs JS, like the dialog. */
  .prefs-gear { flex-shrink: 0; width: 2.25rem; height: 2.25rem; }
  .prefs-gear svg { width: 1.2rem; height: 1.2rem; }
  html:not(.js) .prefs-gear { display: none; }
  @media (prefers-reduced-motion: no-preference) {
    .prefs-gear svg { transition: transform 420ms cubic-bezier(0.16, 1, 0.3, 1); }
    .prefs-gear:hover svg { transform: rotate(60deg); }
  }

  /* --- Hero actions: one primary button + the phone number, on the first screen --- */
  .hero-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.9rem 1.5rem;
  }
  /* Tagline: a quieter second lede under the main one. */
  .hero-tagline { font-size: 0.9375rem; letter-spacing: 0.01em; }
  /* CTA note: sits under the button, not beside it, inside the wrapping flex row. */
  .hero-cta-note {
    flex-basis: 100%;
    margin-top: -0.4rem;
    font-size: 0.875rem;
    color: var(--ink-soft);
  }
  /* Credentials beside the CTA: plain text, not a link. */
  .hero-creds {
    margin-top: 0.75rem;
    font-family: theme('fontFamily.label');
    font-size: 1.0625rem;
    color: var(--ink);
  }

  /* Trust stack: five reasons in a row under the hero; two columns on
     tablets, one list on phones. Each item gets a brand accent. No rule above
     it: the hero maze runs underneath and a full-width line cut across it.
     The maze is knocked back instead of ruled off — a soft wash of the page
     ground fades it under the strip, and every glyph carries a small halo of
     the same ground, so walls stop at the letterforms rather than crossing
     them. Both are page-ground colored, so they follow the theme. */
  .trust-stack {
    position: relative;
    margin-top: 3rem;
    padding-top: 1.5rem;
  }
  /* Paints over .maze-hero: both sit at z-index -1 in the hero's stacking
     context (.hero-atmos isolates), and this comes later in tree order. */
  .trust-stack::before {
    content: "";
    position: absolute;
    inset: -0.5rem -2.5rem -1.25rem;
    z-index: -1;
    pointer-events: none;
    background: radial-gradient(110% 80% at 50% 66%,
      color-mix(in srgb, var(--bg) 60%, transparent) 35%, transparent 100%);
  }
  .trust-stack :is(.trust-stack-title, .trust-item strong, .trust-item p) {
    text-shadow: 0 0 5px var(--bg), 0 0 10px var(--bg);
  }
  .trust-stack-title {
    font-family: theme('fontFamily.label');
    font-size: 0.875rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-soft);
  }
  .trust-list {
    display: grid;
    gap: 1.25rem 1.5rem;
    margin-top: 1.25rem;
    list-style: none;
    padding: 0;
  }
  @media (min-width: 640px) { .trust-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
  @media (min-width: 1024px) { .trust-list { grid-template-columns: repeat(5, minmax(0, 1fr)); } }
  .trust-item {
    --trust-accent: var(--navy);
    display: flex;
    align-items: flex-start;
    gap: 0.8rem;
    color: var(--ink-soft);
    font-size: 0.9375rem;
    line-height: 1.45;
  }
  .trust-item strong {
    display: block;
    margin-bottom: 0.15rem;
    font-family: theme('fontFamily.label');
    font-size: 1.0625rem;
    color: var(--ink);
  }
  .trust-item--years { --trust-accent: var(--coral-ink); }
  .trust-item--sky { --trust-accent: var(--sky-ink); }
  .trust-item--seagrass { --trust-accent: var(--seagrass-ink); }
  .trust-item--lantern { --trust-accent: var(--lantern-ink); }
  .trust-item--navy { --trust-accent: var(--ink); }
  .trust-ico {
    flex-shrink: 0;
    width: 2.25rem;
    height: 2.25rem;
    color: var(--trust-accent);
  }

  /* Byline: Lindsey's face on the first screen, linking to her full bio. At
     every width, so the text-only phone hero still has a person in it. */
  .hero-byline {
    display: inline-flex;
    align-items: center;
    gap: 0.9rem;
    color: var(--ink);
    text-decoration: none;
    max-width: 33.75rem;
  }
  /* Circular mask framed on her face: the <picture> is the circle and the
     portrait inside is scaled up and shifted so hair-to-chin fills it. */
  .hero-byline-frame {
    position: relative;
    display: block;
    width: 7.5rem;
    height: 7.5rem;
    flex: none;
    overflow: hidden;
    border-radius: 999px;
    border: 2px solid var(--surface);
    box-shadow: 0 0 0 1px var(--line), 0 4px 12px rgba(10, 15, 36, 0.10);
  }
  .hero-byline-photo {
    position: absolute;
    left: -27%;
    top: -1.5%;
    width: 155%;
    max-width: none;
    height: auto;
  }
  .hero-byline-text { display: flex; flex-direction: column; gap: 0.1rem; }
  .hero-byline-name {
    font-family: theme('fontFamily.label');
    font-size: 1rem;
    text-decoration: underline;
    text-decoration-color: var(--line);
    text-underline-offset: 0.22em;
  }
  .hero-byline:hover .hero-byline-name { text-decoration-color: currentColor; }
  .hero-byline-meta { font-size: 0.9375rem; line-height: 1.4; color: var(--ink-soft); }

  /* Plain-sentence reassurance inside the crisis path card. */
  .path-note {
    font-family: theme('fontFamily.label');
    font-size: 1.0625rem;
    color: var(--ink);
  }

  /* --- Services as rows, not a tile grid: icon in its own column, two
     columns of rows from md up. Each row takes a palette accent (Seagrass,
     Lantern, Sky, repeating): a tinted icon tile, a soft glow behind the
     icon and matching bullet markers. --svc tints marks and washes; --svc-ink
     is the contrast-safe step. --- */
  .service-rows { display: grid; column-gap: 3rem; }
  @media (min-width: 768px) { .service-rows { grid-template-columns: 1fr 1fr; } }
  .service-rows > :nth-child(3n+1) { --svc: var(--seagrass); --svc-ink: var(--seagrass-ink); }
  .service-rows > :nth-child(3n+2) { --svc: var(--lantern);  --svc-ink: var(--lantern-ink); }
  .service-rows > :nth-child(3n)   { --svc: var(--sky);      --svc-ink: var(--sky-ink); }
  .service-row {
    position: relative;
    display: grid;
    grid-template-columns: 3.5rem minmax(0, 1fr);
    column-gap: 1.1rem;
    align-content: start;
    padding-block: 1.75rem;
    background: radial-gradient(22rem 13rem at 0 0, color-mix(in srgb, var(--svc) 13%, transparent), transparent 70%);
  }
  .service-row > .service-icon {
    grid-row: 1 / span 3;
    display: grid;
    place-items: center;
    width: 3.5rem;
    height: 3.5rem;
    color: var(--svc-ink);
  }
  .service-row > .service-icon .icon-lg { width: 2.25rem; height: 2.25rem; }
  .service-row > :not(.service-icon) { grid-column: 2; }
  .service-row p { margin-top: 0.4rem; }

  /* --- Who we help: a photo card per audience on a Sky-tinted band. One
     column on phones, two from 640px, four in a row from 1024px (below).
     Photos: Unsplash, credited on about-this-site.html; responsive
     derivatives from scripts/fetch-photos.sh. --- */
  .who-band {
    background: color-mix(in srgb, var(--sky) 11%, var(--bg));
    border-block: 1px solid color-mix(in srgb, var(--sky) 24%, var(--bg));
  }
  /* Spacing matches the For Professionals "Who refers" list (gap-y-10 gap-x-6, h3 mt-4, p mt-2). */
  .who-list { display: grid; gap: 2.5rem 1.5rem; }
  @media (min-width: 640px) { .who-list { grid-template-columns: 1fr 1fr; } }
  .who-item { display: flex; flex-direction: column; }
  .who-photo { margin-bottom: 1rem; }
  .who-photo img { display: block; width: 100%; height: auto; aspect-ratio: 3 / 2; object-fit: cover; }
  .who-item p { margin-top: 0.5rem; }

  /* --- About: the page's showcase. Large portrait, editorial rail. --- */
  .about-portrait { width: 100%; max-width: 16.5rem; }
  .about-portrait img { aspect-ratio: 4 / 5; object-fit: cover; }

  /* Fee agreement promise, placed beside the numbers it reassures. */
  .fee-promise {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    margin-top: 1.25rem;
    color: var(--ink);
  }
  .fee-promise .icon { color: var(--seagrass-ink); margin-top: 0.1rem; }

  /* --- What is a healthcare advocate: the four value items as a quiet list --- */
  .value-list { display: flex; flex-direction: column; gap: 1.5rem; }
  .value-list > li { display: flex; align-items: flex-start; gap: 1rem; }
  .value-list > li > .icon { width: 2.28rem; height: 2.28rem; margin-top: 0.1rem; color: var(--sky); }
  .value-list > li:nth-child(1) > .icon { color: var(--sky); }
  .value-list > li:nth-child(2) > .icon { color: var(--seagrass-ink); }
  .value-list > li:nth-child(3) > .icon { color: var(--lantern-ink); }
  .value-list > li:nth-child(4) > .icon { color: var(--coral-ink); }
  .value-list p { margin-top: 0.35rem; }
  @media (min-width: 480px) {
    .value-list > li > .icon { width: 2.6rem; height: 2.6rem; }
  }

  /* Before/after: two framed panels, a header band over a white list, stacked
     on phones and side by side from 640px. Side by side, the panels share row
     tracks (subgrid) so each "before" row lines up with its "after" row. */
  .ba-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    row-gap: 1rem;
  }
  .ba-panel {
    --ba-frame: color-mix(in srgb, var(--ink) 6%, var(--surface));
    --ba-head-ink: var(--ink);
    display: grid;
    align-content: start;
    background: var(--ba-frame);
    border-radius: 18px;
    padding: 0 0.3rem 0.3rem;
  }
  .ba-panel--after { --ba-frame: var(--ba-after-bg); --ba-head-ink: var(--ba-after-ink); }
  .ba-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.1rem 1rem;
    padding: 0.85rem 0.7rem 0.75rem;
    color: var(--ba-head-ink);
  }
  .ba-head h3 { font-size: 1.0625rem; line-height: 1.3; letter-spacing: 0.01em; color: inherit; font-weight: 700; text-transform: uppercase; }
  .ba-head p { font-size: 1.0625rem; line-height: 1.35; }
  .ba-list {
    display: grid;
    background: var(--surface);
    border-radius: 13px;
    padding-inline: 0.75rem;
    overflow-wrap: break-word;
  }
  /* The one-colour Beacon as a faint green watermark filling the After list,
     painted under the rows (isolation keeps z-index -1
     above the white fill). */
  .ba-panel--after .ba-list { position: relative; isolation: isolate; }
  .ba-panel--after .ba-list::before {
    content: "";
    position: absolute;
    inset: 1.25rem 0.5rem;
    z-index: -1;
    background: var(--seagrass);
    -webkit-mask: url('../logomarks/marks/advocate-beacon-one-colour.svg') center / contain no-repeat;
            mask: url('../logomarks/marks/advocate-beacon-one-colour.svg') center / contain no-repeat;
    opacity: 0.08;
    pointer-events: none;
  }
  /* Narrow: the marker leads the title and the description takes the full
     width. From 640px the marker gets its own column beside both. */
  .ba-list > li {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-content: start;
    column-gap: 0.45rem;
    padding-block: 0.9rem 1rem;
    border-top: 1px solid var(--line);
  }
  .ba-list > li:first-child { border-top: 0; }
  .ba-desc { grid-column: 1 / -1; }
  /* Red cross / green tick discs; the palette hues clear 3:1 behind white. */
  .ba-mark {
    display: grid;
    place-items: center;
    width: 1.2rem;
    height: 1.2rem;
    margin-top: 0.2rem;
    border-radius: 50%;
    background: var(--coral);
  }
  .ba-mark--yes { background: var(--seagrass); }
  .ba-mark svg {
    width: 100%;
    height: 100%;
    fill: none;
    stroke: #FFFFFF;
    stroke-width: 3.2;
    stroke-linecap: round;
    stroke-linejoin: round;
  }
  .ba-title { font-weight: 600; line-height: 1.35; color: var(--ink); text-wrap: wrap; }
  .ba-desc { margin-top: 0.2rem; color: var(--ink-soft); }
  /* Phones (and Larger / Largest text): Before shows its titles only, so the
     stacked panels reach the answer sooner. */
  @media screen and (max-width: 639.98px) {
    .ba-panel--before .ba-desc { display: none; }
  }
  @media (min-width: 640px) {
    .ba-grid {
      grid-template-columns: repeat(2, minmax(0, 1fr));
      grid-template-rows: auto repeat(6, auto);
      column-gap: 1rem;
    }
    .ba-panel { grid-row: span 7; grid-template-rows: subgrid; padding: 0 0.375rem 0.375rem; }
    /* Before stays on top so After can slide out from beneath it. */
    .ba-panel--before { position: relative; z-index: 1; }
    .ba-list { grid-row: span 6; grid-template-rows: subgrid; }
    .ba-head { gap: 0.15rem 1rem; padding: 1rem 1.1rem 0.9rem; }
    .ba-list { padding-inline: 1.25rem; }
    .ba-list > li { column-gap: 0.7rem; padding-block: 1.05rem 1.15rem; }
    .ba-list > li > .ba-desc { grid-column: 2; }
    .ba-mark { grid-row: 1 / span 2; }
    .ba-mark { width: 1.4rem; height: 1.4rem; margin-top: 0.1rem; }
  }


  /* About / How it works: an action where the reading ends */

  /* Message field character count */
  /* Sand from 550 characters; once a keystroke or paste runs past the limit it takes
     the .error-summary treatment (Coral edge, 10% Coral wash, ink text). Sand sits
     behind the count as a highlight because it can't carry text; Midnight on Sand ≈12:1.
     The edge is an inset shadow so the 2px doesn't nudge the surrounding text. */
  /* Non-breaking spaces (contact-form.js) keep "n of 600" together; the count can still
     wrap before "characters" in a very narrow column. */
  .field-count { padding-inline: 0.3em; border-radius: 0.25rem; }
  .field-count.is-near { background: var(--sand); color: var(--midnight); font-weight: 600; }
  .field-count.is-over {
    background: color-mix(in srgb, var(--coral) 10%, var(--surface));
    color: var(--ink);
    box-shadow: inset 0 0 0 2px var(--coral);
  }

  /* Contact dialog: book or call first, message form as the alternative */
  .modal-quickbook { margin-top: 1rem; }
  .modal-quickbook[hidden] { display: none; }
  .modal-quickbook-lede { color: var(--ink-soft); }
  .modal-quickbook-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 1rem; }
  /* Labels stay on one line (so the Calendly mark sits level with the text);
     when both don't fit side by side, the buttons stack instead. */
  .modal-quickbook-actions .btn { flex: 1 1 auto; }
  .modal-divider {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    margin-top: 1.5rem;
    font-size: 0.875rem;
    color: var(--ink-soft);
  }
  .modal-divider::before,
  .modal-divider::after { content: ""; flex: 1; height: 1px; background: var(--line); }

  /* Discard check: dims the dialog's own content (made inert by modal.js) and
     centers a small card over it while the visitor decides. */
  .modal-confirm {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: grid;
    place-items: center;
    padding: 1.25rem;
    background: rgba(10, 15, 36, 0.45);
  }
  .modal-confirm[hidden] { display: none; }
  .modal-confirm-card {
    width: 100%;
    max-width: 24rem;
    padding: 1.5rem;
    background: var(--bg);
    color: var(--ink);
    border: 1px solid var(--line);
    border-radius: theme('borderRadius.card');
    animation: backdrop-in 160ms ease both;
  }
  .modal-confirm-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 1.25rem; }
  .modal-confirm-actions .btn { flex: 1 1 10rem; }

  /* --- "The light comes on": the one delight moment ---
     When a message reaches Lindsey, the Advocate Beacon mark appears in the
     success panel and its light comes up behind it: a soft Beacon Sky glow
     blooms, one slow beam sweeps round, and it settles to a steady glow. The
     mark itself is never altered (brand geometry); the light is drawn behind it.
     Runs when the panel is unhidden (display:none -> shown restarts the
     keyframes), once per send, never loops. A consultation booked in the inline
     Calendly embed is the same moment and gets the same light ([data-calendly-booked],
     with the Reversed mark where it sits on the navy booking band). Reduced motion: the glow fades in,
     no sweep, no scale. */
  .beacon-lit {
    position: relative;
    isolation: isolate;
    display: grid;
    place-items: center;
    width: 5.5rem;
    height: 5.5rem;
    margin-bottom: 1rem;
  }
  .beacon-lit-mark {
    position: relative;
    z-index: 2;
    width: 3.5rem;
    height: auto;
  }
  /* steady glow */
  .beacon-lit::before {
    content: "";
    position: absolute;
    inset: -0.75rem;
    z-index: 0;
    border-radius: 999px;
    background: radial-gradient(closest-side,
      color-mix(in srgb, var(--sky) 42%, transparent),
      color-mix(in srgb, var(--sky) 14%, transparent) 60%,
      transparent);
    opacity: 0.7;
  }
  /* the sweeping beam: a soft wedge of light, feathered by a radial mask */
  .beacon-lit::after {
    content: "";
    position: absolute;
    inset: -1.75rem;
    z-index: 1;
    border-radius: 999px;
    background: conic-gradient(from 0deg,
      transparent 0deg,
      color-mix(in srgb, var(--sky) 55%, transparent) 18deg,
      transparent 42deg,
      transparent 360deg);
    -webkit-mask-image: radial-gradient(closest-side, #000 35%, transparent 100%);
            mask-image: radial-gradient(closest-side, #000 35%, transparent 100%);
    opacity: 0;
    pointer-events: none;
  }
  @media (prefers-reduced-motion: no-preference) {
    :is([data-contact-success], [data-calendly-booked]):not([hidden]) .beacon-lit::before {
      animation: beacon-bloom 900ms cubic-bezier(0.16, 1, 0.3, 1) both;
    }
    :is([data-contact-success], [data-calendly-booked]):not([hidden]) .beacon-lit::after {
      animation: beacon-sweep 1600ms cubic-bezier(0.45, 0, 0.25, 1) 240ms both;
    }
    :is([data-contact-success], [data-calendly-booked]):not([hidden]) .beacon-lit-mark {
      animation: beacon-arrive 520ms cubic-bezier(0.16, 1, 0.3, 1) both;
    }
  }
  @media (prefers-reduced-motion: reduce) {
    :is([data-contact-success], [data-calendly-booked]):not([hidden]) .beacon-lit::before {
      animation: beacon-glow-fade 400ms ease both;
    }
  }
  @keyframes beacon-bloom {
    0%   { opacity: 0; transform: scale(0.55); }
    55%  { opacity: 1; transform: scale(1.08); }
    100% { opacity: 0.7; transform: scale(1); }
  }
  @keyframes beacon-sweep {
    0%   { opacity: 0; transform: rotate(-40deg); }
    15%  { opacity: 1; }
    80%  { opacity: 0.85; }
    100% { opacity: 0; transform: rotate(320deg); }
  }
  @keyframes beacon-arrive {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
  }
  @keyframes beacon-glow-fade {
    from { opacity: 0; }
    to   { opacity: 0.7; }
  }

  /* --- Hero maze: a generated maze (#maze-hero) behind the whole hero, anchored to
     its bottom-right corner. It fades in toward the right, so the copy column stays
     clean and the maze runs around and under the illustration. Phones get the same
     right-hand fade. Walls only, static; non-scaling strokes hold 1.75px. --- */
  .maze-hero {
    position: absolute;
    inset: 0;
    z-index: -1;
    width: 100%;
    height: 100%;
    pointer-events: none;
    fill: none;
    stroke: var(--ink);
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
    -webkit-mask-image: linear-gradient(to right, transparent 34%, #000 88%);
            mask-image: linear-gradient(to right, transparent 34%, #000 88%);
  }
  .maze-hero use { opacity: 0.16; }
  #top > .canvas.section { padding-bottom: clamp(4rem, 2rem + 5vw, 7rem); }

  /* --- Fine print, read: a sample statement beside three plain notes. The
     statement is a paper document (Surface, hairline, Lift); flagged lines take
     a Sand highlighter wash, and the reviewer's marks are Sky Ink pen strokes
     (an underline, a ring) drawn in the sprite's round caps. Numbered pins tie
     each mark to its note. Everything is visible without JS or motion. --- */
  .fineprint-head p { max-width: theme('maxWidth.measure'); color: var(--ink-soft); }
  .fineprint-body {
    display: grid;
    gap: 2rem;
    margin-top: 1.75rem;
    align-items: start;
    container: fineprint / inline-size;
  }
  @media (min-width: 820px) {
    .fineprint-body { grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr); gap: 2.75rem; }
  }
  .fineprint-notes p.fineprint-note {
    grid-column: 1 / -1;
    margin-top: 1.5rem;
    text-align: left;
    color: var(--ink-soft);
  }

  .fineprint-doc {
    position: relative;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: theme('borderRadius.tile');
    box-shadow: theme('boxShadow.lift');
    padding: 1.25rem 0.85rem 1.25rem 2.35rem;
  }
  @media (min-width: 480px) {
    .fineprint-doc { padding: 1.6rem 1.5rem 1.5rem 2.85rem; }
  }
  .fineprint-doc-top {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
  }
  .fineprint-doc-title {
    font-family: theme('fontFamily.label');
    font-size: 0.8125rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ink);
  }
  .fineprint-stamp {
    flex: none;
    margin-top: -0.2rem;
    padding: 0.15rem 0.55rem;
    border: 1.5px solid var(--ink-soft);
    border-radius: 6px;
    font-family: theme('fontFamily.label');
    font-size: 0.8125rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--ink-soft);
    rotate: -6deg;
  }
  .fineprint-doc-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.1rem 1.25rem;
    margin-top: 0.3rem;
    padding-bottom: 0.8rem;
    border-bottom: 1px solid var(--line);
    font-size: 0.875rem;
    color: var(--ink-soft);
  }
  .fineprint-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.9375rem;
    line-height: 1.4;
  }
  .fineprint-table thead th {
    padding: 0.7rem 0.45rem 0.4rem;
    font-family: theme('fontFamily.label');
    font-size: 0.75rem;
    font-weight: 400;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    text-align: left;
    color: var(--ink-soft);
  }
  .fineprint-table td {
    padding: 0.62rem 0.45rem;
    border-top: 1px solid var(--line-soft);
    vertical-align: top;
    color: var(--ink);
  }
  .fineprint-table td:first-child {
    position: relative;
    width: 3.6rem;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
  }
  @media (min-width: 480px) { .fineprint-table td:first-child { width: 4.4rem; } }
  .fineprint-table .fp-amt {
    text-align: right;
    white-space: nowrap;
    font-family: theme('fontFamily.mono');
    font-feature-settings: 'tnum' 1;
    font-size: 0.875rem;
  }
  .fineprint-table thead .fp-amt { font-family: theme('fontFamily.label'); font-size: 0.75rem; }
  .fineprint-table tfoot th,
  .fineprint-table tfoot td {
    padding: 0.8rem 0.45rem 0.1rem;
    border-top: 3px double var(--line);
    color: var(--ink);
  }
  .fineprint-table tfoot th {
    text-align: left;
    font-family: theme('fontFamily.label');
    font-weight: 400;
    font-size: 0.9375rem;
  }
  /* The narrowest phones: with 320px or less to work in (a 320–360px screen, or
     wider ones at the larger text sizes), tighter tracking, padding and figures
     keep all three columns inside the document, and at a larger text size it
     borrows a little of the page gutter. A container query, so it follows the
     space actually left at every text size (text-layout.js only rewrites @media). */
  @container fineprint (max-width: 320px) {
    .fineprint-doc { padding-left: 2.15rem; padding-right: 0.6rem; }
    .fineprint-table thead th { letter-spacing: 0.04em; }
    .fineprint-table :is(thead, tbody, tfoot) :is(th, td) { padding-inline: 0.3rem; }
    .fineprint-table td:first-child { width: 3.3rem; }
    .fineprint-table .fp-amt { font-size: 0.8125rem; }
    :root[data-text] .fineprint-doc { margin-inline: -0.75rem; }
  }

  /* Sand highlighter behind a flagged line. Each cell paints its own slice of one
     row-wide gradient (main.js sets --fp-row-w and each cell's --fp-x), so a single
     --fp-sweep on the row moves one edge across all three cells in one stroke.
     Without the measurements each cell just carries the full wash. */
  .fineprint-table tr.is-flagged td {
    --fp-wash: color-mix(in srgb, var(--sand) 28%, transparent);
    background-image: linear-gradient(to right, var(--fp-wash) var(--fp-sweep), transparent var(--fp-sweep));
    background-repeat: no-repeat;
    background-size: var(--fp-row-w, 100%) 100%;
    background-position: calc(-1 * var(--fp-x, 0px)) 0;
  }
  .fineprint [data-note="1"] { --fp-d: 450ms; }
  .fineprint [data-note="2"] { --fp-d: 850ms; }
  .fineprint [data-note="3"] { --fp-d: 1250ms; }

  .fp-pin {
    display: grid;
    place-items: center;
    width: 1.4rem;
    height: 1.4rem;
    border: 1.5px solid var(--sky-ink);
    border-radius: 999px;
    background: var(--surface);
    color: var(--sky-ink);
    font-family: theme('fontFamily.label');
    font-size: 0.8125rem;
    line-height: 1;
    transition: background-color 0.2s ease, color 0.2s ease;
  }
  .fineprint-table .fp-pin { position: absolute; left: -1.95rem; top: 0.6rem; }
  @media (min-width: 480px) { .fineprint-table .fp-pin { left: -2.2rem; } }

  .fp-mark { position: relative; display: inline-block; }
  .fp-ink { position: absolute; overflow: visible; pointer-events: none; }
  .fp-ink path {
    fill: none;
    stroke: var(--sky-ink);
    stroke-width: 2.25;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 1;
  }
  .fp-ink--under { left: -3px; bottom: -0.3rem; width: calc(100% + 6px); height: 0.5rem; }
  .fp-ink--ring { left: -0.6rem; top: -0.5rem; width: calc(100% + 1.2rem); height: calc(100% + 1rem); }

  .fineprint-notes { display: flex; flex-direction: column; gap: 1.6rem; }
  .fineprint-notes > li {
    display: grid;
    grid-template-columns: 2rem minmax(0, 1fr);
    column-gap: 0.9rem;
  }
  .fineprint-notes .fp-pin { width: 2rem; height: 2rem; font-size: 0.9375rem; }
  .fineprint-notes h3 { transition: color 0.2s ease; }
  .fineprint-notes p { margin-top: 0.3rem; }
  @media (min-width: 820px) { .fineprint-notes { padding-top: 0.5rem; } }

  /* Pointing at a note (or its line) lights the pair. */
  .fineprint[data-active="1"] [data-note="1"] .fp-pin,
  .fineprint[data-active="2"] [data-note="2"] .fp-pin,
  .fineprint[data-active="3"] [data-note="3"] .fp-pin { background: var(--sky-ink); color: var(--surface); }
  .fineprint[data-active="1"] li[data-note="1"] h3,
  .fineprint[data-active="2"] li[data-note="2"] h3,
  .fineprint[data-active="3"] li[data-note="3"] h3 { color: var(--link); }

  @media (prefers-reduced-motion: no-preference) {
    .js [data-reveal="marks"].reveal-pending { opacity: 0.01; }
    .js [data-reveal="marks"].reveal-pending .fp-ink path { stroke-dashoffset: 1; }
    .js [data-reveal="marks"].reveal-pending tr.is-flagged { --fp-sweep: 0%; }
    .js [data-reveal="marks"].reveal-pending .fineprint-table .fp-pin { opacity: 0; }

    .js [data-reveal="marks"].reveal-in {
      animation: reveal-rise 720ms var(--reveal-ease) backwards;
    }
    /* One stroke per row: a quick start, an even pull across, a soft lift at the end. */
    .js [data-reveal="marks"].reveal-in tr.is-flagged {
      animation: fp-highlight 780ms cubic-bezier(0.4, 0, 0.25, 1) backwards;
      animation-delay: var(--fp-d);
    }
    .js [data-reveal="marks"].reveal-in .fineprint-table .fp-pin {
      animation: reveal-settle 420ms var(--reveal-ease) backwards;
      animation-delay: var(--fp-d);
    }
    .js [data-reveal="marks"].reveal-in .fp-ink path {
      animation: fp-draw 560ms cubic-bezier(0.45, 0, 0.2, 1) backwards;
      animation-delay: calc(var(--fp-d) + 300ms);
    }
    @keyframes fp-highlight {
      from { --fp-sweep: 0%; }
      to   { --fp-sweep: 100%; }
    }
    @keyframes fp-draw {
      from { stroke-dashoffset: 1; }
      to   { stroke-dashoffset: 0; }
    }
  }

  /* --- Who we help: from 1024px the four audiences stand in a row. --- */
  @media (min-width: 1024px) {
    .who-list { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  }

  /* --- Credentials: as the rows arrive, a Sky rule runs along each one, in
     reading order, and fades back to the hairline. --- */
  .dl-rows > div { position: relative; }
  @media (prefers-reduced-motion: no-preference) {
    .js .dl-rows.reveal-in > div::after {
      content: "";
      position: absolute;
      left: 0;
      right: 0;
      bottom: -1px;
      height: 2px;
      background: var(--sky);
      transform-origin: left center;
      opacity: 0;
      animation: dl-read 1000ms var(--reveal-ease) backwards;
      animation-delay: calc(var(--reveal-i, 0) * 150ms + 250ms);
    }
    @keyframes dl-read {
      0%   { opacity: 1; transform: scaleX(0); }
      60%  { opacity: 1; transform: none; }
      100% { opacity: 0; transform: none; }
    }
  }

  /* --- Booking band: the maze, solved, full width along the bottom of the navy
     ground. On the index it has a strip of its own under the buttons (rule below),
     so no copy crosses a wall; the style guide's masthead still uses it as a
     background, anchored to the band's bottom edge and fading out upward, so it
     takes no space of its own. It
     scales with the page, so both walls always show; a smaller maze stands in on
     phones so the cells stay readable. A Beacon Sky ring waits outside the entrance
     in the left wall; the route crosses to the exit in the right wall and ends at a
     dot. main.js adds .route-pending, then .is-routed once the footer comes into
     view: ring, route, dot. No JS: all shown. Reduced motion: shown without the
     draw (global rule). --- */
  .schedule-band { position: relative; isolation: isolate; overflow: hidden; }
  .maze-cross {
    display: block;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: -1;
    width: 100%;
    height: auto;
    pointer-events: none;
    fill: none;
    stroke: var(--paper);
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
    -webkit-mask-image: linear-gradient(to bottom, transparent, #000 70%);
            mask-image: linear-gradient(to bottom, transparent, #000 70%);
  }
  .maze-cross--lg { aspect-ratio: 1840 / 328; }
  .maze-cross--sm { aspect-ratio: 720 / 248; display: none; }
  @media (max-width: 640px) {
    .maze-cross--lg { display: none; }
    .maze-cross--sm { display: block; }
  }
  /* On the index the maze is the last thing in the band, in the flow: it takes the
     room its aspect ratio asks for, so the copy and buttons end above it instead of
     running across the walls. The negative bottom margin lands it flush with the
     band's edge, in the padding .section leaves under the buttons (the clamp repeats
     .section's), and goes with the maze where the maze is hidden (reader mode,
     print), leaving that padding as the band's ordinary bottom. */
  #schedule .maze-cross {
    position: static;
    margin-block: 1.5rem calc(-1 * clamp(2.25rem, 1.5rem + 3.25vw, 4rem));
  }
  .maze-cross use { opacity: 0.15; }
  .maze-route { stroke: var(--sky); stroke-width: 3; opacity: 0.6; stroke-dasharray: 1; }
  .maze-cross--sm .maze-route { stroke-width: 4; }
  .maze-route-start { fill: none; stroke: var(--sky); stroke-width: 2.5; opacity: 0.8; }
  .maze-route-end { fill: var(--sky); stroke: none; opacity: 0.9; }
  /* Between phones and desktops the wide maze scales down to about half. */
  @media (max-width: 1023px) {
    .maze-cross--lg .maze-route { stroke-width: 5; }
    .maze-cross--lg .maze-route-start { stroke-width: 4; }
  }
  .maze-cross.route-pending .maze-route { stroke-dashoffset: 1; }
  .maze-cross.route-pending :is(.maze-route-start, .maze-route-end) { opacity: 0; }
  .maze-cross.route-pending.is-routed .maze-route {
    stroke-dashoffset: 0;
    transition: stroke-dashoffset 4200ms cubic-bezier(0.45, 0, 0.25, 1) 500ms;
  }
  .maze-cross.route-pending.is-routed .maze-route-start {
    opacity: 0.8;
    transition: opacity 500ms ease;
  }
  .maze-cross.route-pending.is-routed .maze-route-end {
    opacity: 0.9;
    transition: opacity 500ms ease 4600ms;
  }

  /* --- Calendly, inline: the scheduler framed in the page by calendly.js, in the
     booking band and in a panel of the contact dialog. Until the frame is in, a
     mount holds only its authored fallback, a plain link out; .is-live swaps that
     for the frame plus a one-line way out for a visitor whose browser refuses it.
     Calendly's page is transparent around its own white card, so the mount adds no
     card of its own. Its colors need a paid plan: the card is white in both
     themes.

     The mount's width picks Calendly's layout, and with it the size (its breakpoints
     are px widths of the frame, measured 2026-09-18 with the event details hidden):
       - under 650px, the phone layout: the page fills the frame, 602px tall, and a
         picked day opens its times as a second view;
       - 650 to 999px, a stacked card: 711px, growing past 1300px when a day is picked.
         Avoided: it is the largest of the three;
       - 1000px and up, the desktop layout: a 680px card in a transparent page, 660px
         tall and fixed, because the times scroll inside the card.
     So the mount is capped at 640px, and the booking band widens it to 1040px where
     the viewport has room (clear of the 1000px edge; the band clips any overflow). --- */
  .calendly-embed { position: relative; width: 100%; max-width: 640px; margin: 1.75rem auto 0; }
  /* 1040px + a 1rem gutter each side. The Larger / Largest text sizes keep the phone
     layout (text-layout.js rewrites this query), and so the 640px mount. */
  @media (min-width: 1072px) {
    #schedule .calendly-embed {
      width: 1040px;
      max-width: none;
      margin-inline: calc((100% - 1040px) / 2);
    }
    /* The desktop layout brings a 66px top margin of its own. */
    #schedule .calendly-embed.is-live { margin-top: 0.5rem; }
    #schedule .calendly-frame { height: 660px; }
  }
  /* The height is only a start, matched to the layout's first report so nothing
     jumps: calendly.js sets the real one from the frame's own reports, step by step.
     color-scheme matches Calendly's page; without it the dark theme makes the
     browser paint an opaque backdrop behind the frame. */
  .calendly-frame {
    display: block;
    position: relative;
    z-index: 1;
    width: 100%;
    height: 602px;
    border: 0;
    border-radius: 12px;
    color-scheme: light;
  }
  /* Sits where Calendly's card will land and is covered by it; calendly.js removes
     it when the frame has loaded. */
  .calendly-loading {
    position: absolute;
    inset: 7rem 0 auto;
    text-align: center;
    opacity: 0.82;
  }
  .calendly-alt { display: none; margin-top: 0.75rem; font-size: 0.9375rem; color: var(--ink-soft); }
  .on-dark .calendly-alt { color: rgba(245, 251, 255, 0.82); }
  .calendly-embed.is-live .calendly-alt { display: block; }
  .calendly-embed.is-live .calendly-fallback { display: none; }
  /* Booked: calendly.js has taken the frame out and unhidden the authored thanks
     (.calendly-booked), which is all the mount shows. It is the same moment as a sent
     message, so the beacon's light comes on the same way (rules below, "The light
     comes on"). In the dialog, modal.js marks the booking block too, and everything
     in it but the mount steps aside. */
  .calendly-embed.is-booked .calendly-fallback { display: none; }
  .calendly-booked[hidden] { display: none; }
  .calendly-booked:focus { outline: none; }
  .modal-quickbook.is-booked > :not(.calendly-embed) { display: none; }
  /* Once booked, not asked again: calendly.js remembers a booking (localStorage) and
     sets data-booked on <html>, and this takes the invitations to book away: every
     mount but the one showing its thanks, every toggle, and whatever is marked
     data-calendly-offer (the success panel's "Book time with us"). Copy marked
     data-calendly-after is authored hidden and stands in where a sentence needs
     rewording. The footer's Calendly icon is left alone, and following it brings
     all of this back. A rule and not script, so a dialog modal.js imports later
     needs no setup. Screen only: paper keeps the band's fallback link. */
  @media screen {
    :root[data-booked] :is([data-calendly]:not(.is-booked), [data-calendly-toggle], [data-calendly-offer]) { display: none; }
    :root[data-booked] [data-calendly-after] { display: inline; }
  }
  /* In the dialog: a gap under the buttons. The drawer is 30rem wide, so this is
     always Calendly's phone layout. */
  .calendly-embed.calendly-embed--panel { margin-top: 1rem; }
  .calendly-embed--panel .calendly-loading { top: 3rem; }

  /* --- "Get Started" tab: the first time it comes out it leans a little
     further than its resting place and settles back, once per page view. --- */
  .drawer-tab {
    --tab-rest: translateY(-50%);
    --tab-away: translateY(-50%) translateX(110%);
    --tab-lean: translateY(-50%) translateX(-0.45rem);
  }
  @media (max-width: 479px) {
    .drawer-tab {
      --tab-rest: translateY(0);
      --tab-away: translateY(110%);
      --tab-lean: translateY(-0.2rem);
    }
  }
  @media (prefers-reduced-motion: no-preference) {
    .drawer-tab.is-arriving { animation: tab-arrive 900ms cubic-bezier(0.16, 1, 0.3, 1) both; }
    @keyframes tab-arrive {
      0%   { transform: var(--tab-away); }
      55%  { transform: var(--tab-lean); }
      100% { transform: var(--tab-rest); }
    }
  }

  /* --- 404: a small maze with its route drawn through to the way out --- */
  .lost-maze { display: block; width: 100%; max-width: 22rem; margin-top: 2rem; overflow: visible; }
  .lost-maze path { fill: none; stroke-linecap: round; stroke-linejoin: round; }
  .lost-maze-walls { stroke: var(--ink-soft); stroke-width: 1.5; opacity: 0.45; }
  .lost-maze-route { stroke: var(--sky-ink); stroke-width: 2.5; stroke-dasharray: 1; }
  .lost-maze-end { fill: var(--sky-ink); }
  @media (prefers-reduced-motion: no-preference) {
    .lost-maze-route { animation: maze-route 2000ms cubic-bezier(0.45, 0, 0.2, 1) 400ms both; }
    .lost-maze-end { animation: reveal-fade 400ms ease 2300ms both; }
    @keyframes maze-route {
      from { stroke-dashoffset: 1; }
      to   { stroke-dashoffset: 0; }
    }
  }

  @media print {
    .maze-hero, .maze-cross { display: none; }
    .trust-stack::before { content: none; }
    .trust-stack :is(.trust-stack-title, .trust-item strong, .trust-item p) { text-shadow: none; }
  }

  /* --- Legal pages --- */
  .legal-prose { max-width: theme('maxWidth.prose'); }
  .legal-prose h2 { font-size: theme('fontSize.h3'); margin-top: 2.5rem; margin-bottom: 0.75rem; }
  .legal-prose h3 { font-size: theme('fontSize.h4'); margin-top: 1.75rem; margin-bottom: 0.5rem; }
  .legal-prose p, .legal-prose li { color: var(--ink-soft); }
  .legal-prose > p + p { margin-top: 1rem; }
  .legal-prose ul, .legal-prose ol { margin: 1rem 0; padding-left: 1.4rem; }
  .legal-prose ul { list-style: disc; }
  .legal-prose ol { list-style: decimal; }
  .legal-prose li { margin-top: 0.4rem; }
  .legal-prose li::marker { color: var(--sky); }
  .legal-prose a { color: var(--ink); text-decoration: underline; text-decoration-color: var(--sky); }
  .legal-prose :where(h1, h2, h3)[id] { scroll-margin-top: 6rem; }
  .legal-prose address {
    margin: 1.25rem 0;
    padding: 0.1rem 0 0.1rem 1.1rem;
    border-left: 3px solid var(--sky);
    color: var(--ink-soft);
    line-height: 1.7;
  }
  .legal-toc { background: var(--surface); border: 1px solid var(--line); border-radius: theme('borderRadius.card'); padding: 1.25rem 1.5rem; }
  .legal-toc ol { margin: 0.5rem 0 0; }
  .legal-prose code {
    font-family: theme('fontFamily.mono');
    font-size: 0.85em;
    background: var(--tag-bg);
    color: var(--ink);
    padding: 0.1em 0.4em;
    border-radius: 0.25rem;
  }
  .legal-prose .table-wrap {
    overflow-x: auto;
    margin: 1.5rem 0;
    border: 1px solid var(--line);
    border-radius: theme('borderRadius.card');
  }
  .legal-prose table { width: 100%; min-width: 30rem; border-collapse: collapse; font-size: 0.9375rem; }
  .legal-prose th, .legal-prose td { padding: 0.7rem 0.95rem; text-align: left; vertical-align: top; border-top: 1px solid var(--line-soft); }
  .legal-prose thead th {
    background: var(--tag-bg);
    color: var(--ink);
    font-family: theme('fontFamily.label');
    font-size: 0.75rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    border-top: 0;
  }
  .legal-prose tbody tr:first-child td, .legal-prose tbody tr:first-child th { border-top: 0; }
  .legal-prose td:last-child, .legal-prose th:last-child { text-align: right; }
  .legal-prose .sig-grid {
    display: grid;
    gap: 1rem;
    margin: 1.25rem 0;
  }
  @media (min-width: 480px) { .legal-prose .sig-grid { grid-template-columns: 1fr 1fr; } }
  .legal-prose .sig-card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: theme('borderRadius.card');
    padding: 1rem 1.15rem;
  }
  .legal-prose .sig-card p { margin-top: 0.5rem; }
  .legal-prose .sig-card .role {
    display: block;
    margin-bottom: 0.25rem;
    font-family: theme('fontFamily.label');
    font-size: 0.9375rem;
    font-weight: 500;
    letter-spacing: 0.01em;
    color: var(--ink);
  }
  .legal-prose .sig-card .role em { color: var(--ink-soft); font-weight: 400; }
  .legal-prose .fill { display: inline-block; min-width: 9rem; border-bottom: 1px solid var(--line); }
  .legal-prose .checklist { list-style: none; padding-left: 0; }
  .legal-prose .checklist li { padding-left: 1.6rem; text-indent: -1.6rem; }
  .legal-prose .checklist li::before { content: "\2610"; color: var(--sky); margin-right: 0.6rem; }
  .todo-flag {
    border: 1px dashed var(--warn-line);
    background: var(--warn-bg);
    color: var(--warn-ink);
    border-radius: 0.5rem;
    padding: 0.9rem 1rem;
    font-size: 0.9375rem;
    margin: 1rem 0;
  }
  .todo-flag strong { color: var(--ink); }
  .todo-flag ul, .todo-flag ol { margin: 0.5rem 0 0; padding-left: 1.2rem; }
  .legal-toc .footer-col-title { color: var(--ink-soft); }

  /* --- Style guide: the bar. The internal pages' own bar: the mark, the page's
     name, links between the guide and the two code references, and the theme
     and Site preferences buttons. Deliberately not the site header, so nobody
     takes these pages for public ones. Navy in both themes, like the booking
     band, so it borrows the dark palette (.sg-theme-dark on the element) and
     the header's round buttons work on it unchanged. Not sticky: a reference
     page is scrolled, not navigated from the top. --- */
  .sg-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem 1.75rem;
    padding: 0.85rem 1.5rem;
    background: var(--navy);
    color: var(--paper);
    border-bottom: 1px solid color-mix(in srgb, var(--paper) 18%, transparent);
  }
  /* The bar's content keeps the shell's edges: 2rem of padding, or whatever
     centers it on the same 1116px once the window is wider than the shell. */
  @media (min-width: 480px) { .sg-bar { padding-inline: max(2rem, calc((100% - 1116px) / 2)); } }
  .sg-bar-home { display: flex; flex: none; border-radius: 6px; }
  .sg-bar-home img { height: 2.5rem; width: auto; }
  .sg-bar-title {
    margin-right: auto;
    font-family: theme('fontFamily.label');
    font-size: theme('fontSize.h4[0]');
    line-height: 1;
  }
  .sg-bar-nav { display: flex; flex-wrap: wrap; gap: 0.25rem 1.6rem; }
  /* Label-voice links. The current page holds the nav's 2px Sky underline.
     Two classes, to outrank .on-dark's Sky for classless links. */
  .sg-bar .sg-bar-nav a {
    position: relative;
    padding: 0.7rem 0;
    font-family: theme('fontFamily.label');
    font-size: 0.9375rem;
    color: color-mix(in srgb, var(--paper) 86%, transparent);
    text-decoration: none;
  }
  .sg-bar .sg-bar-nav a:hover, .sg-bar .sg-bar-nav a[aria-current] { color: var(--paper); }
  .sg-bar-nav a[aria-current]::after {
    content: "";
    position: absolute;
    inset: auto 0 0.4rem;
    height: 2px;
    border-radius: 1px;
    background: var(--sky);
  }
  .sg-bar-tools { display: flex; gap: 0.5rem; }
  /* On a phone the links and the two buttons share the second row. */
  @media (max-width: 479px) {
    .sg-bar { column-gap: 0.75rem; }
    .sg-bar-nav { margin-right: auto; column-gap: 1.1rem; }
  }
  /* The header sets its theme and gear buttons at 2.25rem; here there is room
     for the full 44px. A class of its own rather than a descendant selector:
     Tailwind keeps a rule when any class in it is used, and .header-icon-btn
     is, on every page. */
  .sg-bar-btn {
    width: 44px;
    height: 44px;
    border-color: color-mix(in srgb, var(--paper) 38%, transparent);
  }
  /* At the larger text sizes the bar holds a fixed-geometry mark and round
     buttons, so it is zoomed back like the site header, and its buttons up again. */
  @media screen {
    :root[data-text] .sg-bar { zoom: var(--text-unscale); }
    :root[data-text] .sg-bar-btn { zoom: var(--text-scale); }
  }

  /* --- Style guide: the masthead. The guide opens on a real component: the
     booking band's navy ground with its maze, solved, along the bottom edge
     (.schedule-band and .maze-cross, unchanged), and the title block above it.
     main.js isn't loaded here, so the route is simply shown drawn. --- */
  .sg-mast {
    background: var(--navy);
    color: var(--paper);
    padding: clamp(2.5rem, 1.5rem + 4vw, 4.5rem) 1.5rem clamp(7rem, 3rem + 15vw, 15rem);
  }
  @media (min-width: 480px) { .sg-mast { padding-inline: 2rem; } }
  .sg-mast-copy { max-width: 1116px; margin-inline: auto; }
  .sg-mast h1 { color: var(--paper); }
  .sg-mast-lede { margin-top: 1.25rem; color: color-mix(in srgb, var(--paper) 86%, transparent); }
  .sg-mast-key {
    margin-top: 1.5rem;
    font-family: theme('fontFamily.label');
    font-size: 0.8125rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: color-mix(in srgb, var(--paper) 74%, transparent);
  }

  /* --- Style guide: shell and contents. A sticky contents list beside one
     reading column. The list is numbered because the numbers are how sections
     get referred to ("see 9, Buttons"); the headings carry no numeral. Below
     1024px, and at the Larger and Largest text sizes, it is one column and the
     contents becomes a two-column block at the top. --- */
  .sg-shell {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    row-gap: 2.5rem;
    max-width: 1180px;
    margin-inline: auto;
    padding: clamp(2.25rem, 1.5rem + 3.25vw, 4rem) 1.5rem clamp(3rem, 2rem + 4vw, 6rem);
    align-items: start;
  }
  @media (min-width: 480px) { .sg-shell { padding-inline: 2rem; } }
  @media (min-width: 1024px) {
    .sg-shell { grid-template-columns: 14rem minmax(0, 1fr); column-gap: 4rem; }
  }
  .sg-toc-title {
    display: block;
    margin-bottom: 0.85rem;
    font-family: theme('fontFamily.label');
    font-size: 0.8125rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-soft);
  }
  .sg-toc ol { counter-reset: toc; }
  .sg-toc li { counter-increment: toc; border-top: 1px solid var(--line); break-inside: avoid; }
  .sg-toc li:last-child { border-bottom: 1px solid var(--line); }
  .sg-toc a {
    position: relative;
    display: block;
    padding: 0.55rem 0 0.55rem 1.75rem;
    font-size: 1rem;
    line-height: 1.35;
    color: var(--ink-soft);
    text-decoration: none;
  }
  /* Out of flow, so the current link's underline stops at its words. */
  .sg-toc a::before {
    content: counter(toc);
    position: absolute;
    left: 0;
    top: 0.7rem;
    font-family: theme('fontFamily.label');
    font-size: 0.8125rem;
    font-variant-numeric: tabular-nums;
    color: var(--ink-soft);
  }
  .sg-toc a:hover { color: var(--ink); }
  /* The section being read, set by style-guide.js: full-strength ink and the
     2px Sky underline the site nav gives its current link. */
  .sg-toc a[aria-current] {
    color: var(--ink);
    text-decoration: underline;
    text-decoration-color: var(--sky);
    text-decoration-thickness: 2px;
    text-underline-offset: 0.3em;
  }
  @media (min-width: 560px) { .sg-toc ol { columns: 2; column-gap: 2rem; } }
  @media (min-width: 1024px) {
    .sg-toc {
      position: sticky;
      top: 1.5rem;
      max-height: calc(100vh - 3rem);
      overflow-y: auto;
      scrollbar-width: thin;
      padding-right: 0.5rem;
    }
    .sg-toc ol { columns: auto; }
  }
  /* The bar isn't sticky, so an in-page jump needs none of the site header's
     scroll padding. */
  :root:has(.sg-shell) { scroll-padding-top: 1.25rem; }

  /* --- Style guide: sections and prose. One hairline and a generous gap above
     each section, more space above a heading than below it. The prose rules
     carry no specificity and skip any element with a class, so a real
     component shown as a specimen is never restyled by the page around it. --- */
  .sg-main { min-width: 0; }
  .sg-main > section {
    margin-top: clamp(3rem, 2rem + 4vw, 4.5rem);
    padding-top: clamp(3rem, 2rem + 4vw, 4.5rem);
    border-top: 1px solid var(--line);
  }
  /* Every section heading carries the site's 8rem Sky rule. */
  .sg-main > section > h2::after {
    content: "";
    display: block;
    width: 8rem;
    height: 2px;
    margin-top: 1.5rem;
    background: var(--sky);
    border-radius: 1px;
  }
  :where(.sg-main) h3:not([class]) { margin-top: 3.25rem; }
  :where(.sg-main) h4:not([class]) { margin-top: 2.25rem; }
  :where(.sg-main) p:not([class]) { margin-top: 1rem; max-width: theme('maxWidth.prose'); color: var(--ink-soft); }
  /* A section's one opening sentence: .lede's size and measure, in full-strength ink. */
  .sg-lede { margin-top: 1.25rem; color: var(--ink); }
  :where(.sg-main) :is(ul, ol):not([class]) {
    margin-top: 1rem;
    padding-left: 1.25rem;
    max-width: theme('maxWidth.prose');
    list-style: disc;
    color: var(--ink-soft);
  }
  :where(.sg-main) :is(ul, ol):not([class]) > li + li { margin-top: 0.5rem; }
  /* Code is code: the mono face, on a quiet chip. A chip moves to the next line
     whole, and only breaks inside when it is longer than the line. */
  .sg-main code {
    display: inline-block;
    max-width: 100%;
    padding: 0.05em 0.35em;
    border-radius: 4px;
    background: var(--tag-bg);
    font-family: theme('fontFamily.mono');
    font-size: 0.84em;
    line-height: 1.35;
    color: var(--ink);
    vertical-align: baseline;
  }
  .sg-main pre {
    margin-top: 1.25rem;
    padding: 1rem 1.25rem;
    overflow-x: auto;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--surface);
    font-family: theme('fontFamily.mono');
    font-size: 0.875rem;
    line-height: 1.6;
    color: var(--ink-soft);
  }
  .sg-main pre code { display: inline; padding: 0; background: none; font-size: inherit; }
  .sg-note { font-size: 0.9375rem; }
  .sg-foot {
    margin-top: clamp(4rem, 3rem + 4vw, 6rem);
    padding-top: 1.35rem;
    border-top: 1px solid var(--line);
    font-size: 0.9375rem;
    color: var(--ink-soft);
  }

  /* --- Style guide: named rules. A short list that closes a section: each
     rule's name in the heading face over its sentence. Names are sentence case,
     not tracked capitals, because several run to a full clause. --- */
  .sg-rules { display: grid; gap: 1.35rem; margin-top: 2.25rem; max-width: theme('maxWidth.prose'); }
  .sg-rules > li { color: var(--ink-soft); }
  .sg-rules > li > b {
    display: block;
    margin-bottom: 0.2rem;
    font-family: theme('fontFamily.label');
    font-weight: 400;
    font-size: 1.0625rem;
    line-height: 1.35;
    color: var(--ink);
  }

  /* --- Style guide: ledgers. Reference tables are ledgers, not grids:
     label-voice column heads over hairline rows, no cell borders, no zebra.
     .sg-scroll wraps any ledger that may be wider than a phone, so the page
     itself never scrolls sideways. --- */
  .sg-scroll { margin-top: 1.5rem; max-width: 100%; overflow-x: auto; scrollbar-width: thin; }
  .sg-table { width: 100%; border-collapse: collapse; font-size: 1rem; line-height: 1.5; }
  .sg-scroll > .sg-table { min-width: 36rem; }
  .sg-table th, .sg-table td {
    padding: 0.7rem 1.15rem 0.7rem 0;
    text-align: left;
    vertical-align: top;
    border-top: 1px solid var(--line);
  }
  .sg-table :is(th, td):last-child { padding-right: 0; }
  .sg-table td { color: var(--ink-soft); }
  .sg-table tbody th { font-weight: 400; color: var(--ink); }
  .sg-table thead th {
    padding-top: 0;
    padding-bottom: 0.6rem;
    border-top: 0;
    vertical-align: bottom;
    font-family: theme('fontFamily.label');
    font-weight: 400;
    font-size: 0.8125rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    white-space: nowrap;
    color: var(--ink-soft);
  }
  .sg-table tbody tr:last-child > * { border-bottom: 1px solid var(--line); }
  /* A token or selector in a ledger's narrow first column never breaks at its hyphens. */
  .sg-table tbody th > code { white-space: nowrap; }
  /* A group heading inside a long ledger (the token table's families). */
  .sg-table tr.sg-group th {
    padding: 2rem 0 0.6rem;
    border-top: 0;
    font-family: theme('fontFamily.label');
    font-size: theme('fontSize.h4[0]');
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink);
  }
  .sg-table--tokens { font-size: 0.9375rem; }
  .sg-table--tokens td:last-child { min-width: 16rem; }
  /* A value cell keeps its chip and its hex on one line. */
  .sg-table--tokens td:has(> .sg-chip) { white-space: nowrap; }
  /* A small square of a color beside its name or value. */
  .sg-chip {
    display: inline-block;
    width: 0.9rem;
    height: 0.9rem;
    margin-right: 0.5rem;
    vertical-align: -0.1em;
    border: 1px solid color-mix(in srgb, var(--ink) 22%, transparent);
    border-radius: 3px;
  }
  /* Marks a palette color that appears in the mark itself. */
  .sg-tag {
    display: inline-block;
    margin-left: 0.4rem;
    padding: 0.05rem 0.4rem;
    border: 1px solid var(--line);
    border-radius: 4px;
    font-family: theme('fontFamily.label');
    font-size: 0.75rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    white-space: nowrap;
    color: var(--ink-soft);
  }
  /* A measured contrast ratio, with the level it clears printed after it in
     words, never by color. */
  .sg-ratio { white-space: nowrap; font-variant-numeric: tabular-nums; color: var(--ink); }
  .sg-ratio > span {
    display: block;
    font-family: theme('fontFamily.label');
    font-size: 0.75rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-soft);
  }
  /* scripts/build-docs.js writes the grade onto the cell as data-grade. */
  .sg-ratio[data-grade^="Below"] > span { color: var(--ink); text-decoration: underline; text-underline-offset: 0.2em; }
  /* A contrast sample: real ink on a real ground. Each one sits in a
     .sg-theme-light or .sg-theme-dark scope and paints with var(), inline because
     every row is a different pair, so a sample can't drift from its tokens. */
  .sg-pair {
    display: inline-flex;
    align-items: baseline;
    gap: 0.6rem;
    min-width: 11.5rem;
    padding: 0.45rem 0.85rem;
    border-radius: 8px;
    font-size: 1rem;
    line-height: 1.3;
  }
  .sg-pair b { font-family: theme('fontFamily.label'); font-weight: 400; font-size: 1.5rem; line-height: 1; }
  .sg-pair--edge { box-shadow: inset 0 0 0 1px var(--line); }
  /* A drawn sample of the 3px focus ring, for the contrast ledger. */
  .sg-ringbox {
    display: inline-block;
    width: 1rem;
    height: 1rem;
    margin: 0 0.35rem 0 0.25rem;
    border-radius: 3px;
    outline: 3px solid currentColor;
    vertical-align: -0.1em;
  }

  /* --- Style guide: palette strips. Each color family is a contiguous strip
     of steps over a ledger that names every one. The name and value sit under
     the color in ordinary ink, so no caption depends on the color above it.
     The grounds are the brand's fixed hex values, theme-independent. --- */
  .sg-tints { display: flex; flex-wrap: wrap; row-gap: 1.1rem; margin-top: 1.25rem; }
  .sg-tint { flex: 0 0 50%; }
  @media (min-width: 640px) { .sg-tints { flex-wrap: nowrap; } .sg-tint { flex: 1 1 0; } }
  .sg-tint > i { display: block; height: 4.5rem; }
  @media (min-width: 640px) { .sg-tint > i { height: 6.5rem; } }
  .sg-tint:first-child > i { border-radius: 12px 0 0 12px; }
  .sg-tint:last-child > i { border-radius: 0 12px 12px 0; }
  .sg-tint:only-child > i { border-radius: 12px; }
  /* A step too close to the page ground to show its own edge. */
  .sg-tint--edge > i { box-shadow: inset 0 0 0 1px var(--line); }
  .sg-tint > b {
    display: block;
    margin-top: 0.6rem;
    padding-right: 0.6rem;
    font-family: theme('fontFamily.label');
    font-weight: 400;
    font-size: 0.9375rem;
    line-height: 1.3;
    color: var(--ink);
  }
  .sg-main .sg-tint > code { margin-top: 0.25rem; font-size: 0.8125rem; }
  /* The palette tokens never change with the theme, so these point at them
     rather than repeating the hex. Deep-tone·alt has no token: its value is the
     README's. */
  .sg-chip-navy { background: var(--navy); }
  .sg-chip-sky { background: var(--sky); }
  .sg-chip-cyan { background: var(--cyan); }
  .sg-chip-coral { background: var(--coral); }
  .sg-chip-midnight { background: var(--midnight); }
  .sg-chip-paper { background: var(--paper); }
  .sg-chip-seagrass { background: var(--seagrass); }
  .sg-chip-lantern { background: var(--lantern); }
  .sg-chip-sand { background: var(--sand); }
  .sg-chip-deep-tone { background: #2E3F72; }
  .sg-ground-paper { background: var(--paper); color: var(--navy); }
  .sg-ground-midnight { background: var(--midnight); color: var(--paper); }
  .sg-ground-navy { background: var(--navy); color: var(--paper); }
  .sg-favicon-dark { background: var(--midnight); border-radius: 4px; padding: 2px; }

  /* --- Style guide: specimen plates. A specimen sits on the ground it ships
     on. The base plate is Surface with a hairline; the navy plate is the
     booking band's ground and takes .on-dark beside it for the Sky focus ring.
     .sg-theme-light and .sg-theme-dark (defined with the tokens) re-point a
     plate at one palette, whatever theme the page is in. --- */
  .sg-plate {
    margin-top: 1.5rem;
    padding: 1.75rem 1.5rem;
    border: 1px solid var(--line);
    border-radius: theme('borderRadius.card');
    background: var(--surface);
    color: var(--ink);
  }
  .sg-plate--navy { background: var(--navy); border-color: var(--navy); color: var(--paper); }
  .sg-plate--navy :is(h3, h4) { color: var(--paper); }
  .sg-plate-cap { margin-top: 0.6rem; font-size: 0.9375rem; line-height: 1.5; color: var(--ink-soft); }
  .sg-row { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem 1.25rem; }
  /* A forced focus ring, for showing the ring without asking the reader to tab. */
  .sg-ring-demo { outline: 3px solid var(--ring); outline-offset: 2px; }
  .sg-plate--navy .sg-ring-demo { outline-color: var(--focus-on-dark); }
  /* A specimen that must not act: the button keeps its look and loses its pointer. */
  .sg-inert { pointer-events: none; }

  /* Mark and lockup tiles are fixed to their documented ground per
     assets/logomarks/README.txt, so they stay correct in either theme. */
  .sg-tile {
    border: 1px solid var(--line);
    border-radius: theme('borderRadius.card');
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.85rem;
    padding: 2rem 1.25rem 1.4rem;
    text-align: center;
  }
  .sg-tile img { width: auto; }
  .sg-tile p {
    margin: 0;
    max-width: none;
    font-family: theme('fontFamily.label');
    font-size: 0.8125rem;
    letter-spacing: 0.02em;
    color: inherit;
  }
  .sg-tile .sg-tile-hex { font-family: theme('fontFamily.body'); font-size: 0.8125rem; opacity: 0.8; }
  .sg-tiles { display: grid; gap: theme('gap.gutter'); grid-template-columns: repeat(2, minmax(0, 1fr)); margin-top: 1.5rem; }
  @media (min-width: 768px) { .sg-tiles--5 { grid-template-columns: repeat(5, minmax(0, 1fr)); } }
  @media (max-width: 479px) { .sg-tiles--2 { grid-template-columns: minmax(0, 1fr); } }

  /* --- Style guide: type specimens. The faces at poster size, then each role
     set live from its own token beside its name and numbers. --- */
  .sg-faces { display: grid; gap: 1.5rem theme('gap.gutter'); margin-top: 1.75rem; }
  @media (min-width: 640px) { .sg-faces { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
  .sg-face { border-top: 1px solid var(--line); padding-top: 1rem; }
  .sg-face-big { font-size: clamp(3.25rem, 2rem + 5vw, 5.25rem); line-height: 1; color: var(--ink); }
  .sg-face-name { margin-top: 0.75rem; font-family: theme('fontFamily.label'); font-size: theme('fontSize.h4[0]'); line-height: 1.2; color: var(--ink); }
  .sg-face-note { margin-top: 0.35rem; color: var(--ink-soft); }
  .sg-face-alpha { margin-top: 0.5rem; font-size: 1.25rem; line-height: 1.4; color: var(--ink); overflow-wrap: anywhere; }
  .sg-face--logo :is(.sg-face-big, .sg-face-alpha) { font-family: theme('fontFamily.logo'); letter-spacing: 0.04em; text-transform: uppercase; }
  .sg-face--label :is(.sg-face-big, .sg-face-alpha) { font-family: theme('fontFamily.label'); }
  .sg-face--mono :is(.sg-face-big, .sg-face-alpha) { font-family: theme('fontFamily.mono'); }

  .sg-specimens { margin-top: 1.75rem; }
  .sg-specimen {
    display: grid;
    gap: 0.6rem;
    padding-block: 1.4rem;
    border-top: 1px solid var(--line);
    align-items: baseline;
  }
  .sg-specimen:last-child { border-bottom: 1px solid var(--line); }
  @media (min-width: 768px) {
    .sg-specimen { grid-template-columns: 12.5rem minmax(0, 1fr); column-gap: 2rem; }
  }
  .sg-specimen-meta { font-size: 0.9375rem; line-height: 1.5; color: var(--ink-soft); }
  .sg-specimen-meta > b {
    display: block;
    margin-bottom: 0.3rem;
    font-family: theme('fontFamily.label');
    font-weight: 400;
    font-size: 0.8125rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink);
  }
  .sg-sample { color: var(--ink); overflow-wrap: anywhere; }
  .sg-spec-h1 { font-family: theme('fontFamily.label'); font-size: theme('fontSize.h1'); line-height: 1.08; letter-spacing: -0.005em; }
  .sg-spec-h2 { font-family: theme('fontFamily.label'); font-size: theme('fontSize.h2'); line-height: 1.18; }
  .sg-spec-h3 { font-family: theme('fontFamily.label'); font-size: theme('fontSize.h3'); line-height: 1.25; }
  .sg-spec-h4 { font-family: theme('fontFamily.label'); font-size: theme('fontSize.h4[0]'); line-height: 1.3; }
  .sg-spec-row { font-family: theme('fontFamily.label'); font-size: 1.0625rem; }
  .sg-spec-caption { font-size: 0.875rem; }
  .sg-spec-label-sm { font-family: theme('fontFamily.label'); font-size: 0.8125rem; letter-spacing: 0.08em; text-transform: uppercase; }
  .sg-spec-overline { font-family: theme('fontFamily.label'); font-size: 0.75rem; letter-spacing: 0.12em; text-transform: uppercase; }

  /* --- Style guide: depth and shape specimens. One card per shadow or radius,
     drawn with the real value. --- */
  .sg-depths { display: grid; gap: 1.75rem theme('gap.gutter'); margin-top: 1.75rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  @media (min-width: 768px) { .sg-depths { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
  .sg-depth {
    display: flex;
    align-items: flex-end;
    min-height: 5.5rem;
    padding: 0.75rem 0.9rem;
    border: 1px solid var(--line);
    border-radius: theme('borderRadius.card');
    background: var(--surface);
    font-family: theme('fontFamily.label');
    font-size: 0.9375rem;
    color: var(--ink);
  }
  .sg-depth--rest { box-shadow: theme('boxShadow.card'); }
  .sg-depth--lift { box-shadow: theme('boxShadow.lift'); }
  .sg-depth--float { box-shadow: 0 18px 44px -14px rgba(10, 15, 36, 0.32); }
  .sg-depth--tab { box-shadow: -2px 2px 16px rgba(10, 15, 36, 0.22); }
  .sg-depth--r12 { border-radius: 12px; }
  .sg-depth--r10 { border-radius: 10px; }
  .sg-depth--r8 { border-radius: 8px; }
  .sg-depth--round { border-radius: 999px; justify-content: center; align-items: center; width: 5.5rem; }

  /* --- Grid demo: the real .grid-9 tracks at the real gutter, so the strip is
     the thing it documents rather than a picture of it. It sits in a .table-wrap
     with a min-width, which is how the guide handles anything too wide to fold. --- */
  .sg-grid-demo {
    display: grid;
    grid-template-columns: repeat(9, minmax(0, 1fr));
    gap: theme('gap.gutter');
  }
  .sg-grid-demo + .sg-grid-demo { margin-top: theme('gap.gutter'); }
  .sg-grid-demo > * {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 2.5rem;
    border-radius: 6px;
    background: var(--tag-bg);
    font-family: theme('fontFamily.label');
    font-size: 0.75rem;
    color: var(--ink-soft);
    white-space: nowrap;
  }
  /* The two filled spans borrow token pairs that are already contrast-checked in
     both themes. The *-ink accents point back at the raw palette in dark, so
     Paper on them would land near 2.3:1 — these pairs put dark ink on the bright
     ground instead, the way the primary button and the Before/After panel do. */
  .sg-grid-demo > .sg-grid-fill {
    background: var(--cta-bg);
    color: var(--cta-ink);
  }
  .sg-grid-demo > .sg-grid-fill--alt {
    background: var(--ba-after-bg);
    color: var(--ba-after-ink);
  }
  /* The demo documents the >=1024px behavior, so it keeps its nine tracks at
     every width — .grid-9's own collapse rule would otherwise flatten it here
     and the spec would illustrate nothing. */
  .sg-grid-demo > * { grid-column: span 1; }
  .sg-grid-demo > .sg-span-3 { grid-column: span 3; }
  .sg-grid-demo > .sg-span-4 { grid-column: span 4; }
  .sg-grid-demo > .sg-span-5 { grid-column: span 5; }
  .sg-grid-demo > .sg-span-6 { grid-column: span 6; }
  .sg-grid-demo > .sg-span-9 { grid-column: span 9; }
  /* Unused columns: outlined, not filled, so white space reads as space. */
  .sg-grid-demo > .sg-grid-ghost {
    background: none;
    border: 1px dashed var(--line);
    color: var(--ink-soft);
  }
  /* Annotations over a diagram, in the label voice. The wireframes caption every
     frame the same way, so their captions take this by position rather than
     carrying the class on 42 elements. */
  .sg-grid-label,
  .sg-wire-frames > div > p {
    margin-top: 1.25rem;
    margin-bottom: 0.45rem;
    font-family: theme('fontFamily.label');
    font-size: 0.75rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-soft);
  }
  .sg-grid-label:first-child,
  .sg-wire-frames > div > p { margin-top: 0; }

  /* --- Theme panels: the same card rendered in each palette, side by side, so
     light and dark can be compared without toggling the page. --- */
  .sg-theme-pair { display: grid; gap: theme('gap.gutter'); margin-top: 1.5rem; }
  @media (min-width: 640px) { .sg-theme-pair { grid-template-columns: 1fr 1fr; } }
  .sg-theme-panel {
    background: var(--bg);
    color: var(--ink);
    border: 1px solid var(--line);
    border-radius: theme('borderRadius.card');
    padding: 1.5rem;
  }
  .sg-theme-panel .sg-theme-name {
    font-family: theme('fontFamily.label');
    font-size: 0.8125rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-soft);
  }
  .sg-theme-swatches { display: flex; gap: 0.4rem; margin-top: 1rem; }
  .sg-theme-swatches > span {
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 5px;
    border: 1px solid var(--line);
  }

  /* --- Layout wireframes: one schematic per layout the site actually uses.
     Plain boxes on the real tokens — a picture of the tracks, not a copy of
     the section, so a diagram stays true when the copy inside it changes.
     Each frame sets its own tracks through the .sg-wf-row* classes rather than
     Tailwind's `grid`, which keeps reader mode's flatten-everything rule off
     the diagrams: a wireframe collapsed to one column illustrates nothing.

     The vocabulary is flat, not BEM: every block and every row variant carries
     the shared base through the :is() lists below, so markup writes
     `class="sg-wf-card sg-wf-tall"` rather than repeating a base class on 230
     elements. Order matters — each base rule comes before the variants that
     override it. --- */
  .sg-wire { border-top: 1px solid var(--line); padding-block: 2rem; }
  .sg-wire:first-child { border-top: 0; padding-top: 0.5rem; }
  .sg-wire-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.15rem 0.85rem; }
  /* The layout's name and, beside it, where it lives. */
  .sg-wire-head h4 { margin-top: 0; font-size: theme('fontSize.h4[0]'); line-height: 1.3; }
  .sg-wire-where {
    margin: 0;
    font-family: theme('fontFamily.mono');
    font-size: 0.75rem;
    color: var(--ink-soft);
  }
  /* The wide frame with the phone frame beside it. Both stack on a narrow
     screen, and the phone frame keeps its width there — it reads as a phone
     because it is narrow, so letting it fill the column would lose the point.
     Widths are in rem, so the whole diagram grows with the text-size setting
     instead of being zoomed back down. */
  .sg-wire-frames { display: grid; gap: 1.1rem; margin-top: 1.25rem; align-items: start; }
  @media (min-width: 640px) {
    .sg-wire-frames { grid-template-columns: minmax(0, 1fr) 9rem; gap: 1.75rem; }
    .sg-wire-frames--trio { grid-template-columns: minmax(0, 1fr) 9rem 9rem; }
    .sg-wire-frames--duo  { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  }
  .sg-wire-frame {
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--bg);
    padding: 0.5rem;
    display: grid;
    gap: 0.4rem;
    align-content: start;
  }
  .sg-wire-frame--phone { max-width: 9rem; }

  /* The key: each block style beside what it stands for. */
  .sg-wf-key-list { display: grid; gap: 0.7rem 1.5rem; margin-top: 0.8rem; }
  @media (min-width: 560px) { .sg-wf-key-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
  @media (min-width: 900px) { .sg-wf-key-list { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
  .sg-wf-key-list > li {
    display: grid;
    grid-template-columns: 3.75rem minmax(0, 1fr);
    align-items: center;
    gap: 0.7rem;
    font-size: 0.9375rem;
    color: var(--ink-soft);
  }

  /* The reading column drawn inside the viewport: dashed, because the canvas
     is a width limit, not a thing you can see. The inset stands in for the cap
     plus the side padding — a proportion, not a measurement. */
  .sg-wf-canvas {
    margin-inline: 9%;
    padding: 0.35rem;
    border: 1px dashed var(--wf-line, var(--line));
    border-radius: 7px;
    display: grid;
    gap: 0.4rem;
    align-content: start;
  }
  .sg-wf-canvas--wide { margin-inline: 4%; }
  /* A band whose ground runs the full width with only its content in the
     canvas — the closing booking panel. It re-points the block tokens rather
     than reaching in with descendant selectors, so a CTA on the navy ground
     still reads as a CTA, the way .btn-primary does on dark. */
  .sg-wf-band {
    margin: -0.5rem;
    padding: 0.5rem 0;
    border-radius: 9px;
    background: var(--tag-bg);
  }
  .sg-wf-band--dark {
    background: var(--navy);
    --wf-bg: rgba(245, 251, 255, 0.16);
    --wf-ink: var(--paper);
    --wf-key-bg: var(--sky);
    --wf-key-ink: var(--navy);
    --wf-line: rgba(245, 251, 255, 0.35);
  }

  /* Rows and stacks inside a frame. A row that has real proportions — the
     hero's 4:5, the fine print's 1.45:1 — states them; the rest split evenly. */
  .sg-wf-stack { display: grid; gap: 0.4rem; align-content: start; }
  :is(.sg-wf-row, .sg-wf-row-4, .sg-wf-row-5, .sg-wf-row-hero,
      .sg-wf-row-rail, .sg-wf-row-aside, .sg-wf-row-doc, .sg-wf-row-icon,
      .sg-wf-row-bar) {
    display: grid;
    gap: 0.4rem;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: stretch;
  }
  .sg-wf-row-4     { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .sg-wf-row-5     { grid-template-columns: repeat(5, minmax(0, 1fr)); }
  .sg-wf-row-hero  { grid-template-columns: 4fr 5fr; }
  .sg-wf-row-rail  { grid-template-columns: 22fr 56fr; }
  .sg-wf-row-aside { grid-template-columns: 16fr 54fr; }
  .sg-wf-row-doc   { grid-template-columns: 1.45fr 1fr; }
  .sg-wf-row-icon  { grid-template-columns: 1.5rem minmax(0, 1fr); gap: 0.3rem; }
  .sg-wf-row-bar   { grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; }
  /* Closes a stack or a row up; reads the same on either. */
  .sg-wf-tight { gap: 0.25rem; }
  /* The empty middle track of a bar row: the space between the two ends, with
     nothing in it to draw. */
  .sg-wf-gap { min-width: 1rem; }
  /* A block held narrower than the column it sits in — the intake card. */
  .sg-wf-measure { width: 78%; margin-inline: auto; }

  /* How it works: the same four grid areas the real steps take, cards pinned to
     the outer edges of a two-column block with the thread weaving the gap. */
  .sg-wf-zig {
    position: relative;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.4rem 1.5rem;
  }
  .sg-wf-zig::before {
    content: "";
    position: absolute;
    top: 0.75rem;
    bottom: 0.75rem;
    left: 50%;
    border-left: 1px dashed var(--line);
  }
  /* width, not max-width: these are flex containers, and justify-self would
     otherwise shrink them to their label. */
  .sg-wf-zig > * { width: 88%; }
  .sg-wf-zig > :nth-child(1) { grid-area: 1 / 1; justify-self: start; }
  .sg-wf-zig > :nth-child(2) { grid-area: 2 / 2; justify-self: end; }
  .sg-wf-zig > :nth-child(3) { grid-area: 3 / 1; justify-self: start; }
  .sg-wf-zig > :nth-child(4) { grid-area: 4 / 2; justify-self: end; }

  /* Float demos: the vignette photo, and About between 450 and 699px. */
  .sg-wf-float::after { content: ""; display: block; clear: both; }
  .sg-wf-float > .sg-wf-floated { float: right; width: 40%; margin: 0 0 0.4rem 0.4rem; }
  .sg-wf-float > :not(.sg-wf-floated) { margin-bottom: 0.4rem; }

  /* The blocks themselves. Labels are set small and centered; a block that runs
     out of room clips rather than pushing the diagram out of shape. The two
     tokens let a band re-point every block inside it in one place. */
  :is(.sg-wf-b, .sg-wf-xs, .sg-wf-tall, .sg-wf-xl, .sg-wf-card, .sg-wf-photo,
      .sg-wf-key, .sg-wf-alt, .sg-wf-ghost, .sg-wf-bleed, .sg-wf-floated) {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 1.5rem;
    padding: 0.2rem 0.3rem;
    border-radius: 5px;
    background: var(--wf-bg, var(--tag-bg));
    font-family: theme('fontFamily.label');
    font-size: 0.6875rem;
    line-height: 1.2;
    letter-spacing: 0.01em;
    text-align: center;
    color: var(--wf-ink, var(--ink-soft));
    overflow: hidden;
  }
  .sg-wf-xs   { min-height: 0.7rem; }
  .sg-wf-tall { min-height: 3rem; }
  .sg-wf-xl   { min-height: 5rem; }
  /* The filled blocks borrow token pairs already contrast-checked in both
     themes, same as the grid demo's spans. */
  .sg-wf-key  { background: var(--wf-key-bg, var(--cta-bg)); color: var(--wf-key-ink, var(--cta-ink)); }
  .sg-wf-alt  { background: var(--ba-after-bg); color: var(--ba-after-ink); }
  .sg-wf-card { background: none; border: 1px solid var(--wf-line, var(--line)); }
  /* Dashed = space the layout leaves empty, or a block that isn't there at
     this width. Never a thing you can see on the page. */
  .sg-wf-ghost { background: none; border: 1px dashed var(--wf-line, var(--line)); }
  /* Photography, hatched so it doesn't read as another panel. */
  .sg-wf-photo {
    background-image: repeating-linear-gradient(-45deg,
      transparent 0 0.3rem, var(--line) 0.3rem 0.36rem);
  }
  /* A block that bleeds off the right edge of the frame — the hero photo. */
  .sg-wf-bleed { margin-right: -0.5rem; border-radius: 5px 0 0 5px; }

  /* Overlay frame: the contact drawer over a dimmed page, plus the tab that
     opens it. The scrim and panel are absolutely placed the way the real ones
     are fixed, so the diagram keeps the drawer's proportion (min(30rem, 100vw)
     against the viewport). */
  .sg-wf-overlay { position: relative; }
  .sg-wf-scrim {
    position: absolute;
    inset: 0;
    border-radius: 9px;
    background: rgba(10, 15, 36, 0.55);
  }
  .sg-wf-drawer {
    position: absolute;
    inset: 0 0 0 auto;
    width: 46%;
    padding: 0.4rem;
    border-left: 1px solid var(--line);
    border-radius: 0 9px 9px 0;
    background: var(--bg);
    display: grid;
    gap: 0.3rem;
    align-content: start;
  }
  .sg-wf-tab {
    position: absolute;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 0.85rem;
    height: 3.25rem;
    border-radius: 5px 0 0 5px;
    background: var(--sky);
  }

  /* --- Style guide on paper: kept here, inside the layer, rather than in the
     PRINT section at the end of the file. That section is outside @layer and so
     is never purged; these rules name only the guide's classes, and in here
     they land in style-guide.css alone. They follow the furniture above, so
     they still win on source order. Every selector must carry a class: one
     rule without one (an attribute selector, say) and Tailwind keeps the whole
     @media block in both stylesheets. --- */
  @media print {
    /* The bar is navigation, so it goes; the masthead's navy becomes plain
       type, and the contents folds to two columns. */
    .sg-bar { display: none; }
    .sg-toc a[aria-current] { color: var(--ink-soft); text-decoration: none; }
    .sg-mast { padding: 1rem 0 0; background: none; color: var(--ink); }
    .sg-mast h1, .sg-mast-lede { color: var(--ink); }
    .sg-mast-maze, .sg-mast-key { display: none; }
    .sg-shell { display: block; max-width: none; padding: 1rem 0 0; }
    .sg-toc { position: static; max-height: none; overflow: visible; margin-bottom: 1.5rem; }
    .sg-toc ol { columns: 2; column-gap: 2rem; }
    .sg-main > section { margin-top: 1.75rem; padding-top: 1.5rem; }
    .sg-main :is(h2, h3, h4) { break-after: avoid; }
    .sg-scroll { overflow: visible; }
    .sg-scroll > .sg-table { min-width: 0; }
    .sg-table tr, .sg-specimen, .sg-rules > li, .sg-plate, .sg-tile, .sg-theme-panel { break-inside: avoid; }
    /* Color is the content of these, so they print. */
    .sg-tint > i,
    .sg-chip,
    .sg-pair,
    .sg-plate--navy,
    .sg-theme-panel,
    .sg-theme-swatches > span,
    .sg-grid-demo > *,
    .sg-tile,
    .sg-favicon-dark {
      -webkit-print-color-adjust: exact;
              print-color-adjust: exact;
    }
    /* So are the wireframes. Their blocks are filled shapes, and a printer
       that drops the fills leaves a page of empty outlines. */
    .sg-wf-b, .sg-wf-xs, .sg-wf-tall, .sg-wf-xl, .sg-wf-photo, .sg-wf-key,
    .sg-wf-alt, .sg-wf-bleed, .sg-wf-floated, .sg-wf-band,
    .sg-wf-tab,
    .sg-wf-scrim {
      -webkit-print-color-adjust: exact;
              print-color-adjust: exact;
    }
    /* The wide frame and the phone frame side by side, so a layout and its
       phone form stay on one line of the printed page. */
    .sg-wire { break-inside: avoid; }
    .sg-wire-frames { grid-template-columns: minmax(0, 1fr) 9rem; gap: 1.5rem; }
    .sg-wire-frames--trio { grid-template-columns: minmax(0, 1fr) 9rem 9rem; }
    .sg-wire-frames--duo  { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  }
}

/* ============================================================================
   UTILITIES
   ========================================================================== */
@layer utilities {
  .on-dark { color: var(--paper); }
  .grid-9 { display: grid; grid-template-columns: repeat(9, minmax(0, 1fr)); gap: theme('gap.gutter'); }
  @media (max-width: 1023px) {
    .grid-9 { grid-template-columns: 1fr; }
    .grid-9 > * { grid-column: auto !important; }
  }
  /* Hero: keep the framed photo beside the heading below the lg breakpoint too,
     mirroring the desktop split, instead of dropping it full-width under the
     copy. Tablet range keeps the whole illustration at its natural ratio. */
  @media (max-width: 1023px) {
    #top .grid-9 { grid-template-columns: minmax(0, 1fr) 38%; }
    #top .grid-9 > div:nth-of-type(2) { margin-top: 0; }
  }
  /* Below 500px the hero image itself is hidden (index.html), so collapse
     back to a single column instead of reserving the 38% track for nothing. */
  @media (max-width: 499px) {
    #top .grid-9 { grid-template-columns: 1fr; }
  }
  /* Below the md breakpoint the column is too narrow for the full 4:3
     illustration, so the figure stretches to the copy's height and the photo
     covers it. The focus sits just left of the figures (~78%): they stay in
     frame at every width, and the maze on their left fills more of the strip
     as the screen widens, less as it narrows. */
  @media (max-width: 767px) {
    #top .grid-9 > div:nth-of-type(2) { align-self: start; margin-top: 0.5rem; }
    #top .grid-9 > div:nth-of-type(2) .framed img {
      width: 100%;
      aspect-ratio: 1 / 2;
      max-height: 498px;
      object-fit: cover;
      object-position: 85% center;
    }
  }
  /* Wherever the hero photo shows, run it flush to the browser's right edge:
     pull it across .canvas's 2rem padding plus the gutter that opens once
     .canvas hits its 1000px cap. Square off the right side so it reads as
     bleeding off-screen. The people sit on the right, so they stay in view. */
  @media (min-width: 768px) {
    #top .grid-9 > div:nth-of-type(2) { align-self: stretch; }
    #top .grid-9 > div:nth-of-type(2) .framed { height: 100%; max-height: 500px; }
    #top .grid-9 > div:nth-of-type(2) .framed img {
      height: 100%;
      object-fit: cover;
      object-position: 82% 80%;
    }
  }
  @media (min-width: 500px) {
    #top .grid-9 > div:nth-of-type(2) {
      margin-right: calc(min(0px, (1000px - 100vw) / 2) - 2rem);
    }
    #top .grid-9 > div:nth-of-type(2) .framed {
      border-radius: theme('borderRadius.card') 0 0 theme('borderRadius.card');
      border-right: 0;
    }
  }
  /* About: at >=700px the left column (heading + portrait) is one sticky rail beside
     the scrolling bio. The rail only needs room for the ~200px portrait, so it gets a
     fixed track rather than a 4/9 share that would strand a gap before the bio.
     Between 450 and 699 the rail goes full-width and the portrait floats right so the
     bio wraps around it. Below 450 everything stacks. */
  @media (min-width: 700px) {
    #about .grid-9 { grid-template-columns: 18rem minmax(0, 1fr); column-gap: 3rem; }
    #about .about-rail { position: sticky; top: calc(var(--header-h) + 1.5rem); align-self: start; }
  }
  @media (min-width: 450px) and (max-width: 699px) {
    #about .grid-9 { display: block; }
    #about .grid-9::after { content: ""; display: block; clear: both; }
    #about .about-rail > .meta { margin-bottom: 1.5rem; }
    #about .about-rail > figure { float: right; width: 45%; margin: 0 0 1rem 1.75rem; }
  }
  @media (min-width: 1024px) {
    #about .grid-9 { grid-template-columns: 22rem minmax(0, 1fr); column-gap: 4rem; }
  }
  /* Paper text at reduced strength, for copy on the navy/midnight grounds. */
  .on-dark-soft { color: rgba(245, 251, 255, 0.82); }
  /* Stagger index for [data-rise] entrance animations. */
  .rise-0 { --i: 0; }
  .rise-1 { --i: 1; }
  .rise-3 { --i: 3; }
}

/* ============================================================================
   PRINT
   Unlayered, so it follows Tailwind's utilities and wins on source order.
   Written for the browser default of "background graphics" off: anything that
   has to reach paper is carried by text color or a border, never a fill.
   Margin boxes are Chromium-only; other engines fall back to the footer URL.
   ========================================================================== */
@page {
  margin: 0.6in 0.65in 0.7in;
  @bottom-left {
    /* The number is split across strings so it never sits whole in site.css. */
    content: "Coastal Healthcare Advocates \00B7  (757) " "574-" "0771 (Monday\2013 Friday, 8:00 AM\2013 5:00 PM ET) \00B7  coastalhealthcareadvocates.org";
    font-family: Questrial, "Century Gothic", system-ui, sans-serif;
    font-size: 8pt;
    color: #3C4A73;
  }
  @bottom-right {
    content: counter(page) " of " counter(pages);
    font-family: "IBM Plex Mono", ui-monospace, monospace;
    font-size: 8pt;
    color: #3C4A73;
  }
}

@media print {
  /* Paper is always the light theme, whatever the screen was set to. The triple
     :root outranks both dark blocks ((0,2,0) each). */
  :root:root:root {
    --bg: #FFFFFF;
    --surface: #FFFFFF;
    --ink: #0F1E4A;
    --ink-soft: #3C4A73;
    --line: #C9D3E6;
    --line-soft: #DDE4F2;
    --tag-bg: #FFFFFF;
    --warn-bg: #FFFFFF;
    --warn-line: #A9B8D4;
    --warn-ink: #2F4468;
    --link: #0B5FA5;
    --ring: #0F1E4A;
    --sky-ink: #1F74C4;
    --seagrass-ink: #087A58;
    --lantern-ink: #A85A00;
    --coral-ink: #C4140F;
    --ba-after-bg: var(--seagrass-ink);
    --ba-after-ink: #FFFFFF;
    --brand-mark: url('../logomarks/marks/advocate-beacon-primary.svg');
    --brand-ink: var(--navy);
    --brand-sub: var(--sky-ink);
    color-scheme: light;
  }

  /* Every rem shrinks together: 18px body copy lands at ~11pt. */
  html { font-size: 13px; scroll-padding-top: 0; }
  body { background: #FFFFFF; color: var(--ink); line-height: 1.5; }

  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
    box-shadow: none !important;
    text-shadow: none !important;
  }
  /* Entrance start-states hold content invisible until it scrolls into view. */
  .reveal-pending,
  .reveal-pending > *,
  .reveal-pending tr,
  [data-rise],
  .how-flow .how-step { opacity: 1 !important; transform: none !important; }
  .reveal-pending::after { transform: none !important; }

  /* Scroll containers don't fragment across pages; let them flow. */
  .table-wrap,
  [data-turnstile-field] { overflow: visible !important; }

  /* --- Screen-only chrome and interactions --- */
  body::after,
  .skip-link,
  .site-header nav,
  #nav-toggle,
  .drawer-tab,
  #contact-backdrop,
  #contact-modal,
  #intake,
  .path-card ul,
  p:has(> button[data-open-contact]),
  button[data-open-contact],
  p:has(> a[href$=".pdf"]),
  p:has(> a.link-quiet[href="index.html"]),
  .hero-atmos::before,
  .path-card::before,
  .how-flow::before,
  .how-spine,
  .how-pin,
  .ba-list::before,
  .faq-sign,
  .theme-switch,
  .theme-orb,
  .site-footer nav,
  .site-footer .brand-lockup,
  .site-footer div:has(> a > .icon) { display: none !important; }

  /* --- Type and rhythm --- */
  h1, h2, h3, h4 { break-after: avoid; }
  p, li, dd { orphans: 3; widows: 3; }
  img, svg, figure, tr, .card, .callout, .ba-list > li, .dl-rows > div { break-inside: avoid; }

  a[href^="#"],
  a[href^="tel:"],
  a[href^="mailto:"] { text-decoration: none; color: inherit; }
  main a[href^="http"]:not(.btn):not([href^="https://coastalhealthcareadvocates.org"])::after {
    content: " (" attr(href) ")";
    font-size: 0.85em;
    color: var(--ink-soft);
    overflow-wrap: anywhere;
  }

  .canvas,
  .canvas-wide { max-width: none; padding-inline: 0; }
  main > section { background: none !important; border: 0 !important; }
  .section,
  .section--tight { padding-block: 1.1rem; }

  /* The before/after marks are a colored disc with a white glyph; print drops
     background fills, so redraw them as an ink glyph inside a thin ring. */
  .ba-mark {
    background: none !important;
    border: 1pt solid var(--coral-ink);
    color: var(--coral-ink);
  }
  .ba-mark--yes { border-color: var(--seagrass-ink); color: var(--seagrass-ink); }
  .ba-mark svg { stroke: currentColor; }

  /* The Sky accent rule under section headings is a fill; redraw it as a border. */
  .section h2[id$="-h"]::after,
  .section--tight h2[id$="-h"]::after {
    height: 0;
    width: 4.5rem;
    margin-block: 0.55rem 1rem;
    background: none;
    border-top: 2px solid var(--sky);
  }

  /* --- Header: the logo alone, once, at the top of page one --- */
  .site-header {
    position: static;
    background: none;
    backdrop-filter: none;
    border-bottom: 1px solid var(--line);
    transform: none !important;
    opacity: 1 !important;
    visibility: visible !important;
  }
  .site-header > .canvas-wide { padding-block: 0 0.9rem; }
  .brand-lockup {
    --logo-h: 0.6in;
    -webkit-print-color-adjust: exact;
            print-color-adjust: exact;
  }

  /* --- Hero: the photo is atmosphere, not content — drop it and let the
     heading column take the full width. --- */
  #top .grid-9 { display: block; }
  #top .grid-9 > div:nth-of-type(2) { display: none; }
  .hero-actions,
  .hero-byline { margin-top: 1.25rem; }

  /* --- Two entry paths --- */
  .path-card { padding: 1rem 1.2rem; gap: 0.5rem; }

  /* --- What is an advocate: the vignette photo is atmosphere, not content --- */
  .vignette-figure { display: none; }
  .callout.p-7 { padding: 1.1rem 1.3rem; }

  /* --- Services: two columns of rows --- */
  .service-rows { grid-template-columns: 1fr 1fr; column-gap: 2rem; }
  .service-rows.mt-10 { margin-top: 0.5rem; }
  .service-row {
    grid-template-columns: 2.25rem minmax(0, 1fr);
    column-gap: 0.8rem;
    padding-block: 0.85rem;
    break-inside: avoid;
    background: none;
  }
  .service-row > .service-icon { width: 2.25rem; height: 2.25rem; background: none; }
  .service-row > .service-icon .icon-lg { width: 2rem; height: 2rem; }
  .who-photo { display: none; }

  /* --- Who we help --- */
  .who-list { grid-template-columns: 1fr 1fr; gap: 1.1rem 2rem; }
  .who-list.mt-10 { margin-top: 0.5rem; }
  .who-item { grid-template-columns: 2.25rem minmax(0, 1fr); column-gap: 0.8rem; break-inside: avoid; }

  /* --- About: the portrait is a people photo, not content --- */
  #about .grid-9 { display: block; }
  #about .about-rail { position: static; }
  #about .about-rail > figure { display: none; }
  .about-bio .mt-12 { margin-top: 1.75rem; }
  .dl-rows.mt-8 { margin-top: 0.75rem; }
  .dl-rows > div { grid-template-columns: 8.5rem 1fr; padding-block: 0.55rem; }

  /* --- How it works: a flat 2x2 of cards, each keyed by its step color --- */
  .how-flow { margin-top: 0.5rem; }
  .how-steps {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem 1.25rem;
    align-items: stretch;
  }
  .how-step {
    grid-area: auto !important;
    justify-self: stretch !important;
    max-width: none;
    break-inside: avoid;
  }
  .how-card {
    height: 100%;
    padding: 0;
    rotate: none !important;
    scale: none !important;
    border: 1px solid color-mix(in srgb, var(--how-accent, var(--sky)) 55%, var(--line));
    border-radius: 12px;
  }
  .how-card-inner { height: 100%; background: none; border: 0; padding: 0.8rem 1rem 1rem; }
  .step-num { font-size: 2rem; }

  /* --- Pricing --- */
  .price-choices { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.5rem; margin-top: 1rem; }
  .price-choice { padding: 0.6rem 0.75rem; break-inside: avoid; }
  .price-choice-fee { font-size: 1.1rem; }
  .price-choice-cta { display: none; }
  .price-choice--start { background: none; }
  .price-table { min-width: 0; }
  .price-table thead { display: table-header-group; }
  .price-table th,
  .price-table td { padding: 0.5rem 0.85rem; }
  .price-group { background: none; padding-top: 0.75rem; padding-bottom: 0.3rem; }

  /* --- FAQs: every answer open --- */
  .faq-layout { display: block; }
  .faq-aside { position: static; margin-bottom: 0.75rem; }
  .faq-more { max-width: none; }
  .faq-item { break-inside: avoid; }
  .faq-trigger { padding: 0.8rem 0 0.35rem; font-size: 1.2rem; }
  .faq-panel > * { padding-bottom: 0.85rem; }
  /* A closed <details> keeps its answer in ::details-content, which the browser
     hides with content-visibility. Kept in a rule of its own: a browser that
     doesn't know the pseudo-element (before Chrome 131, Safari 18.4 and
     Firefox 143) drops the whole rule, and prints only the questions. */
  .faq-item::details-content { content-visibility: visible; }

  /* --- Closing booking panel: navy fill becomes a navy outline --- */
  main > #schedule {
    margin-top: 1rem;
    padding-block: 1.25rem;
    border: 1.5px solid var(--navy) !important;
    border-radius: 12px;
    color: var(--ink);
    break-inside: avoid;
  }
  #schedule :is(h2, p, .text-paper, .on-dark-soft) { color: var(--ink); }
  #schedule .schedule-actions { flex-direction: column; align-items: center; gap: 0.2rem; margin-top: 0.9rem; }
  #schedule .btn {
    display: inline;
    min-height: 0;
    padding: 0;
    border: 0;
    background: none;
    color: var(--ink);
    font-family: theme('fontFamily.body');
    font-size: 1.05rem;
    letter-spacing: 0;
    text-transform: none;
    white-space: normal;
  }
  #schedule .btn .icon { display: none; }
  #schedule a.btn[href*="calendly.com"]::after {
    content: "\2014  calendly.com/coastalhealthcareadvocates/30min";
  }
  /* The inline scheduler doesn't print: the fallback link and its address do. */
  .calendly-frame,
  .calendly-loading,
  .calendly-embed.is-live .calendly-alt { display: none; }
  .calendly-embed.is-live .calendly-fallback { display: block; }

  /* --- Footer: tagline, one contact line, badge + disclaimers. Kept short so
     break-inside: avoid never strands it alone on a page. --- */
  .site-footer {
    background: none !important;
    border-top: 1px solid var(--line);
    margin-top: 1.25rem;
    break-inside: avoid;
  }
  .site-footer,
  .site-footer * { color: var(--ink) !important; }
  .site-footer .meta,
  .site-footer .meta *,
  .site-footer address span { color: var(--ink-soft) !important; }
  .site-footer .canvas-wide { padding-block: 0.8rem 0; }
  /* Tagline and contact line stack on the left; the QR code sits to their right. */
  .site-footer .canvas-wide > div:first-child {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-content: center;
    gap: 0.35rem 1.5rem;
  }
  .site-footer .canvas-wide > div:first-child > :not(.footer-qr) { grid-column: 1; }
  .site-footer .canvas-wide > div:first-child .meta { margin-top: 0; }
  .site-footer .footer-qr {
    display: flex;
    flex-direction: column;
    align-items: center;
    /* The SVG has no quiet zone of its own; keep the caption 4+ modules clear. */
    gap: 0.12in;
    grid-column: 2;
    grid-row: 1 / span 2;
    margin: 0;
    break-inside: avoid;
  }
  .site-footer .footer-qr img { width: 0.8in; height: 0.8in; }
  .site-footer .footer-qr figcaption { font-size: 0.75rem; }
  .site-footer .footer-col-title { display: none; }
  .site-footer address { flex-direction: row; flex-wrap: wrap; gap: 0.1rem 1.5rem; }
  .site-footer address::after { content: "coastalhealthcareadvocates.org"; }
  .site-footer .canvas-wide > div:last-child {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    margin-top: 0.7rem;
    padding-top: 0.7rem;
    border-color: var(--line-soft);
  }
  .site-footer .canvas-wide > div:last-child .meta { gap: 0; font-size: 0.85rem; text-align: right; }
  /* The white badge would vanish on paper; swap in the full-color lockup. */
  .site-footer .gna-badge img {
    content: url('../img/gna-logo-w-tagline.svg');
    height: 2.1rem;
    width: auto;
  }

  /* --- Legal pages --- */
  .legal-prose { max-width: none; }
  .legal-prose h2 { margin-top: 1.6rem; }
  .legal-prose li::marker { color: var(--ink-soft); }
  .legal-toc { padding: 0.8rem 1.1rem; break-inside: avoid; }
  .legal-toc ol { columns: 2; column-gap: 2rem; }
  .legal-toc li { break-inside: avoid; margin-top: 0; padding-block: 0.15rem; }
  .legal-prose table { min-width: 0; }
  .legal-prose thead { display: table-header-group; }
  .legal-prose thead th { background: none; border-bottom: 1px solid var(--line); }
  .todo-flag { break-inside: avoid; }
  .legal-prose .checklist li::before { color: var(--ink); }
  /* Blanks meant for a pen: dark enough to see, long enough to write on. */
  .legal-prose .fill { min-width: 11rem; border-bottom-color: var(--ink-soft); }
  .legal-prose .sig-grid { grid-template-columns: 1fr 1fr; }
  .legal-prose .sig-card { break-inside: avoid; }
  .legal-prose .sig-card p { margin-top: 1.1rem; }
  .legal-prose .sig-card .fill { min-width: 60%; }

  /* The style guide's own print rules sit with the rest of its furniture, in
     the components layer, so they stay out of site.css. */
}

An internal page: generated by scripts/build-docs.js, not in the site’s navigation, and marked noindex. Back to the style guide.