/* Fraunces — variable, latin + latin-ext, upright and italic. */
@font-face {
	font-family: 'Fraunces';
	src: url('/fonts/fraunces-latin-variable.woff2') format('woff2');
	font-weight: 100 900;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2122;
}
@font-face {
	font-family: 'Fraunces';
	src: url('/fonts/fraunces-latin-ext-variable.woff2') format('woff2');
	font-weight: 100 900;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0100-024F, U+1E00-1EFF, U+2020, U+20A0-20AB;
}
@font-face {
	font-family: 'Fraunces';
	src: url('/fonts/fraunces-latin-italic-variable.woff2') format('woff2');
	font-weight: 100 900;
	font-style: italic;
	font-display: swap;
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2122;
}
@font-face {
	font-family: 'Fraunces';
	src: url('/fonts/fraunces-latin-ext-italic-variable.woff2') format('woff2');
	font-weight: 100 900;
	font-style: italic;
	font-display: swap;
	unicode-range: U+0100-024F, U+1E00-1EFF, U+2020, U+20A0-20AB;
}

/* Source Sans 3 — variable, latin + latin-ext. */
@font-face {
	font-family: 'Source Sans 3';
	src: url('/fonts/source-sans-3-latin-variable.woff2') format('woff2');
	font-weight: 200 900;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2122;
}
@font-face {
	font-family: 'Source Sans 3';
	src: url('/fonts/source-sans-3-latin-ext-variable.woff2') format('woff2');
	font-weight: 200 900;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0100-024F, U+1E00-1EFF, U+2020, U+20A0-20AB;
}

