/* Alata — the church's own heading face. Ships in one weight (400) only. */
@font-face {
	font-family: 'Alata';
	src: url('/fonts/alata-latin-400.woff2') format('woff2');
	font-weight: 400;
	font-style: normal;
	font-display: swap;
}

/* Alegreya — the church's body and subheading face. Variable, roman + italic. */
@font-face {
	font-family: 'Alegreya';
	src: url('/fonts/alegreya-latin-variable.woff2') format('woff2');
	font-weight: 400 900;
	font-style: normal;
	font-display: swap;
}
@font-face {
	font-family: 'Alegreya';
	src: url('/fonts/alegreya-latin-variable-italic.woff2') format('woff2');
	font-weight: 400 900;
	font-style: italic;
	font-display: swap;
}

/* Footer social glyphs are inline SVG (simple-icons, CC0): three paths
   instead of a 117 KB icon font that was preloaded on every page. */
.social-icon { width: 1.35rem; height: 1.35rem; fill: currentColor; display: block; }

:root {
	color-scheme: light dark;

	/* Lagoon + gold, the client's call (2026-09-26): deep lagoon teal leads,
	   gold is the accent, and red lives only in the logo. Moving off
	   burgundy + cream keeps ARBC distinct from GPFBC.

	   The brand/ink split is gpfbc's, confirmed there in its own dark-mode
	   sweep (gpfbc 2a110f9 + 222c8a4) and adopted here so the two sibling
	   sites answer the same question the same way. The rule: a brand colour
	   token keeps the brand value for FILLS, BORDERS and RULES, where the
	   3:1 non-text bar applies and the value does not flip; a matching -ink
	   token carries that colour as TYPE, where the 4.5:1 bar applies, and it
	   is the -ink token that flips on the dark floor. Mixing the two jobs in
	   one token is what broke every solid button here. */
	--c-primary:      #14343C;
	--c-primary-dark: #0A2026;
	--c-primary-soft: #DCE9E7;
	/* Lagoon as TYPE. Eyebrows, quote text, hovers and the focus ring. */
	--c-primary-ink:  var(--c-primary);
	--c-accent:       #C9A04A;
	--c-accent-soft:  #EFDFB4;
	/* The hero and the .gp-section--dark panels are dark in BOTH themes, so
	   type on them must not follow the theme at all. This is the fixed pale
	   gold for exactly that. */
	--c-accent-on-dark: #E9CF8A;
	/* Gold as TYPE on a light surface. --c-accent is the brand gold and stays
	   it for rules, borders and dots; as small type on mist it is far under
	   4.5:1. Same hue, deepened until it clears: every card kicker, every
	   scripture citation, the FAQ glyph. */
	--c-accent-ink:   #7A5A14;
	/* Form errors only. Not a brand colour — the brand red is the logo's. */
	--c-error:        #B3261E;
	--c-error-soft:   #F9DEDC;
	--c-mist:         #EEF3F1;
	--c-white:        #FCFDFC;
	--c-divider:      #D3DEDC;
	--c-ink:          #132226;
	--c-muted:        #4A5A5D;
	--c-silver:       #9DB0AE;
	--c-lagoon:       #14343C;

	--f-display: 'Alata', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
	--f-text:    'Alegreya', Georgia, 'Times New Roman', serif;

	--section-pad:           clamp(64px, 4rem + 4vw, 120px);
	--section-pad-collapsed: clamp(32px, 1.5rem + 2vw, 56px);
	--radius-card:           14px;
	--radius-button:         8px;
	--t-ui:                  160ms;
	--t-card:                220ms;
	--t-reveal:              420ms;
	--ease:                  cubic-bezier(0.22, 1, 0.36, 1);

	/* Surface roles — flipped by the dark floor further down the stylesheet. */
	--surface:      var(--c-white);
	--surface-alt:  var(--c-mist);
	--text-body:    var(--c-muted);
	--text-heading: var(--c-ink);

	/* Slate as TYPE. --c-silver is a border and a footer-legal colour, far
	   under 4.5:1 as a card's meta line. This is the quieter-than-body role
	   those want, clearing AA on both mist and white. */
	--text-meta:    #5E6F6C;

	/* Font sizes from theme.json settings.typography.fontSizes. */
	--f-size-tiny:     clamp(12px, 0.75rem + 0.05vw, 13px);
	--f-size-x-small:  clamp(13px, 0.8125rem + 0.05vw, 14px);
	--f-size-small:    clamp(15px, 0.9375rem + 0.1vw, 16px);
	--f-size-medium:   16px;
	--f-size-large:    clamp(18px, 1.125rem + 0.2vw, 20px);
	--f-size-x-large:  clamp(21px, 1.3125rem + 0.4vw, 28px);
	--f-size-xx-large: clamp(26px, 1.625rem + 1vw, 42px);
	--f-size-huge:     clamp(34px, 2rem + 1.8vw, 60px);
	/* Solved for 40px at 390px and 96px at 1440px, rather than nudged.
	   The old 44px floor never actually bound on a phone: at 390px the
	   PREFERRED value computed to ~55px, so the clamp minimum was dead and
	   the 74-character tagline ran to five lines filling the first screen. */
	--f-size-display:  clamp(40px, 1.2rem + 5.33vw, 96px);

	/* JS sets this from data-reveal-delay; default is no stagger. */
	--reveal-delay: 0ms;
}

/* The theme switch.

   Three states, and only two of them are an attribute: "System" is the
   ABSENCE of data-theme, so a visitor who never touches the control — and a
   visitor with JS off — gets the same prefers-color-scheme behaviour the site
   had before the control existed. A stored choice is applied by an inline
   script in <head>, before first paint, or the page would flash the system
   theme on every navigation.

   color-scheme has to move with it, or a forced theme leaves the browser's
   own furniture (form controls, scrollbars, the canvas behind the page) still
   dressed for the system one. */
:root[data-theme="light"] { color-scheme: light; }

/* Dark floor: legible, non-broken; a full bespoke dark theme can come later.

   Each dark block below is written TWICE: once for "follow the system, unless
   the visitor chose light", once for "the visitor chose dark". CSS cannot add
   a selector to a rule from inside a media query, so there is no way to share
   one declaration list between the two. The pair is kept honest by a test
   (`the_two_dark_floors_say_the_same_thing`) that fails if they drift.

   The :root pair carries real specificity, because it has to outrank the
   plain :root above. The element-level pairs further down are wrapped in
   :where() so they contribute nothing — those rules already sit in a
   cascade that was tuned without them, and .gp-section--dark a would lose to
   an unwrapped :root-scoped `a`. */
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) {
		color-scheme: dark;
		--surface: #0F1C1F;
		--surface-alt: #16272B;
		--text-body: #C3CFCD;
		--text-heading: #EEF3F1;
		--c-divider: #2A3C40;
		--c-primary-soft: #1B3338;
		/* Same hue as the brand lagoon, lightened until it clears AA rather
		   than swapped for a colour off the palette. --c-primary itself does
		   NOT flip — it is still the fill under every solid button. */
		--c-primary-ink: #8CC4C9;
		/* On the near-black lagoon floor the brand gold and the slate already
		   are the right colours. Only light mode has to deepen. */
		--c-accent-ink: var(--c-accent);
		--text-meta:    var(--c-silver);
	}
}
:root[data-theme="dark"] {
	color-scheme: dark;
	--surface: #0F1C1F;
	--surface-alt: #16272B;
	--text-body: #C3CFCD;
	--text-heading: #EEF3F1;
	--c-divider: #2A3C40;
	--c-primary-soft: #1B3338;
	--c-primary-ink: #8CC4C9;
	--c-accent-ink: var(--c-accent);
	--text-meta:    var(--c-silver);
}

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