:root {
	color-scheme: light dark;

	--c-primary:      #7A1E23;
	--c-primary-dark: #551318;
	--c-primary-soft: #F3E7E6;
	/* Crimson as TEXT. The brand crimson is a dark colour, so it only works as
	   type on a light surface: on the dark floor #7A1E23 measures 1.53:1 on
	   --surface-alt, which is not "hard to read", it is unreadable. Anything
	   that sets crimson TYPE uses this token; --c-primary itself stays the
	   brand crimson for fills, borders and rules, where it is a background and
	   the 3:1 non-text bar applies. */
	--c-primary-ink:  var(--c-primary);
	--c-accent:       #B0762E;
	--c-accent-soft:  #E8D9BF;
	/* The hero and the dark sections are dark in BOTH themes, so accent type
	   on them must not follow prefers-color-scheme. --c-accent-soft flips to a
	   near-black brown in dark mode; anything sitting on the scrim uses this
	   fixed value instead, or it goes dark-on-dark. */
	--c-accent-on-dark: #E8D9BF;
	/* Accent TEXT on a light surface. --c-accent is the brand bronze and stays
	   the brand bronze for rules, dots and borders, where the bar is 3:1 for
	   non-text. As small bold TEXT on cream it measures 3.46-3.83:1 against a
	   4.5:1 AA floor — every eyebrow on the site, plus the doctrine contents
	   title. This is the same hue two steps down: 5.00:1 on cream. */
	--c-accent-ink: #8F5E24;
	--c-cream:        #F7F3EB;
	--c-white:        #FFFFFF;
	--c-divider:      #E3DCCF;
	--c-ink:          #26211E;
	--c-muted:        #5C554E;
	/* Silver is a BORDER and a footer-legal colour: it is 1.88:1 on white, so
	   as body-adjacent type on a light surface it is a hard AA failure. Card
	   meta lines and event locations used it and were unreadable in the light
	   theme — the opposite failure to the crimson above, found in the same
	   sweep. --text-meta is the quieter-than-body role those want: 5.57:1 on
	   white, 5.04:1 on cream, 5.40:1 on the dark floor's lighter surface. */
	--c-silver:       #B9B3AA;
	--text-meta:      #6E675F;
	/* A text field's border is the ONLY thing that says a control is there, so
	   WCAG 1.4.11 wants 3:1 against the surface around it — a bar --c-divider
	   was never meant to clear (it is a hairline between rows: 1.2:1 on white).
	   It mattered less while the fields were painted hardcoded white on a cream
	   section; the dark-mode fix above makes the field the same colour as the
	   page, so the border is now carrying the whole job. 3.75:1 on white,
	   3.39:1 on cream. */
	--field-border:   #8A8377;

	--f-serif: 'Fraunces', Georgia, 'Times New Roman', serif;
	--f-sans:  'Source Sans 3', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-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.
	   --page-body / --page-heading are the PAGE's values and are never
	   overridden by a section; --text-body / --text-heading are the CURRENT
	   context's and .gp-section--dark does override them. A card inside a dark
	   section needs the former back, and aliasing is how it gets them without
	   a second copy of the literals. */
	--page-body:    var(--c-muted);
	--page-heading: var(--c-ink);
	--surface:      var(--c-white);
	--surface-alt:  var(--c-cream);
	--text-body:    var(--page-body);
	--text-heading: var(--page-heading);

	/* Role tokens for the controls whose dark values are not simply a token
	   flip. These exist so the dark theme is ONE block of custom properties
	   rather than four scattered @media rules — see the note on the dark floor
	   below. */
	--link:            var(--c-primary-ink);
	--link-hover:      var(--c-primary-dark);
	--ghost-fg:        var(--c-primary-ink);
	--ghost-border:    var(--c-primary);
	--ghost-hover-bg:  var(--c-primary);
	--ghost-hover-fg:  var(--c-white);

	/* 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);
	--f-size-display:  clamp(44px, 2.4rem + 4.2vw, 96px);

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

/* ---------------------------------------------------------- the dark floor */

/* ONE trigger: the visitor's system setting. There is no theme switch
 * (peaceharbor/toolbox/color-scheme), so this block is the only copy of the
 * dark values.
 *
 * Everything the dark theme changes is a custom property here. Three
 * @media (prefers-color-scheme: dark) blocks used to sit further down the file
 * setting `color` on `a`, `.btn--ghost` and a card; they are role tokens now
 * (--link, --ghost-fg, --page-heading…) so the dark theme stays one block. */
@media (prefers-color-scheme: dark) {
	:root {
		--surface: #1C1917;
		--surface-alt: #262220;
		--page-body: #C9C2B9;
		--page-heading: #F1EBE1;
		--c-divider: #3A342F;
		--c-primary-soft: #33201F;
		--c-accent-soft: #3A2F1D;
		/* The darker ink fails on the dark floor, but so does the brand bronze:
		   #B0762E is 4.11:1 on --surface-alt, under the same 4.5 this token
		   exists to satisfy. I had called that passing; it is not. #C98A3C is
		   5.39:1. */
		--c-accent-ink: #C98A3C;
		/* 6.42:1 on --surface-alt, 7.12:1 on --surface. Same hue as the brand
		   crimson (357deg), lightened until it clears AA rather than swapped
		   for a colour off the palette. */
		--c-primary-ink: #DF9094;
		--text-meta: #9E968C;
		/* 3.70:1 on --surface, 3.33:1 on --surface-alt. */
		--field-border: #7A7269;
		/* Body links are the one place that must not be borderline: --c-accent
		   is 4.11:1 here. --link-hover is NOT --c-accent-soft, which flips to
		   #3A2F1D above and made the hover vanish under the cursor. */
		--link: var(--c-accent-ink);
		--link-hover: var(--c-accent-on-dark);
		/* The crimson does not flip, so a ghost button was #7A1E23 on #262220 —
		   1.53:1 — while its filled sibling stayed readable. The border follows
		   the text so the outline does not float unattached. */
		--ghost-fg: var(--text-heading);
		--ghost-border: currentColor;
		--ghost-hover-bg: var(--text-heading);
		--ghost-hover-fg: var(--surface);
	}
}

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

html { scroll-behavior: smooth; scroll-padding-top: 96px; }
/* The 96px above is the inner-page bar (.site-header__inner min-height). The
   home page's bar is taller because its mark is bigger, and scroll-padding-top
   has to track the bar it actually has or every in-page jump lands under it.
   Measured: the footer's live /#contact link put "Write Us" 27px beneath the
   header. :has() is safe to default per the tree's modern-web baseline and is
   already used in this file. The extra 8px is breathing room, not slack. */
html:has(.site-header--over-hero) { scroll-padding-top: 131px; }

body {
	margin: 0;
	background: var(--surface);
	color: var(--text-body);
	font-family: var(--f-sans);
	font-size: var(--f-size-medium);
	line-height: 1.8;
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, h5, h6 {
	font-family: var(--f-serif);
	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(--link); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--link-hover); }

/* Focus: :focus-visible only, ring visible on any surface. */
:focus { outline: none; }
:focus-visible {
	outline: 2px solid var(--c-accent);
	outline-offset: 3px;
	border-radius: 2px;
}

.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; min-width: 0; }
.container--narrow { width: min(100% - 3rem, 992px); margin-inline: auto; }
.container--prose { width: min(100% - 3rem, 720px); margin-inline: auto; }

/* --------------------------------------------------------- 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-sans);
	font-size: var(--f-size-x-small);
	font-weight: 700;
	letter-spacing: 0.22em;
	text-transform: uppercase;
	color: var(--c-accent-ink);
	margin-bottom: 0.9rem;
}
.gp-section--dark .eyebrow { color: var(--c-accent-on-dark); }

/* Siapo-inspired rule: repeated diamond geometry, bronze, hairline-weight. */
.gp-rule {
	border: 0;
	height: 8px;
	margin: 0 0 1.2rem;
	width: 88px;
	background-image: repeating-linear-gradient(135deg, var(--c-accent) 0 1.5px, transparent 1.5px 8px),
		repeating-linear-gradient(45deg, var(--c-accent) 0 1.5px, transparent 1.5px 8px);
	opacity: 0.75;
}
.gp-section__head--center .gp-rule { margin-inline: auto; }

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