html { scroll-behavior: smooth; scroll-padding-top: 96px; }

body {
	margin: 0;
	background: var(--surface);
	color: var(--text-body);
	font-family: var(--f-text);
	/* Alegreya runs small on its x-height; 16px set it like footnotes. */
	font-size: clamp(17px, 1.0625rem + 0.1vw, 18px);
	line-height: 1.75;
	/* Alata has no bold. A synthesised one smears, so labels asking for 600
	   get the real 400 instead. */
	font-synthesis-weight: none;
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, h5, h6 {
	font-family: var(--f-display);
	color: var(--text-heading);
	margin: 0 0 0.5em;
	text-wrap: balance;
}

/* theme.json styles.elements — WordPress generated these; they never lived in main.css. */
h1 { font-size: var(--f-size-display); font-weight: 420; line-height: 1.08; letter-spacing: -0.015em; }
h2 { font-size: var(--f-size-huge);    font-weight: 420; line-height: 1.12; letter-spacing: -0.01em; }
h3 { font-size: var(--f-size-xx-large); font-weight: 450; line-height: 1.2; }
h4 { font-size: var(--f-size-x-large);  font-weight: 500; line-height: 1.3; }

p { margin: 0 0 1.25em; }

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

a { color: var(--c-primary-ink); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--c-primary-dark); }

/* NOT --c-accent: the brand sand is a rules-and-borders value. And NOT
   --c-accent-soft for the hover — gpfbc's sweep found that one vanishing
   under the cursor. Body links are the one place that must not be
   borderline. */
@media (prefers-color-scheme: dark) {
	:where(:root:not([data-theme="light"])) a { color: var(--c-accent-ink); }
	:where(:root:not([data-theme="light"])) a:hover { color: var(--c-accent-on-dark); }
}
:where(:root[data-theme="dark"]) a { color: var(--c-accent-ink); }
:where(:root[data-theme="dark"]) a:hover { color: var(--c-accent-on-dark); }

/* Focus: :focus-visible only. */
:focus { outline: none; }
:focus-visible {
	outline: 2px solid var(--c-primary-ink);
	outline-offset: 3px;
	border-radius: 2px;
}
/* "Visible on any surface" was not true, and the theme control is what made it
   load-bearing. --c-primary-ink clears 3:1 on both PAGE floors, but the lagoon
   footer and the maroon panels are dark in BOTH themes, so in light mode the
   ring was the brand colour on them: 1.43:1 and 1.62:1 — keyboard focus effectively
   gone. That is every link, field and button on those surfaces, and below
   900px the footer holds the ONLY theme control. The hero is the same case:
   its scrim is the lagoon itself, so a lagoon ring on it measured 1:1. Same
   fixed pale gold the type on those surfaces already uses, both themes. */
.site-footer :focus-visible,
.gp-section--dark :focus-visible,
.hero :focus-visible { outline-color: var(--c-accent-on-dark); }
/* Over the hero the bar is transparent on a photograph, where the white the
   brand and the nav already wear is the thing that reads. */
.site-header:not(.is-opaque):not(.is-scrolled) :focus-visible { outline-color: var(--c-white); }

.skip-link {
	position: absolute;
	left: -9999px;
	top: 0;
	z-index: 200;
	padding: 0.75rem 1.25rem;
	background: var(--c-ink);
	color: var(--c-white);
}
.skip-link:focus-visible { left: 0; }

.screen-reader-text {
	clip-path: inset(50%);
	height: 1px; width: 1px;
	overflow: hidden;
	position: absolute;
	white-space: nowrap;
}

/* ---------------------------------------------------------------- layout */

.container { width: min(100% - 3rem, 1280px); margin-inline: auto; }
.container--narrow { width: min(100% - 3rem, 992px); margin-inline: auto; }
.container--prose { width: min(100% - 3rem, 720px); margin-inline: auto; }

/* Narrow phones get a 16px gutter instead of 24px. This is not cosmetic:
   Cloudflare's Turnstile widget is a hard 300px wide with no smaller option
   short of its 150px "compact" variant, so at a 320px viewport the default
   24px gutter left a 272px container and pushed the widget 4px past the
   screen — a real sideways scroll on an iPhone SE. 16px gutters land its
   right edge at 316px. 360px and up already clear 300px and are untouched. */
@media (max-width: 360px) {
	.container,
	.container--narrow,
	.container--prose { width: min(100% - 2rem, 1280px); }
}

/* --------------------------------------------------------- section engine */

.gp-section { padding-block: var(--section-pad); background: var(--surface); }
.gp-section--cream { background: var(--surface-alt); }
.gp-section--dark {
	background: var(--c-primary-dark);
	--text-body: rgba(255, 255, 255, 0.82);
	--text-heading: var(--c-white);
	color: var(--text-body);
}
.gp-section--dark a { color: var(--c-accent-on-dark); }

/* Same-background neighbours read as one visual unit. */
.gp-section--cream + .gp-section--cream,
.gp-section--dark + .gp-section--dark { padding-top: 0; }
.gp-section--cream:has(+ .gp-section--cream),
.gp-section--dark:has(+ .gp-section--dark) { padding-bottom: var(--section-pad-collapsed); }

.gp-section__head { max-width: 46rem; margin-bottom: clamp(2rem, 1.5rem + 2vw, 3.5rem); }
.gp-section__head--center { margin-inline: auto; text-align: center; }

.eyebrow {
	display: block;
	font-family: var(--f-text);
	font-size: var(--f-size-small);
	font-weight: 500;
	font-style: italic;
	letter-spacing: 0.02em;
	color: var(--c-primary-ink);
	margin-bottom: 0.9rem;
}
.gp-section--dark .eyebrow { color: var(--c-accent-on-dark); }

/* A-mark chevron: one triangle, from the church logo. */
.gp-rule {
	border: 0;
	height: 10px;
	margin: 0 0 1.2rem;
	width: 56px;
	clip-path: polygon(50% 0, 100% 100%, 0 100%);
	background: var(--c-primary-ink);
}
/* The panel is maroon in both themes, so neither half of the split works on
   it: the brand maroon is 1.6:1 there in light, and the flipped ink is what
   rescues it only in dark. Same fixed value the eyebrow above it already
   uses — 10.6:1. */
.gp-section--dark .gp-rule { background: var(--c-accent-on-dark); }
.gp-section__head--center .gp-rule { margin-inline: auto; }

/* ---------------------------------------------------------------- buttons */

.btn {
	display: inline-block;
	font-family: var(--f-display);
	font-size: 14px;
	font-weight: 600;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	text-decoration: none;
	line-height: 1;
	padding: 0.9rem 1.875rem;
	border-radius: var(--radius-button);
	border: 1px solid var(--c-primary);
	background: var(--c-primary);
	color: var(--c-white);
	cursor: pointer;
	transition: background-color var(--t-ui) var(--ease), border-color var(--t-ui) var(--ease),
		color var(--t-ui) var(--ease), transform var(--t-ui) var(--ease);
}
.btn:hover { background: var(--c-primary-dark); border-color: var(--c-primary-dark); color: var(--c-white); transform: translateY(-1px); }

/* A solid pill on DARK ground takes the gold. The lagoon fill is near the
   lagoon panels, the lagoon scrim and the dark floor itself — 1.03:1 to
   1.32:1 — so the button's shape disappeared even though its label read.
   Gold with deep-lagoon type reads on all three. The two ghost variants draw
   their colour from the panel and are excluded. `.gp-section--dark a` (0,1,1)
   would otherwise repaint the label, which the (0,4,0) selector outranks. */