.btn {
	display: inline-block;
	font-family: var(--f-sans);
	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); }

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

.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);
}

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

.site-header__brand {
	display: flex;
	align-items: center;
	gap: 0.9rem;
	font-family: var(--f-serif);
	font-size: 1.05rem;
	font-weight: 550;
	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;
}
/* The home page is the only page with over_hero (pages.rs), so this class IS
   "on the home page". The mark gets more room there because the header is
   transparent over the hero with nothing to crowd it; the inner pages keep the
   compact bar. Scoped to the class rather than the scroll state so it does not
   resize under the reader on scroll. */
.site-header--over-hero .site-header__brand img {
	width: 96px;
	height: 96px;
}

/* Over the hero the type goes light; keep the mark in full color so the
   wreath, cross, and lockup still read. */
.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(12, 6, 6, 0.45);
}
.site-header:not(.is-opaque):not(.is-scrolled) .site-header__brand img {
	filter: drop-shadow(0 2px 10px rgba(12, 6, 6, 0.45));
}

.site-nav > ul {
	display: flex;
	align-items: center;
	gap: clamp(1rem, 0.4rem + 1vw, 1.75rem);
	list-style: none;
	margin: 0;
	padding: 0;
}
.site-nav a {
	font-size: 1.05rem;
	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;
	flex-shrink: 0;
	background: none;
	border: 0;
	padding: 0.65rem;
	margin-left: auto;
	cursor: pointer;
}
.nav-toggle .bars,
.nav-toggle .bars::before,
.nav-toggle .bars::after {
	content: "";
	display: block;
	width: 22px;
	height: 2.5px;
	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. That offset was left over from the resting state, where
   ::before sits at top:-7px; once `top: 0` did that job it double-counted.
   Rotating +90 (not -90) sweeps both arms the same way into the cross. */
.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); }

@media (max-width: 900px) {
	.site-nav > ul { flex-wrap: wrap; justify-content: flex-end; gap: 0.5rem 1rem; }
	html.no-js .nav-toggle { display: none; }
	.nav-toggle { display: block; }
	/* Overlay only when a hamburger precedes the nav (header.php order). */
	.site-header__brand img { width: 56px; height: 56px; }
	/* The 16ch cap, not the mark, is what drives the wrap here: at 72px it
	   pushed the name to FIVE lines and a 128px bar. Giving the text 20ch
	   back lands a bigger mark, three lines instead of four, and a header
	   SHORTER than the 56px one it replaces (99px vs 108px). Measured at
	   414px; still clears the hamburger by a wide margin. */
	.site-header--over-hero .site-header__brand img { width: 72px; height: 72px; }
	.site-header--over-hero .site-header__brand { max-width: 20ch; }
	/* Same reason as the desktop rule: the home bar is 99px here. */
	html:has(.site-header--over-hero) { scroll-padding-top: 107px; }
	.site-header__brand { max-width: 16ch; }
	.site-header__inner { min-height: 80px; }
	.nav-toggle {
		display: flex;
		align-items: center;
		justify-content: center;
		width: 44px;
		height: 44px;
		/* --text-heading, NOT --c-ink. --c-ink is #26211E and does not flip, so
		   on the dark floor the bars were 1.10:1 on --surface — the hamburger
		   simply gone on every inner page, on a phone, in dark mode. The
		   desktop rule above already used the themed token; this override
		   undid it. Found by the ship review, same class as the crimson. */
		color: var(--text-heading);
	}
	.site-header:not(.is-opaque):not(.is-scrolled) .nav-toggle { color: var(--c-white); }
	.nav-toggle ~ .site-nav {
		/* Absolute inside the fixed header, so the sheet starts at the bar's
		   real bottom whatever height the brand wraps to. `100%` in the calc
		   resolves against that same header box. */
		position: absolute;
		top: 100%;
		inset-inline: 0;
		height: calc(100dvh - 100%);
		background: var(--surface);
		padding: 1.5rem;
		overflow-y: auto;
		display: none;
	}
	.nav-toggle ~ .site-nav.is-open { display: block; }
	.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;
	}
	/* Inside the open sheet the links are dark on a light surface, so the
	   over-hero glow has to come off with the white. */
	.site-header:not(.is-opaque):not(.is-scrolled) .nav-toggle ~ .site-nav a {
		color: var(--text-heading);
		text-shadow: none;
	}
	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: visible;
	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 { overflow: hidden; }