.gp-section--dark .btn:not(.btn--ghost):not(.btn--ghost-light),
.hero__actions .btn:not(.btn--ghost):not(.btn--ghost-light) {
	background: var(--c-accent);
	border-color: var(--c-accent);
	color: var(--c-primary-dark);
}
.gp-section--dark .btn:not(.btn--ghost):not(.btn--ghost-light):hover,
.hero__actions .btn:not(.btn--ghost):not(.btn--ghost-light):hover {
	background: var(--c-accent-on-dark);
	border-color: var(--c-accent-on-dark);
	color: var(--c-primary-dark);
}
@media (prefers-color-scheme: dark) {
	:where(:root:not([data-theme="light"])) .btn:not(.btn--ghost):not(.btn--ghost-light) { background: var(--c-accent); border-color: var(--c-accent); color: var(--c-primary-dark); }
	:where(:root:not([data-theme="light"])) .btn:not(.btn--ghost):not(.btn--ghost-light):hover { background: var(--c-accent-on-dark); border-color: var(--c-accent-on-dark); color: var(--c-primary-dark); }
}
:where(:root[data-theme="dark"]) .btn:not(.btn--ghost):not(.btn--ghost-light) { background: var(--c-accent); border-color: var(--c-accent); color: var(--c-primary-dark); }
:where(:root[data-theme="dark"]) .btn:not(.btn--ghost):not(.btn--ghost-light):hover { background: var(--c-accent-on-dark); border-color: var(--c-accent-on-dark); color: var(--c-primary-dark); }

.btn--ghost { background: transparent; color: var(--c-primary-ink); }
.btn--ghost:hover { background: var(--c-primary); color: var(--c-white); }
/* A ghost button IS its outline, and that outline comes from `.btn`'s
   `border: 1px solid var(--c-primary)` — the fill token, which does not flip.
   On the dark floor that is the lagoon on a lagoon-black: far under the 3:1
   non-text bar. The LABEL was never the problem here (--c-primary-ink clears
   AA there); the box around it was, which is the harder kind to notice.
   gpfbc's answer, ported: the dark theme's own light foreground with the
   border following it, so the outline reads and does not float unattached. */
@media (prefers-color-scheme: dark) {
	:where(:root:not([data-theme="light"])) .btn--ghost { color: var(--text-heading); border-color: currentColor; }
	:where(:root:not([data-theme="light"])) .btn--ghost:hover { background: var(--text-heading); color: var(--surface); border-color: var(--text-heading); }
}
:where(:root[data-theme="dark"]) .btn--ghost { color: var(--text-heading); border-color: currentColor; }
:where(:root[data-theme="dark"]) .btn--ghost:hover { background: var(--text-heading); color: var(--surface); border-color: var(--text-heading); }

/* A paragraph whose content is a call-to-action button.

   `p` carries no top margin (see the `p` rule above), and `.btn` is an
   inline-block with line-height:1 and 0.9rem of vertical padding — roughly
   45px tall inside a ~29px line box. An inline-block taller than its line
   sits on the baseline and overflows UPWARD, so these buttons did not merely
   sit flush against the schedule table and the ministries card grid, they
   visually overlapped them. Margin, not padding, is the fix: padding would
   grow the button itself.

   2rem matches `.hero__actions` rather than inventing a second number. */
.gp-section p:has(> .btn) { margin-top: 2rem; }
/* Two or more buttons in one paragraph wrap on a phone; inline-blocks with a
   space between them stacked flush, border on border. A flex row with a gap
   keeps them apart in both directions. */
.gp-section p:has(> .btn + .btn) { display: flex; flex-wrap: wrap; gap: 0.75rem; }
/* A centred call-to-action section centres its copy and button too, not just
   the heading above them. */
.gp-section--cta { text-align: center; }

.btn--ghost-light { background: transparent; border-color: rgba(255, 255, 255, 0.7); color: var(--c-white); }
.btn--ghost-light:hover { background: var(--c-white); border-color: var(--c-white); color: var(--c-primary-dark); transform: translateY(-1px); }

/* ------------------------------------------------------------------- nav */

.site-header {
	position: fixed;
	inset-inline: 0;
	top: 0;
	z-index: 100;
	background: transparent;
	transition: background-color var(--t-card) var(--ease), box-shadow var(--t-card) var(--ease);
}
.site-header.is-opaque,
.site-header.is-scrolled {
	background: color-mix(in oklab, var(--surface) 92%, transparent);
	backdrop-filter: blur(14px) saturate(160%);
	box-shadow: 0 1px 0 var(--c-divider);
}

/* THE MOBILE DRAWER DEPENDS ON THIS RULE.
   backdrop-filter makes an element the containing block for its
   position:fixed descendants. The drawer (.nav-toggle ~ .site-nav) is fixed
   and lives inside the header, so while the blur was active it stopped being
   viewport-relative and collapsed into the 80px header — 48px tall, with the
   page showing through where the links should be.
   It was broken on every interior page from load (header is .is-opaque there)
   and on the home page the moment you scrolled (.is-scrolled).
   The blur is pointless while a full-screen drawer covers everything, so it
   is dropped for exactly as long as the drawer is open. Do not remove this
   without moving the drawer out of the header. */
.site-header:has(.site-nav.is-open) {
	backdrop-filter: none;
	-webkit-backdrop-filter: none;
	/* The bar is normally 92% opaque and leans on the blur to stay legible.
	   With the blur gone, page content read through it behind the brand, so
	   it goes solid for as long as the drawer is open. */
	background: var(--surface);
}

.site-header__inner {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1.5rem;
	min-height: 96px;
}

.site-header__brand {
	display: flex;
	align-items: center;
	gap: 0.9rem;
	font-family: var(--f-display);
	font-size: 1.05rem;
	font-weight: 400;
	letter-spacing: 0.01em;
	line-height: 1.2;
	color: var(--text-heading);
	text-decoration: none;
	max-width: 22ch;
	min-width: 0;
}
.site-header__brand img { width: 72px; height: 72px; flex-shrink: 0; }

/* A scrim for the bar itself, not for the hero.

   `.hero__scrim` is tuned for the headline band at the BOTTOM of the photo and
   deliberately stays light at the top so the roof and the sky still read as a
   photograph. That leaves the bar sitting on pale sunset sky: sampling the
   composited pixels behind the nav links gave 2.83:1 at the brightest, 2.93
   median, with no part of the strip reaching 4.5:1. The halo below is a 14px
   blur — it helps the eye and counts for nothing.

   So the darkening is scoped to the ~180px the bar occupies and fades out
   above the headline, which keeps the tuned ramp underneath untouched. It
   only exists while the bar is transparent: once `.is-opaque` or
   `.is-scrolled` lands, the bar has its own background and this would be a
   dark smear across the top of the page. The drawer is excluded for the same
   reason — an open drawer makes the bar solid, and a negative-z-index
   ::before paints ON TOP of its own element's background, not behind it.

   It FADES rather than switching off. The bar's own background animates in
   over --t-card; a scrim that vanished on the frame the class landed left
   ~220ms of cream nav on the pale sky, which is the exact thing it is here to
   prevent. Scoped to --over-hero so it exists only on the two pages that have
   a hero behind the bar. */
.site-header--over-hero::before {
	content: "";
	position: absolute;
	inset: 0 0 auto 0;
	height: 180px;
	/* Same warm near-black as the halo, so this reads as more of the same
	   effect rather than a second colour. */
	background: linear-gradient(
		to bottom,
		rgba(6, 16, 19, 0.62) 0%,
		rgba(6, 16, 19, 0.50) 53%,
		rgba(6, 16, 19, 0) 100%
	);
	/* The bar is 96px; this is 180px. Without it the overhang is an invisible
	   full-width click blocker over the top of the hero. */
	pointer-events: none;
	z-index: -1;
	transition: opacity var(--t-card) var(--ease);
}
.site-header--over-hero.is-opaque::before,
.site-header--over-hero.is-scrolled::before,
.site-header--over-hero:has(.site-nav.is-open)::before { opacity: 0; }