.hero__media,
.hero__media video,
.hero__media img,
.hero__fallback {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.hero__video:not([src]):not(:has(source)) { display: none; }
.hero__video {
	opacity: 0;
	transition: opacity 0.8s var(--ease);
}
/* Only once main.js has it playing. With JS off there is no autoplay any more
   (2026-09-26), so a visible <video> would be a paused frame, with iOS's play
   chevron, sitting over the poster img. */
.hero__video.is-ready { opacity: 1; }
.hero__fallback {
	background:
		radial-gradient(120% 90% at 15% 10%, color-mix(in oklab, var(--c-primary) 70%, black) 0%, transparent 55%),
		linear-gradient(160deg, var(--c-primary-dark), #2A0D10 78%);
}
.hero__fallback::after {
	content: "";
	position: absolute;
	inset: 0;
	background-image: repeating-linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0 1px, transparent 1px 22px),
		repeating-linear-gradient(45deg, rgba(255, 255, 255, 0.04) 0 1px, transparent 1px 22px);
}

.hero__scrim {
	position: absolute;
	inset: 0;
	background: linear-gradient(to top, rgba(12, 6, 6, 0.72) 0%, rgba(12, 6, 6, 0.25) 55%, rgba(12, 6, 6, 0.35) 100%);
}
.hero:has(.hero__video source) .hero__scrim {
	background: linear-gradient(to top, rgba(12, 6, 6, 0.78) 0%, rgba(12, 6, 6, 0.22) 48%, rgba(12, 6, 6, 0.52) 100%);
}

.hero__content { position: relative; z-index: 1; min-width: 0; max-width: 100%; }
/* The hero type sits directly on moving footage, so every line carries its own
   shadow rather than relying on the scrim alone. */
.hero__content .eyebrow {
	color: var(--c-accent-on-dark);
	text-shadow: 0 1px 2px rgba(12, 6, 6, 0.6), 0 2px 16px rgba(12, 6, 6, 0.75);
}
.hero__content h1 {
	color: var(--c-white);
	max-width: 16ch;
	overflow-wrap: break-word;
	text-shadow: 0 2px 24px rgba(12, 6, 6, 0.55);
}
.hero__lede {
	font-size: var(--f-size-large);
	max-width: min(46ch, 100%);
	color: rgba(255, 255, 255, 0.88);
	text-shadow: 0 1px 14px rgba(12, 6, 6, 0.55);
}
.hero__lede, .hero__meta { overflow-wrap: break-word; }
@media (max-width: 700px) {
	.hero__content h1 {
		max-width: none;
		font-size: clamp(2.15rem, 9vw, 3rem);
		line-height: 1.12;
	}
}
.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;
	text-shadow: 0 1px 2px rgba(12, 6, 6, 0.55), 0 2px 14px rgba(12, 6, 6, 0.65);
}
.hero__actions { display: flex; flex-wrap: wrap; gap: 0.9rem; margin-top: 2rem; }

/* The story prose shares the section head's measure so the eyebrow, rule,
   heading and paragraphs all hang off one left edge. */
/* Copy left, the mark filling the column to its right. The right half of this
   section was empty because the prose is capped at its 46rem measure while the
   container runs to 1280px.
   The split starts at 1100px, NOT at the 900px the rest of the site uses: at
   920px the copy column measured 354px against a 416px mark, i.e. the
   decoration wider than the prose it decorates. 1100px is where the copy stays
   the dominant column. Below it the section stacks. */
.home-story { display: grid; gap: clamp(1.5rem, 6vw, 3rem); }
@media (min-width: 1100px) {
	.home-story {
		grid-template-columns: minmax(0, 1fr) minmax(0, 26rem);
		gap: clamp(2rem, 5vw, 5rem);
		align-items: center;
	}
}
.home-story__copy > p {
	max-width: 46rem;
	font-size: var(--f-size-large);
}
/* Reserve the fixed dot rail's gutter — but only across the band where the rail
   can actually reach the container. `.container` is min(100% - 3rem, 1280px),
   so past roughly 1400px the container centres and the rail sits well outside
   it; padding there would inset the mark for nothing.
   Measured at 1200px: without this the mark's right edge sat 32px under the
   rail; with it, 24px clear.
   NOTE this is not the only content that reaches that edge — `.home-week`'s
   contact column does too, and the rail can cross it at some scroll positions.
   That is pre-existing and deliberately not fixed here; a site-wide gutter is
   its own change. An earlier review raised the overlap and I recorded it as
   not reproducing, which was true only until this section put something there. */
@media (min-width: 1100px) and (max-width: 1400px) {
	.home-story { padding-inline-end: 3.5rem; }
}
.home-story__mark { display: block; }
.home-story__mark img {
	display: block;
	width: 100%;
	height: auto;
}
/* Stacked: the copy reads first and the mark closes the section. */
@media (max-width: 1099px) {
	.home-story__mark img { max-width: 18rem; margin-inline: auto; }
}

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