/* Over the hero the type goes light; the mark stays full colour so the A and
   the cross still read. Knocking it to a white silhouette (as this did before
   the mark gained an alpha channel) erases the cross entirely. */
.site-header:not(.is-opaque):not(.is-scrolled) .site-header__brand,
.site-header:not(.is-opaque):not(.is-scrolled) .site-nav > ul > li > a {
	color: var(--c-white);
	text-shadow: 0 1px 14px rgba(6, 16, 19, 0.45);
}
.site-header:not(.is-opaque):not(.is-scrolled) .site-header__brand img {
	filter: drop-shadow(0 2px 10px rgba(6, 16, 19, 0.55));
}

/* An open drawer makes the bar solid (see the :has rule above), so the light
   type it was wearing over the photograph has to come back down. The drawer
   LINKS already had this; the brand and the hamburger did not, which in light
   mode left cream-on-cream — an invisible church name and an invisible close
   button on the home and visit pages. Dark mode hid it, because there the
   solid bar is near-black and white-on-near-black reads fine.

   This ties the over-hero rule above at (0,4,0), so it wins only by sitting
   after it. The hamburger's own pair lives further down, next to the rest of
   the .nav-toggle rules, for exactly the same reason. */
.site-header:has(.site-nav.is-open) .site-header__brand {
	color: var(--text-heading);
	text-shadow: none;
}

/* On the dark opaque bar the maroon is too close to the surface (1.89:1), so
   there the mark becomes a light silhouette. Ordered after the over-hero rule
   above so the hero keeps full colour in dark mode too.
   An OPEN DRAWER is neither .is-opaque nor .is-scrolled — it makes the bar
   solid by its own rule — so it has to be named here as well, or the first
   open of the menu on the two hero pages in dark left the mark at that 1.89:1. */
@media (prefers-color-scheme: dark) {
	:where(:root:not([data-theme="light"])) .site-header.is-opaque .site-header__brand img,
	:where(:root:not([data-theme="light"])) .site-header.is-scrolled .site-header__brand img,
	:where(:root:not([data-theme="light"])) .site-header:has(.site-nav.is-open) .site-header__brand img { filter: brightness(0) invert(1); }
}
:where(:root[data-theme="dark"]) .site-header.is-opaque .site-header__brand img,
:where(:root[data-theme="dark"]) .site-header.is-scrolled .site-header__brand img,
:where(:root[data-theme="dark"]) .site-header:has(.site-nav.is-open) .site-header__brand img { filter: brightness(0) invert(1); }

.site-nav > ul {
	display: flex;
	align-items: center;
	gap: clamp(0.75rem, 0.2rem + 0.8vw, 1.4rem);
	list-style: none;
	margin: 0;
	padding: 0;
}
.site-nav a {
	/* The site's own small-text token (15->16px fluid) rather than a bespoke
	   rem value. 0.85rem (13.6px) read undersized against a 96px bar and a
	   72px mark. The drawer links at 1.05rem are already larger and unchanged. */
	font-family: var(--f-display);
	font-size: var(--f-size-small);
	font-weight: 600;
	letter-spacing: 0.01em;
	text-decoration: none;
	white-space: nowrap;
	color: var(--text-heading);
}
.site-nav a:hover { color: var(--c-primary-ink); }
.site-header:not(.is-opaque):not(.is-scrolled) .site-nav > ul > li > a:hover { color: var(--c-accent-on-dark); }

/* Dropdowns with an invisible hover bridge. */
.site-nav li { position: relative; }
.site-nav .sub-menu {
	position: absolute;
	top: calc(100% + 14px);
	left: -1rem;
	min-width: 230px;
	list-style: none;
	margin: 0;
	padding: 0.6rem 0;
	background: var(--surface);
	border: 1px solid var(--c-divider);
	border-radius: 10px;
	box-shadow: 0 18px 44px rgba(0, 0, 0, 0.12);
	opacity: 0;
	visibility: hidden;
	transform: translateY(6px);
	transition: opacity var(--t-ui) var(--ease), transform var(--t-ui) var(--ease), visibility var(--t-ui);
}
.site-nav li:has(.sub-menu)::after {
	content: "";
	position: absolute;
	inset-inline: -1rem;
	top: 100%;
	height: 18px;
}
.site-nav li:hover > .sub-menu,
.site-nav li:focus-within > .sub-menu { opacity: 1; visibility: visible; transform: translateY(0); }
.site-nav .sub-menu a { display: block; padding: 0.5rem 1.25rem; color: var(--text-heading); }
.site-nav .sub-menu a:hover { color: var(--c-primary-ink); background: var(--surface-alt); }

.nav-toggle {
	display: none;
	background: none;
	border: 0;
	/* 44x44 minimum. The bars are only ~16px tall, so without this the whole
	   tap target was 18px — the primary navigation control on every phone
	   page, and the hardest thing on the site to hit. */
	min-width: 44px;
	min-height: 44px;
	padding: 0.5rem;
	display: none;
	align-items: center;
	justify-content: center;
	cursor: pointer;
}
.nav-toggle .bars,
.nav-toggle .bars::before,
.nav-toggle .bars::after {
	content: "";
	display: block;
	width: 24px;
	height: 2px;
	background: currentColor;
	transition: transform var(--t-ui) var(--ease), opacity var(--t-ui) var(--ease);
	position: relative;
}
.nav-toggle .bars::before { position: absolute; top: -7px; }
.nav-toggle .bars::after { position: absolute; top: 7px; }
.nav-toggle[aria-expanded="true"] .bars { transform: rotate(45deg); }
/* `top: 0` already stacks this bar onto the base one, so the rotation alone
   makes the cross. The old rule also carried translateX(-7px), which applies
   in the ROTATED frame — it slid this arm 7px along its own axis and left a
   visibly lopsided X. Rotating +90 (not -90) sweeps both arms the same way
   into the cross rather than counter-rotating. */
.nav-toggle[aria-expanded="true"] .bars::before { transform: rotate(90deg); top: 0; }
.nav-toggle[aria-expanded="true"] .bars::after { opacity: 0; }
.site-header:not(.is-opaque):not(.is-scrolled) .nav-toggle { color: var(--c-white); }
.nav-toggle { color: var(--text-heading); }
/* The close X, once the drawer has made the bar solid. Ties the over-hero
   rule directly above at (0,4,0) and has to follow it — the same fix as the
   brand, and it has to live HERE rather than beside it, because that rule is
   further down the sheet than the brand's was. */
.site-header:has(.site-nav.is-open) .nav-toggle { color: var(--text-heading); }

@media (max-width: 900px) {
	/* The bar already carries a three-line church name and a 44px hamburger at
	   390px. The footer copy is the one phones get. */
	.site-header .theme-toggle { display: none; }
	.site-nav > ul { flex-wrap: wrap; justify-content: flex-end; gap: 0.5rem 1rem; }
	html.no-js .nav-toggle { display: none; }
	.nav-toggle { display: inline-flex; }
	.site-header__brand img { width: 56px; height: 56px; }
	/* No hard max-width here. 16ch (~161px) forced "Apologia Reformed Baptist
	   Church" onto FOUR lines that exactly filled the 80px bar, even with
	   320px of space free. Flex already bounds this between the mark and the
	   hamburger; min-width:0 lets it shrink on the narrowest phones. */
	.site-header__brand { max-width: none; min-width: 0; }
	.site-header__inner { min-height: 80px; }
	/* Overlay only when a hamburger precedes the nav (header.php order). */
	.nav-toggle ~ .site-nav {
		position: fixed;
		inset: 0;
		/* Matches .site-header__inner's 80px mobile min-height; at 76px the
		   drawer clipped the last 4px of the bar it hangs from. */
		top: 80px;
		background: var(--surface);
		padding: 1.5rem;
		overflow-y: auto;
		display: none;
	}
	.nav-toggle ~ .site-nav.is-open { display: block; }
	.nav-toggle ~ .site-nav.is-open ~ * { display: none; }
	.nav-toggle ~ .site-nav > ul { flex-direction: column; align-items: stretch; gap: 0; }
	.nav-toggle ~ .site-nav > ul > li > a {
		display: block;
		padding: 0.9rem 0;
		border-bottom: 1px solid var(--c-divider);
		font-size: 1.05rem;
		color: var(--text-heading);
	}
	.nav-toggle ~ .site-nav .sub-menu {
		position: static;
		opacity: 1;
		visibility: visible;
		transform: none;
		border: 0;
		box-shadow: none;
		background: transparent;
		padding-left: 1rem;
	}
	.site-header:not(.is-opaque):not(.is-scrolled) .nav-toggle ~ .site-nav a { color: var(--text-heading); }
	/* ...and the hover with it. The over-hero hover rule is (0,5,3) — it
	   outranks the rescue above at (0,5,1), so a drawer link went pale sand
	   on the drawer's own cream panel the moment the cursor touched it:
	   1.37:1, on home and visit, in light mode only. The desktop nav hover
	   colour is the right one here, and it is gated on both surfaces. */
	.site-header:not(.is-opaque):not(.is-scrolled) .nav-toggle ~ .site-nav > ul > li > a:hover { color: var(--c-primary-ink); }
	html.no-js .nav-toggle ~ .site-nav {
		position: static;
		inset: auto;
		display: block;
		background: transparent;
		padding: 0;
		overflow: visible;
	}
	html.no-js .nav-toggle ~ .site-nav > ul {
		display: flex;
		flex-direction: row;
		flex-wrap: wrap;
		justify-content: flex-end;
		align-items: center;
		gap: 0.5rem 1rem;
	}
	html.no-js .nav-toggle ~ .site-nav > ul > li > a {
		display: inline;
		padding: 0;
		border-bottom: 0;
		font-size: inherit;
	}
	html.no-js .site-header:not(.is-opaque):not(.is-scrolled) .nav-toggle ~ .site-nav a {
		color: var(--c-white);
	}
}

/* ------------------------------------------------------------------ hero */

.hero {
	position: relative;
	min-height: min(92vh, 900px);
	display: flex;
	align-items: flex-end;
	background: var(--c-primary-dark);
	color: var(--c-white);
	overflow: hidden;
	padding-block: clamp(7rem, 10vh, 10rem) clamp(3.5rem, 9vh, 7rem);
}
.hero--short { min-height: 52vh; padding-bottom: clamp(2.5rem, 6vh, 4.5rem); }

.hero__media,
.hero__media video,
.hero__media img,
.hero__fallback {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* Sourceless video shows the fallback layer. The inner pages have none; the
   home page has the service footage (2026-09-26), which fades in over its own
   first frame (the poster) once it is actually playing, so there is no flash
   of the fallback photo between the two. */
.hero__video:not([src]):not(:has(source)) { display: none; }
.hero__video { opacity: 0; transition: opacity 0.8s var(--ease); }
.hero__video.is-ready { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
	.hero__video { display: none; }
}
/* The congregation photo, 1600px wide: AVIF, then WebP, then JPEG for a
   browser that knows neither. The plain url() line is the fallback for a
   browser without image-set(); the second declaration replaces it where
   image-set() is understood. */
.hero__fallback {
	background: url('/images/hero.jpg') center 42% / cover no-repeat, var(--c-lagoon);
	background-image: image-set(
		url('/images/hero.avif') type('image/avif'),
		url('/images/hero.webp') type('image/webp'),
		url('/images/hero.jpg') type('image/jpeg')
	);
}
.hero__fallback::after {
	content: none;
}

/* The content sits at the BOTTOM of the hero, so the scrim is weighted
   there. The old ramp reached 0.28 by 55% height, which is exactly where the
   taller headline sits — the type was competing with faces and bright sand.
   This holds the darker band higher before releasing to the warm cast at the
   top, so the photograph still reads as a photograph.

   Measured, not eyeballed: sampling the composited photo+scrim behind the
   headline band gives a worst case of 3.50:1 against the type colour, up from
   2.53:1 — which FAILED the 3.0:1 large-text AA floor. A heavier ramp reached
   4.13:1 but muddied the congregation, and on a church site the people in the
   photograph are the point. */
.hero__scrim {
	position: absolute;
	inset: 0;
	background: linear-gradient(
		to top,
		rgba(20, 52, 60, 0.90) 0%,
		rgba(20, 52, 60, 0.72) 34%,
		rgba(20, 52, 60, 0.46) 62%,
		rgba(10, 32, 38, 0.42) 100%
	);
}

.hero__content { position: relative; z-index: 1; }
/* A soft halo rather than a drop shadow: it lifts the type off whatever the
   photograph is doing underneath without reading as an effect. Same idiom the
   header brand already uses over the hero. */
.hero__content .eyebrow,
.hero__content h1,
.hero__content .hero__lede,
.hero__content .hero__meta { text-shadow: 0 2px 18px rgba(6, 16, 19, 0.55); }
.hero__content .eyebrow { color: var(--c-accent-on-dark); }
.hero__content h1 { color: var(--c-white); max-width: 16ch; }
/* The slogan is four clauses set as a two-by-two grid. The gold hairlines
   between the cells meet in a cross, which is the point of drawing them. The
   spaces between the spans stay in the markup so a screen reader or a
   copy-paste still has word boundaries. On a phone the grid folds to one
   column, and the rules become separators between the clauses. */
.hero__content .hero__slogan {
	display: grid;
	grid-template-columns: auto auto;
	width: max-content;
	max-width: 100%;
	font-size: clamp(28px, 0.8rem + 2.6vw, 54px);
	line-height: 1.1;
}
/* The first child is the church's name, for search engines and screen
   readers only (.screen-reader-text). It is out of the grid (position: absolute),
   so the four visible clauses are children 2-5: left column = even, top row =
   2 and 3. */
.hero__content .hero__slogan span:not(.screen-reader-text) { display: block; padding: 0.28em 0.7em; }
.hero__content .hero__slogan span:nth-child(even) { padding-left: 0; border-right: 1px solid var(--c-accent); }
.hero__content .hero__slogan span:nth-child(n+2):nth-child(-n+3) { border-bottom: 1px solid var(--c-accent); }
@media (max-width: 640px) {
	.hero__content .hero__slogan { grid-template-columns: auto; }
	.hero__content .hero__slogan span:nth-child(n) { padding-left: 0; border: 0; }
	.hero__content .hero__slogan span:not(:last-child):not(.screen-reader-text) { border-bottom: 1px solid var(--c-accent); }
}
.hero__lede { font-size: var(--f-size-large); max-width: 46ch; color: rgba(255, 255, 255, 0.88); }
.hero__meta {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem 1.5rem;
	font-size: var(--f-size-small);
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--c-accent-on-dark);
	margin-bottom: 1.75rem;
}
.hero__actions { display: flex; flex-wrap: wrap; gap: 0.9rem; margin-top: 2rem; }

/* ----------------------------------------------------------------- cards */

.card-grid { display: grid; gap: 1.5rem; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }

.card {
	background: var(--surface);
	border: 1px solid var(--c-divider);
	border-radius: var(--radius-card);
	overflow: hidden;
	display: flex;
	flex-direction: column;
	transition: transform var(--t-card) var(--ease), box-shadow var(--t-card) var(--ease);
}
.gp-section--cream .card,
.gp-section--dark .card { background: var(--surface); }
.card:hover { transform: translateY(-3px); box-shadow: 0 18px 40px rgba(10, 28, 32, 0.12); }

.card__media { aspect-ratio: 3 / 2; overflow: hidden; background: linear-gradient(150deg, var(--c-primary-soft), var(--c-accent-soft)); }

/* People (elders, deacons): every photo is now a portrait, and the married
   men's are family portraits (2026-09-26 round) — a wife and children
   standing in front. A 4:3 box took a thin band across the middle and cut
   the family out, so people cards are 4:5 and anchored high, where the
   heads sit. */
.card__media--person { aspect-ratio: 4 / 5; }
.card__media--person img { object-position: center 20%; }

/* <picture> only chooses the file; the img inside it does the sizing. */
.card__media picture { display: contents; }
.card__media img { width: 100%; height: 100%; object-fit: cover; transition: transform 480ms var(--ease); }
.card:hover .card__media img { transform: scale(1.03); }

.card__body { padding: 1.5rem 1.5rem 1.75rem; display: flex; flex-direction: column; gap: 0.35rem; flex: 1; }
.card__kicker { font-size: var(--f-size-tiny); font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase; color: var(--c-accent-ink); }
.card__title {
	font-size: clamp(19px, 1.05rem + 0.35vw, 26px);
	line-height: 1.25;
	margin: 0;
	overflow-wrap: break-word;
	text-wrap: pretty;
}
.card__title a { color: var(--text-heading); text-decoration: none; }
.card__title a:hover { color: var(--c-primary-ink); }
.card__meta { font-size: var(--f-size-small); color: var(--text-meta); }

/* -------------------------------------------------------- misc components */

.scripture {
	font-family: var(--f-text);
	font-style: italic;
	font-size: var(--f-size-large);
	color: var(--c-primary-ink);
	border-inline-start: 3px solid var(--c-accent);
	margin: 2rem 0;
	padding: 0.25rem 0 0.25rem 1.5rem;
}
.gp-section--dark .scripture { color: var(--c-accent-on-dark); }
/* The panel is maroon in BOTH themes, so the citation cannot follow the
   light-mode bronze down onto it. */
.gp-section--dark .scripture cite { color: var(--c-accent-on-dark); }
.scripture cite { display: block; font-style: normal; font-family: var(--f-display); font-size: var(--f-size-small); font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; margin-top: 0.6rem; color: var(--c-accent-ink); }

.schedule { width: 100%; border-collapse: collapse; }
.schedule th, .schedule td { text-align: left; padding: 0.9rem 1rem; border-bottom: 1px solid var(--c-divider); }
.schedule th { font-family: var(--f-display); font-size: var(--f-size-small); letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-heading); font-weight: 600; white-space: nowrap; }

details.faq {
	border-bottom: 1px solid var(--c-divider);
	padding-block: 1rem;
}
details.faq summary {
	cursor: pointer;
	font-family: var(--f-text);
	font-size: var(--f-size-x-large);
	color: var(--text-heading);
	list-style: none;
	display: flex;
	justify-content: space-between;
	align-items: center;
	gap: 1rem;
}
details.faq summary::-webkit-details-marker { display: none; }
details.faq summary::after { content: "+"; font-family: var(--f-display); color: var(--c-accent-ink); font-size: 1.5rem; transition: transform var(--t-ui) var(--ease); }
details.faq[open] summary::after { transform: rotate(45deg); }

/* Click-to-load embed facade (Tithe.ly, maps): static card until interaction. */
.embed-facade {
	position: relative;
	display: grid;
	place-items: center;
	gap: 0.75rem;
	min-height: 320px;
	border-radius: var(--radius-card);
	border: 1px dashed var(--c-accent);
	background: var(--surface-alt);
	text-align: center;
	padding: 2rem;
}
html.no-js .embed-facade button { display: none; }
.embed-facade iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; border-radius: var(--radius-card); }
.embed-facade:has(iframe) { padding: 0; min-height: 360px; aspect-ratio: 16 / 9; }

.socials {
	display: flex;
	flex-wrap: wrap;
	gap: 0.75rem 1.5rem;
	list-style: none;
	margin: 0 0 1.5rem;
	padding: 0;
	font-weight: 600;
}

.audio-player { width: 100%; }
.audio-player audio { width: 100%; }

.sermon-filters { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-bottom: 2rem; }
.sermon-filters select {
	font: inherit;
	padding: 0.6rem 1rem;
	border-radius: var(--radius-button);
	border: 1px solid var(--c-divider);
	background: var(--surface);
	color: var(--text-heading);
}

.pagination { display: flex; gap: 0.4rem; justify-content: center; margin-top: 3rem; }
.pagination .page-numbers {
	padding: 0.5rem 0.9rem;
	border-radius: var(--radius-button);
	border: 1px solid var(--c-divider);
	text-decoration: none;
	color: var(--text-heading);
}
.pagination .page-numbers.current { background: var(--c-primary); border-color: var(--c-primary); color: var(--c-white); }

/* ---------------------------------------------------------------- footer */

.site-footer { background: var(--c-lagoon); color: rgba(255, 255, 255, 0.75); padding-block: 4rem 2.25rem; }
.site-footer a { color: rgba(255, 255, 255, 0.9); }
.site-footer a:hover { color: var(--c-accent-on-dark); }
.site-footer h2, .site-footer h3 { color: var(--c-white); font-size: var(--f-size-large); margin: 0 0 0.35rem; }

.site-footer__inner {
	display: grid;
	grid-template-columns: 1.4fr 1fr auto;
	gap: 1.75rem 2rem;
	align-items: start;
	margin-bottom: 1.5rem;
}
.site-footer__brand p,
.site-footer__place,
.site-footer__contact { margin: 0; }
.site-footer__contact {
	display: flex;
	flex-direction: column;
	align-items: flex-end;
	gap: 0.35rem;
	text-align: end;
}
.site-footer__social {
	display: flex;
	gap: 0.25rem;
	font-size: 1.35rem;
	line-height: 1;
	margin-top: 0.3rem;
}
.site-footer__social a {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 44px;
	min-height: 44px;
	text-decoration: none;
}

.site-footer__nav { margin-top: 0.5rem; }
.site-footer ul { list-style: none; margin: 0; padding: 0; }
.site-footer__nav ul {
	display: flex;
	flex-wrap: wrap;
	justify-content: flex-start;
	gap: 0.5rem 1.75rem;
	width: 100%;
}
.site-footer__nav li { margin: 0; }
/* Standalone navigation links, so they get a real tap target rather than the
   20px line box they inherited. */
.site-footer__nav a { display: inline-block; padding: 0.5rem 0; }
/* Theme switch. It lives in the footer, not the bar: the header's five links
   are the church's navigation and a display preference is not one of them.
   The footer is lagoon in BOTH themes, so every colour here is a fixed value
   rather than a token that flips underneath it. */
.site-footer__utility {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: 0.75rem 1.5rem;
}
.site-footer__utility .site-footer__nav { margin-top: 0; }

/* peaceharbor/toolbox/theme-toggle — the component below is VENDORED VERBATIM
   from toolbox/theme-toggle/public/theme-toggle.css. Do not edit it here; a fix
   belongs in the toolbox and then propagates. This site's only say in it is the
   four --theme-toggle-* roles mapped further down.
   Gated byte for byte by `the_theme_component_is_the_toolbox_copy`. */