.card-grid { display: grid; gap: 1.5rem; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
/* The "where next" line under a section: contextual links into the rest of
   the site, so a visitor who lands here from search has a way on. */
.section-onward { margin-top: 2rem; max-width: 62ch; }
/* Reserve Turnstile's widget height: it now loads as the form approaches, and
   an empty box that later grows would push Send down under a finger. */
.cf-turnstile { min-height: 65px; }

.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);
	/* .gp-section--dark redefines --text-heading to white and --text-body to
	   near-white for type sitting on the crimson. A card inside it does NOT
	   sit on the crimson — it paints itself --surface, which is WHITE in the
	   light theme — so those inherited values would be white-on-white. Take
	   the page's own values back. No page currently puts a card in a dark
	   section; the rule above is what makes that a question of when. */
	--text-heading: var(--page-heading);
	--text-body: var(--page-body);
	color: var(--text-body);
}
.card:hover { transform: translateY(-3px); box-shadow: 0 18px 40px rgba(28, 16, 14, 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, missionaries): the client's photos are mostly FAMILY
   pictures (landscape/square), with one true portrait. A 4:5 portrait crop
   guillotines the group shots, so people cards use a gentle 4:3 and bias the
   crop upward, where faces sit in both shapes. */
.card__media--person { aspect-ratio: 4 / 3; }
/* Anchor high: the one true portrait (a 3:4 source) loses ~40% of its height to
   a 4:3 box, and a centred crop takes the heads with it. */
.card__media--person img { object-position: center 12%; }

.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); }

/* ------------------------------------------------- confession & doctrine */

/* The nine-point statement of faith. Numbered because the document numbers
   them, and each point is a whole confession — so they get room to breathe
   rather than reading as a tight list. */
.confession {
	list-style: none;
	counter-reset: confession;
	padding: 0;
	margin: 0;
}
.confession > li {
	counter-increment: confession;
	position: relative;
	padding-inline-start: 3rem;
	margin-bottom: 1.6rem;
	font-size: var(--f-size-large);
}
.confession > li::before {
	content: counter(confession);
	position: absolute;
	inset-inline-start: 0;
	top: 0.1em;
	font-family: var(--f-serif);
	font-size: 1.5rem;
	line-height: 1;
	color: var(--c-accent);
}

.creed { margin-block: 2.25rem; }
.creed__name {
	font-size: clamp(19px, 1.05rem + 0.35vw, 24px);
	margin: 0 0 0.6rem;
}
.creed__era {
	font-family: var(--f-sans);
	font-size: var(--f-size-x-small);
	font-weight: 700;
	letter-spacing: 0.18em;
	text-transform: uppercase;
	color: var(--c-accent-ink);
	margin-inline-start: 0.6rem;
	white-space: nowrap;
}
/* The creeds are quoted speech, so they read as a quotation, not as body copy. */
.creed__text {
	font-family: var(--f-serif);
	font-size: var(--f-size-large);
	border-inline-start: 3px solid var(--c-divider);
	padding-inline-start: 1.25rem;
	margin: 0;
}

.doctrine__intro { font-size: var(--f-size-large); }

/* A 24-section statement needs a way in. Plain ordered list of the ten top
   level headings; the sub-headings are deliberately left out so the map stays
   glanceable. */
.doctrine__toc {
	margin-top: 2.5rem;
	border-top: 1px solid var(--c-divider);
	padding-top: 1.5rem;
}
.doctrine__toc-title {
	font-family: var(--f-sans);
	font-size: var(--f-size-x-small);
	font-weight: 700;
	letter-spacing: 0.22em;
	text-transform: uppercase;
	color: var(--c-accent-ink);
	margin: 0 0 1rem;
}
.doctrine__toc ol {
	columns: 2;
	column-gap: 2rem;
	margin: 0;
	padding-inline-start: 1.2rem;
}
.doctrine__toc li { margin-bottom: 0.45rem; break-inside: avoid; }
@media (max-width: 560px) {
	.doctrine__toc ol { columns: 1; }
}

/* scroll-margin so an in-page jump clears the fixed header. */
.doctrine__head,
.doctrine__sub { scroll-margin-top: 7rem; }
.doctrine__head {
	margin-top: 3rem;
	padding-top: 2rem;
	border-top: 1px solid var(--c-divider);
}
.doctrine > .doctrine__head:first-of-type { margin-top: 0; padding-top: 0; border-top: 0; }
.doctrine__sub {
	margin-top: 2rem;
	font-size: clamp(18px, 1rem + 0.3vw, 22px);
	color: var(--c-primary-ink);
}

/* ---------------------------------------------------------- section rail */

/* The dot rail from peaceharbor.com, in this site's own tokens — gold accent,
   crimson-tinted glass — not the PH family palette. Below 900px it becomes a
   bottom pill rather than hiding — see the mobile block further down. */