/* Peace Harbor toolbox — theme toggle component.
 *
 * Vendor VERBATIM. Every colour is a `--theme-toggle-*` custom property the
 * adopting site sets in its own stylesheet; the fallbacks are the PH family
 * token names, so a family site can vendor this and set nothing. A site with
 * its own palette (a client site) maps the four roles and touches nothing here.
 *
 *   --theme-toggle-border   the button's edge
 *   --theme-toggle-fg       the icon
 *   --theme-toggle-hover-border / --theme-toggle-hover-fg   on hover
 */
.theme-toggle {
	width: 2.25rem;
	height: 2.25rem;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	background: transparent;
	/* The edge is this control's ONLY affordance — there is no label and no
	   fill — so it is held to 3:1 (WCAG 1.4.11), not to a hairline's bar. */
	border: 1px solid var(--theme-toggle-border, var(--control-border, currentColor));
	border-radius: 999px;
	color: var(--theme-toggle-fg, var(--fg, currentColor));
	cursor: pointer;
	flex-shrink: 0;
	transition: border-color 120ms ease, color 120ms ease, background-color 120ms ease, transform 80ms ease;
}
.theme-toggle:hover {
	border-color: var(--theme-toggle-hover-border, var(--accent, currentColor));
	color: var(--theme-toggle-hover-fg, var(--accent-text, currentColor));
}
.theme-toggle:active { transform: scale(0.96); }
/* 2.25rem is 36px, over the 24px WCAG 2.5.8 floor, and the icon is centred in
   it rather than being the target itself. */
.theme-toggle svg { display: block; }

/* theme.js removes the attribute once it has wired the control. Without JS the
   button cannot work, and a dead control is worse than none. */
.theme-toggle[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
	/* The press-scale is motion and goes. Colour transitions stay: they are a
	   change of colour, not of position. */
	.theme-toggle { transition: border-color 120ms ease, color 120ms ease, background-color 120ms ease; }
	.theme-toggle:active { transform: none; }
}

/* ---- this site's mapping of the four roles --------------------------------

   The button is an icon with no label and no fill, so its EDGE is the whole
   affordance and is held to 3:1, not to a hairline's bar. --text-meta is the
   quiet-type role this sheet already deepens for exactly that reason: 5.4:1 on
   the light surface, 7.6:1 on the dark one. Hover moves to the maroon/rose ink
   the site uses for every other hover — nav links, card titles. */
.theme-toggle {
	--theme-toggle-border: var(--text-meta);
	--theme-toggle-fg: var(--text-body);
	--theme-toggle-hover-border: var(--c-primary-ink);
	--theme-toggle-hover-fg: var(--c-primary-ink);
}
/* .site-header__inner is space-between across four children, so without this
   the button lands stranded in the middle of the bar. An auto margin absorbs
   the free space ahead of it, which groups it with the nav on the right and
   leaves the brand alone on the left. */
.site-header .theme-toggle { margin-inline-start: auto; }

/* Over the hero the backdrop is a PHOTOGRAPH behind a scrim, not a colour, so
   there is no pair for a value gate to check and a fixed grey would read as a
   sticker on some frames. Deliberately the same treatment .btn--ghost-light
   already uses over the same photograph rather than a second answer to a
   question this site has answered once. */
.site-header:not(.is-opaque):not(.is-scrolled) .theme-toggle {
	--theme-toggle-border: rgba(255, 255, 255, 0.7);
	--theme-toggle-fg: var(--c-white);
	--theme-toggle-hover-border: var(--c-white);
	--theme-toggle-hover-fg: var(--c-white);
}

/* The footer is lagoon in BOTH themes, so it takes fixed values rather than
   tokens that flip underneath it. --c-silver is what the footer already uses
   for its quiet type: 5.75:1 on lagoon, clearing the 3:1 an edge-only control
   needs. */
.site-footer .theme-toggle {
	--theme-toggle-border: var(--c-silver);
	--theme-toggle-fg: var(--c-silver);
	--theme-toggle-hover-border: var(--c-accent-on-dark);
	--theme-toggle-hover-fg: var(--c-accent-on-dark);
}

.site-footer__legal {
	border-top: 1px solid rgba(255, 255, 255, 0.14);
	margin: 1.75rem 0 0;
	padding-top: 1.25rem;
	font-size: var(--f-size-small);
	color: var(--c-silver);
	text-align: center;
}
@media (max-width: 800px) {
	.site-footer__inner { grid-template-columns: 1fr; gap: 1.25rem; }
	.site-footer__contact { align-items: flex-start; text-align: start; }
	.site-footer__utility { justify-content: flex-start; }
}

/* ---------------------------------------------------- video + playlists */

/* 16:9 responsive frame. aspect-ratio rather than the padding-top hack, so
   the box reserves its height before the iframe loads and nothing shifts. */
.video-embed {
	position: relative;
	aspect-ratio: 16 / 9;
	border-radius: var(--radius-card);
	overflow: hidden;
	background: var(--c-ink);
}
.video-embed iframe {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	border: 0;
	display: block;
}

/* Four to a row, stepping down rather than shrinking — a 16:9 player below
   about 220px wide is unusable, and YouTube's own chrome starts clipping. */
.playlist-grid {
	display: grid;
	grid-template-columns: repeat(4, 1fr);
	gap: clamp(1rem, 0.7rem + 1vw, 1.75rem);
}
.playlist-card { min-width: 0; }
.playlist-card__title {
	font-size: var(--f-size-small);
	font-weight: 600;
	line-height: 1.35;
	margin: 0 0 0.6rem;
	color: var(--text-heading);
	/* Titles vary from two words to eleven; cap at two lines so the players
	   below them stay on a common baseline across the row. */
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
}
@media (max-width: 1100px) { .playlist-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 820px)  { .playlist-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 520px)  { .playlist-grid { grid-template-columns: 1fr; } }

/* ------------------------------------------------------ section dot-nav */

/* A fixed frosted pill on the right edge, one dot per home-page section.
   Ported from the Peace Harbor family sites (phstudios/phdesigns) as a
   MECHANISM only — the colours are ARBC's own tokens. brand/web/ is the PH
   family system and is deliberately not vendored into a client site.

   The dots are ordinary in-page anchors, so they work with JS off; main.js
   only moves the [aria-current] marker as you scroll. */
.section-nav {
	position: fixed;
	right: 0.85rem;
	top: 50%;
	transform: translateY(-50%);
	z-index: 90;            /* under the header (100), over content */
	pointer-events: none;   /* the pill re-enables it; the gutter stays clickable */
}
.section-nav ul {
	list-style: none;
	margin: 0;
	padding: 0.6rem 0.5rem;
	display: flex;
	flex-direction: column;
	gap: 0.45rem;
	background: color-mix(in oklab, var(--surface) 72%, transparent);
	backdrop-filter: blur(10px) saturate(140%);
	-webkit-backdrop-filter: blur(10px) saturate(140%);
	border: 1px solid var(--c-divider);
	border-radius: 999px;
	box-shadow: 0 10px 30px rgba(10, 28, 32, 0.14);
	pointer-events: auto;
}
.section-nav li { margin: 0; }
.section-nav a {
	display: block;
	padding: 0.3rem;
	border-radius: 50%;
	text-decoration: none;
}
.section-nav a:focus-visible { outline: 2px solid var(--c-primary-ink); outline-offset: 2px; }
.section-nav__dot {
	display: block;
	width: 9px;
	height: 9px;
	border-radius: 50%;
	background: color-mix(in oklab, var(--text-heading) 30%, transparent);
	transition: background var(--t-ui) var(--ease), transform var(--t-ui) var(--ease);
}
.section-nav a:hover .section-nav__dot,
.section-nav a[aria-current="true"] .section-nav__dot {
	background: var(--c-primary-ink);
	transform: scale(1.45);
}
/* No room for a fixed right rail beside content on a phone, and the dots are
   a 9px target — the drawer is the navigation there. */
@media (max-width: 900px) {
	.section-nav { display: none; }
}
@media (prefers-reduced-motion: reduce) {
	.section-nav__dot { transition: none; }
}

/* -------------------------------------------------- home page tightening */

/* The home page is a scan, not a read: six stacked sections at the full
   --section-pad put up to 240px between consecutive blocks of content. The
   interior pages are text-heavy and keep the roomier default. */
.page-home .gp-section { padding-block: clamp(52px, 3rem + 2.4vw, 88px); }
.page-home .gp-section__head { margin-bottom: clamp(1.5rem, 1.15rem + 1.4vw, 2.5rem); }
.page-home .home-contact .gp-section__head { margin-bottom: 1.25rem; }

/* --------------------------------------------------------- contact form */

.home-contact {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 2.5rem 3.5rem;
	align-items: start;
}
/* Grid items default to min-width:auto, so one unshrinkable child (the 300px
   Turnstile widget) widens the whole track instead of overflowing alone. */
.home-contact__intro,
.home-contact__form { min-width: 0; }
.home-contact .gp-section__head { margin-bottom: 1.5rem; }

.contact-form { display: grid; gap: 0.7rem; }
.contact-form h2 { margin: 0 0 0.25rem; font-size: var(--f-size-large); }
.contact-form > label,
.contact-form__field,
.contact-form__row > label { display: grid; gap: 0.25rem; font-size: var(--f-size-small); }
.contact-form__row { display: grid; grid-template-columns: 1fr 1fr; gap: 0.7rem; }
.contact-form .optional { opacity: 0.65; font-weight: 400; }

/* The panel sits on --primary-dark, so the field text colour is pinned to ink
   rather than inherited: --text-body is near-white inside .gp-section--dark. */
.contact-form input,
.contact-form textarea {
	font: inherit;
	color: var(--c-ink);
	background: #FFFFFF;
	border: 1px solid var(--c-divider);
	border-radius: var(--radius-button);
	padding: 0.55rem 0.75rem;
	width: 100%;
}
.contact-form input::placeholder,
.contact-form textarea::placeholder { color: var(--c-muted); }
.contact-form textarea { resize: vertical; min-height: 5.5rem; }
/* An invalid field is the one state that has to be unmissable, so it gets
   the functional error red — not the lagoon, which is the panel itself. Both
   tokens are fixed (neither dark floor touches them), because the panel and
   its white fields are identical in both themes: the pale halo reads against
   the lagoon panel and the red border against the white field. */
.contact-form [aria-invalid="true"] { border-color: var(--c-error); box-shadow: 0 0 0 2px var(--c-error-soft); }
.contact-form .hp {
	position: absolute;
	left: -9999px;
	height: 0;
	overflow: hidden;
}
.contact-form .btn { justify-self: start; margin-top: 0.25rem; }
.contact-form .status { min-height: 1.25em; font-size: var(--f-size-small); margin: 0; }
.contact-form .cf-turnstile { margin-top: 0.2rem; min-height: 65px; }

/* Success toast from the toolbox's contact-form.js, shown for ~4s before the
   page reloads. Lagoon panel, gold rule, Alata — the site's own dark panel. */
.toast {
	position: fixed;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	z-index: 9999;
	max-width: min(90vw, 28rem);
	padding: 1.5rem 2.25rem;
	background: var(--c-primary-dark);
	color: var(--c-white);
	border: 1px solid var(--c-accent-on-dark);
	border-radius: var(--radius-card);
	font-family: var(--f-display);
	font-size: var(--f-size-large);
	line-height: 1.4;
	text-align: center;
	box-shadow: 0 18px 40px rgba(10, 32, 38, 0.35);
	animation:
		toast-in 240ms var(--ease) both,
		toast-out 600ms ease-in 3.4s both;
}
@keyframes toast-in {
	from { opacity: 0; transform: translate(-50%, -42%); }
	to   { opacity: 1; transform: translate(-50%, -50%); }
}
@keyframes toast-out {
	from { opacity: 1; }
	to   { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
	.toast { animation: none; }
}

/* The toolbox phone-field.css reads these five. Mapped on :root, not the
   form: on a narrow screen intl-tel-input appends its fullscreen picker to
   <body>, outside the form. The fields are white in both themes, so the
   picker is too. The border is the muted ink, not --c-divider: a dropdown
   edge owes 3:1 (WCAG 1.4.11) and the divider is decorative. */
:root {
	--ph-phone-surface: #FFFFFF;
	--ph-phone-fg:      var(--c-ink);
	--ph-phone-muted:   var(--c-muted);
	--ph-phone-border:  var(--c-muted);
	--ph-phone-ring:    var(--c-primary);
}

/* intl-tel-input, mounted lazily. Its own stylesheet loads with the script.
   Every colour here is pinned to a literal role token, NOT to --text-heading
   or --surface: this panel sits inside .gp-section--dark, which redefines
   --text-heading to near-white. Inheriting it painted the country names cream
   on a cream dropdown — invisible, with only the dial codes readable. */
.contact-form .iti { width: 100%; display: block; }
.contact-form .iti__selected-dial-code { color: var(--c-ink); }
.contact-form .iti__dropdown-content {
	background: #FFFFFF;
	color: var(--c-ink);
	border: 1px solid var(--c-divider);
	border-radius: var(--radius-button);
}
.contact-form .iti__search-input {
	color: var(--c-ink);
	background: #FFFFFF;
	border-bottom: 1px solid var(--c-divider);
}
.contact-form .iti__country { color: var(--c-ink); }
.contact-form .iti__country-name { color: var(--c-ink); }
.contact-form .iti__dial-code { color: var(--c-muted); }
.contact-form .iti__country.iti__highlight,
.contact-form .iti__country[aria-selected="true"] {
	background: var(--c-mist);
	color: var(--c-ink);
}
.contact-form .iti__country.iti__highlight .iti__dial-code,
.contact-form .iti__country[aria-selected="true"] .iti__dial-code { color: var(--c-muted); }

@media (max-width: 900px) {
	.home-contact { grid-template-columns: 1fr; }
}
@media (max-width: 480px) {
	.contact-form__row { grid-template-columns: 1fr; }
}

/* ------------------------------------------------- posts + generic pages */

.gp-section--top { padding-top: clamp(8rem, 12vh, 11rem); }

.gp-page-head { padding-bottom: 0; }

.post-meta {
	color: var(--c-muted);
	font-size: 0.9rem;
}

.post-card + .post-card {
	margin-top: clamp(2rem, 1.5rem + 1.5vw, 3rem);
	padding-top: clamp(2rem, 1.5rem + 1.5vw, 3rem);
	border-top: 1px solid var(--c-divider);
}

.post-nav .container--prose { display: flex; gap: 1.5rem; justify-content: space-between; }
.post-nav__next { margin-inline-start: auto; text-align: end; }

/* --------------------------------------------------------------- reveals */

[data-reveal] {
	opacity: 0;
	transform: translateY(18px);
	transition: opacity var(--t-reveal) var(--ease), transform var(--t-reveal) var(--ease);
	transition-delay: var(--reveal-delay, 0ms);
}
[data-reveal].is-revealed { opacity: 1; transform: none; }

/* No-JS and reduced-motion: content always visible, motion suppressed. */
.no-js [data-reveal] { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
	html { scroll-behavior: auto; }
	*, *::before, *::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
	}
	[data-reveal] { opacity: 1; transform: none; }
}