.section-nav {
	position: fixed;
	right: 0.85rem;
	top: 50%;
	transform: translateY(-50%);
	z-index: 18;
	pointer-events: none;
}
.section-nav ul {
	list-style: none;
	margin: 0;
	padding: 0.6rem 0.5rem;
	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;
	display: flex;
	flex-direction: column;
	gap: 0.45rem;
	pointer-events: auto;
	box-shadow: 0 6px 24px rgba(12, 6, 6, 0.12);
}
.section-nav a {
	/* The DOT is 9px; the TARGET has to be 24x24 to clear WCAG 2.2 2.5.8, so
	   the anchor is sized explicitly rather than left to padding round a small
	   inline box. */
	display: flex;
	align-items: center;
	justify-content: center;
	min-width: 24px;
	min-height: 24px;
	border-radius: 50%;
	text-decoration: none;
}
.section-nav__dot {
	display: block;
	width: 9px;
	height: 9px;
	border-radius: 50%;
	background: color-mix(in oklab, var(--text-body) 34%, transparent);
	transition: background var(--t-card) var(--ease), transform var(--t-card) var(--ease);
}
.section-nav a:hover .section-nav__dot,
.section-nav a[aria-current="true"] .section-nav__dot {
	background: var(--c-accent);
	transform: scale(1.45);
}
/* The rail is a nav, so the ring has to clear the dot rather than sit on it. */
.section-nav a:focus-visible {
	outline: 2px solid var(--c-accent);
	outline-offset: 2px;
}
/* A long page's rail gets tighter spacing so ten dots do not run the height of
   the screen. (Mobile re-tightens it again — see the bottom-pill block.) */
.section-nav--long ul { gap: 0.3rem; }

/* Mobile. The rail used to be hidden here; it is wanted on phones too, where
   the pages are longest. Two changes make that safe rather than merely
   possible:
   - The targets grow to 32px. 24px clears WCAG 2.5.8 for a mouse; a thumb on a
     moving page wants more.
   - The pill moves to the BOTTOM, horizontal and centred, instead of the right
     edge. A vertical rail mid-screen sits directly over the right end of every
     full-width form field and body line on a phone — the contact form is the
     worst case — and `pointer-events: auto` on the pill means it would eat
     those taps. Along the bottom it overlays only the gutter. */
@media (max-width: 900px) {
	.section-nav {
		top: auto;
		bottom: max(0.75rem, env(safe-area-inset-bottom));
		right: 50%;
		transform: translateX(50%);
	}
	.section-nav ul {
		flex-direction: row;
		padding: 0.4rem 0.6rem;
		gap: 0.35rem;
		/* Eight 32px dots plus gaps is ~314px, which does not fit a 320px
		   phone once the pill's own padding is counted. Cap and wrap rather
		   than overflow the viewport. */
		max-width: calc(100vw - 2rem);
		flex-wrap: wrap;
		justify-content: center;
	}
	.section-nav--long ul { gap: 0.25rem; }
	.section-nav a { min-width: 32px; min-height: 32px; }
	/* Reserve the pill's band at the END OF THE DOCUMENT. Mid-page it overlays
	   the gutter, which was the whole point of moving it down here — but at the
	   bottom of the page the footer's legal line scrolls up into exactly that
	   band, and the pill takes pointer events. That is WCAG 2.4.11: a focused
	   or tapped link obscured by fixed content. Padding the footer pushes the
	   last line clear instead. ~47px pill + 12px offset + breathing room. */
	.site-footer { padding-bottom: 5.5rem; }
}

/* A printed page has no viewport to float in; the rail would stamp itself onto
   every sheet of the 29-couplet statement. */
@media print {
	.section-nav { display: none; }
}

/* The open menu sheet covers the page; a rail floating over it is noise. */
body:has(.site-nav.is-open) .section-nav { display: none; }
@media (prefers-reduced-motion: reduce) {
	.section-nav__dot { transition: none; }
}

/* The Code of Conduct is 29 affirmation/denial couplets. The pair is the unit,
   so it is boxed as one: the affirmation leads at body weight, the denial sits
   under it, indented behind an accent rule and set slightly quieter — present
   and readable, but clearly the guard on the claim above rather than a second
   claim of equal weight. */
.beliefs__actions {
	display: flex;
	flex-wrap: wrap;
	gap: 0.9rem;
}

/* The contents list is the jump-off point for a long statement, so "Back to
   Top" returns THERE rather than to the masthead — landing on the page title
   would make the reader scroll down again to pick the next section.
   scroll-margin clears the fixed header; it is larger than the bar because the
   heading above the list should come back into view with it. */
#on-this-page { scroll-margin-top: 7rem; }
.back-to-top {
	margin: 0.5rem 0 0;
	text-align: end;
}
.back-to-top a {
	font-family: var(--f-sans);
	font-size: var(--f-size-x-small);
	font-weight: 700;
	letter-spacing: 0.18em;
	text-transform: uppercase;
	text-decoration: none;
	color: var(--c-accent-ink);
	/* 24px minimum target (WCAG 2.5.8); the text box alone was 20px tall. */
	display: inline-block;
	padding-block: 0.15rem;
	min-height: 24px;
}
.back-to-top a:hover { color: var(--c-primary-ink); text-decoration: underline; }

.conduct__pairs {
	list-style: none;
	margin: 0;
	padding: 0;
}
.conduct__pair {
	margin-block: 1.75rem;
	padding-inline-start: 1.1rem;
	border-inline-start: 2px solid var(--c-divider);
}
.conduct__believe { margin: 0 0 0.6rem; }
.conduct__deny {
	margin: 0;
	padding-inline-start: 1.1rem;
	border-inline-start: 2px solid var(--c-accent);
	/* Body colour, NOT a muted one. The first attempt reached for
	   var(--text-muted, var(--c-silver)); --text-muted does not exist in this
	   stylesheet, so it always fell through to the silver and measured 1.88:1
	   on cream — a hard WCAG AA failure on doctrinal text. The indent, the gold
	   rule and the smaller size carry the hierarchy; contrast does not have to,
	   and for a church's stated convictions it must not. */
	color: var(--text-body);
	font-size: var(--f-size-small);
}

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

.scripture {
	font-family: var(--f-serif);
	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;
}
/* A dark section is dark in BOTH themes, so its type takes the fixed pale
   value. --c-accent-soft flips to #3A2F1D here and the quotation went
   dark-on-dark. */
.gp-section--dark .scripture { color: var(--c-accent-on-dark); }
/* The citation is a CHILD of the quotation and was left behind by the line
   above: --c-accent-ink is #8F5E24 in the light theme, 2.54:1 on this
   section's #551318. Half a fix is the same bug. */
.gp-section--dark .scripture cite { color: var(--c-accent-on-dark); }
.scripture cite { display: block; font-style: normal; font-family: var(--f-sans); 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-sans); 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-serif);
	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-sans); color: var(--c-accent); 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;
	/* The iframe is absolutely positioned, so the FACADE's height is the
	   player's height. One number cannot serve both: Transistor's single
	   episode player is short and centres in dead space, its playlist is tall
	   and scrolls internally. Each call site sets --embed-min-h. */
	min-height: var(--embed-min-h, 320px);
	border-radius: var(--radius-card);
	border: 1px dashed var(--c-accent);
	background: var(--surface-alt);
	text-align: center;
	padding: 2rem;
}
.embed-facade iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; border-radius: var(--radius-card); }
/* A --dark section is crimson in BOTH themes, so its facade cannot inherit
   --surface-alt: that token is cream in the light theme and #262220 in the
   dark one, which put a cream panel on crimson for half the visitors and a
   near-black one for the rest. Fixed values, same fix as .scripture above. */
.gp-section--dark .embed-facade {
	background: rgba(255, 255, 255, 0.06);
	border-color: var(--c-accent-on-dark);
}

/* Sized to the players, measured in the browser 2026-09-21: the single-episode
   teaser needs ~200px, the archive playlist ~380px for a header plus four rows.
   Do not chase these as the archive grows — past about four episodes Transistor
   scrolls the list INSIDE the iframe, which is its own designed behaviour, and
   a box tall enough for every future sermon is a box mostly full of nothing. */
#latest-sermon .embed-facade { --embed-min-h: 200px; }
#archive .embed-facade { --embed-min-h: 380px; }
.latest-sermon__more { margin-top: 1.75rem; }
/* Injected by main.js after a player loads — the visible escape hatch when the
   iframe comes back blank. Quiet, because when the player works it is noise. */
.embed-facade__fallback { margin-top: 0.75rem; font-size: var(--f-size-small); }

.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(--field-border);
	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-ink);
	color: rgba(255, 255, 255, 0.75);
	padding-block: 0 2.25rem;
}
.site-footer a { color: rgba(255, 255, 255, 0.9); }
/* Fixed pale value, not --c-accent-soft: the footer is always dark, and that
   token flips to a near-black brown on the dark floor. */
.site-footer a:hover { color: var(--c-accent-on-dark); }
/* Whose number the second one is. The footer is dark in both themes, so silver
   is safe here — it is 7.65:1 on --c-ink — where it would fail on a light
   surface. */
.site-footer__phone-label {
	font-size: var(--f-size-x-small);
	letter-spacing: 0.04em;
	color: var(--c-silver);
}
.site-footer h2, .site-footer h3 { color: var(--c-white); font-size: var(--f-size-large); margin: 0 0 0.35rem; }
.site-footer__tatau {
	height: 52px;
	margin-bottom: 2.25rem;
	background: url('/images/siapo-band.svg') center / auto 52px repeat-x;
	opacity: 0.28;
	filter: brightness(0.95);
	pointer-events: none;
}
.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;
}
/* 44px tap target around a 22px glyph; the negative margin keeps the icon
   itself flush with the right-aligned phone lines above it. */
.site-footer__facebook {
	display: inline-flex;
	align-items: center;
	justify-content: flex-end;
	min-width: 44px;
	min-height: 44px;
	margin: -0.35rem 0 -0.5rem;
	line-height: 1;
}
.site-footer__facebook svg { display: block; width: 1.35rem; height: 1.35rem; }
.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; }
.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; }
}

.home-week {
	display: grid;
	grid-template-columns: 3fr 2fr;
	gap: 2.5rem 3rem;
	align-items: start;
}
.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; }
.contact-form input,
.contact-form textarea {
	font: inherit;
	/* Was `color: var(--text)` — a token this stylesheet has never defined, so
	   the declaration was dropped and the field inherited --text-body while its
	   background stayed hardcoded white. On the dark floor that is #C9C2B9 on
	   #FFFFFF: 1.63:1. Everything the visitor typed into the contact form was
	   effectively invisible. Both halves now follow the theme together. */
	color: var(--text-heading);
	background: var(--surface);
	border: 1px solid var(--field-border);
	border-radius: var(--radius-button);
	padding: 0.55rem 0.75rem;
	width: 100%;
}
.contact-form textarea { resize: vertical; min-height: 5.5rem; }
.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); }
.contact-form .cf-turnstile { margin-top: 0.2rem; }
/* The success toast. contact-form.js (toolbox) appends .toast to <body> on a
   sent message and reloads the page 4.2s later; the look is this site's.
   Ink with cream type and a bronze edge, the same in both themes, like the
   site's other always-dark grounds. */
.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-ink);
	color: var(--c-cream);
	border: 1px solid var(--c-accent-on-dark);
	border-radius: var(--radius-card);
	font-family: var(--f-serif);
	font-size: var(--f-size-large);
	text-align: center;
	line-height: 1.45;
	box-shadow: 0 1.5rem 3rem rgba(0, 0, 0, 0.45);
	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; }
}
/* Phone picker — the styling this site pioneered now lives in the toolbox as
   public/phone-field.css, vendored verbatim. All this stylesheet owns is the
   mapping: the toolbox's five required --ph-phone-* variables pointed at
   gpfbc's own tokens, plus the optional --ph-phone-radius so the dropdown
   takes the same corner as every other control here. The other optional one,
   --ph-phone-wash, keeps its default.

   No `?v=` stamp on the <link> for this file, unlike phdesigns and phweb.
   Those workers serve CSS with `max-age=3600` and need the stamp to break it;
   this one passes assets through untouched and Cloudflare serves them
   `public, max-age=0, must-revalidate`, so every request revalidates on ETag
   and an edit is live immediately (verified against gpfbc.org 2026-09-18).
   Adding a stamp here would be cargo-culting the sibling's workaround for a
   header this site does not send.

   These must sit on :root, not .contact-form. On a narrow viewport
   intl-tel-input appends a fullscreen popup to <body> — outside the form —
   and the rules this block replaces were scoped to .contact-form, so they
   stopped applying exactly there. On the dark floor that left the vendor's
   white overlay standing in front of a dark page.

   --ph-phone-border is --field-border, the CONTROL edge, not --c-divider as
   the old block had it: a dropdown boundary is a UI component under WCAG
   1.4.11 and needs 3:1, which --c-divider (#E3DCCF, decoratively light on
   purpose) does not carry. */
:root {
	--ph-phone-surface: var(--surface);
	--ph-phone-fg:      var(--text-heading);
	--ph-phone-muted:   var(--text-body);
	--ph-phone-border:  var(--field-border);
	--ph-phone-ring:    var(--c-accent);
	--ph-phone-radius:  var(--radius-button);
}
.coming-up { margin-top: 2rem; }
.coming-up .gp-section__head { margin-bottom: 0.75rem; }
.coming-up h3 { margin: 0; font-size: var(--f-size-large); }
.coming-up__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: 1.25rem;
}
.coming-up__list li {
	padding-bottom: 1.25rem;
	border-bottom: 1px solid var(--c-divider);
}
.coming-up__list li:last-child { padding-bottom: 0; border-bottom: 0; }
.coming-up__when {
	margin: 0;
	font-size: var(--f-size-tiny);
	font-weight: 700;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--c-accent-ink);
}
.coming-up__title {
	margin: 0.2rem 0 0;
	font-size: var(--f-size-medium);
}
.coming-up__place {
	margin: 0.15rem 0 0;
	font-size: var(--f-size-small);
	color: var(--text-meta);
}

@media (max-width: 800px) {
	.home-week { 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(--text-meta);
	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; }

/* A print or PDF never scrolls, so IntersectionObserver never fires and every
   un-revealed block prints blank. Worst on the long confessional pages, where
   most of the couplets sit below the first screen. */
@media print {
	[data-reveal] { opacity: 1 !important; transform: none !important; }
}

@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; }
	.hero__video { display: none; }
}
