/*
Theme Name: The Haunting Bow
Theme URI: https://hauntingbow.com
Author: The Haunting Bow
Author URI: https://hauntingbow.com
Description: The Haunting Bow's own block theme. Tokens live in theme.json (named after DESIGN.md §3), templates and patterns in the repo, released by rsync (ADR-0002). Colours and type are generated from DESIGN.md §3 and §4 by scripts/theme-tokens.py; Fraunces and Source Sans 3 are self-hosted under the SIL OFL (assets/fonts).
Requires at least: 6.7
Tested up to: 7.1
Requires PHP: 7.4
Version: 0.9.47
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: hauntingbow
Update URI: https://hauntingbow.com
Tags: one-column, custom-colors, custom-menu, editor-style, featured-images, full-site-editing, block-patterns, block-styles, style-variations, wide-blocks
*/

/*
 * Only what theme.json cannot express. Every colour below is a token from
 * theme.json (--wp--preset--color--*), never a literal, so the dark section
 * style and the real palette (HB-12) flip everything at once.
 */

/* Layout: one content width. contentSize and wideSize are both 1312 px (Mohammad, 2026-09-16 on the
   HB-20 preview: a 760 px text column beside 1312 px cards read as "half the page"). The rule below
   still pins any narrower text column to the wide column's left edge — where the header wordmark,
   the footer columns and every alignwide block start — instead of centring it on its own, which
   core would do if the two sizes ever differ again (HB-24; DESIGN.md §8). Placeholder boxes keep
   centring their caption. */
.is-layout-constrained:not(.hb-placeholder) > :where(:not(.alignleft):not(.alignright):not(.alignfull):not(.alignwide)) {
	margin-left: max(0px, calc((100% - var(--wp--style--global--wide-size)) / 2)) !important;
}

/* Landing template (HB-45, Mohammad 2026-09-24): the one exception to the rule above — a single
   875 px column (two thirds of the wide size, set on the template's main) centred on the page. The
   header wordmark and the footer keep the wide edge. */
.hb-landing.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull):not(.alignwide)),
.hb-landing .is-layout-constrained:not(.hb-placeholder) > :where(:not(.alignleft):not(.alignright):not(.alignfull):not(.alignwide)) {
	margin-left: auto !important;
	margin-right: auto !important;
}

/* Header (HB-46): the bar sticks and slims. Sticky goes on the template part's wrapper — the group
   inside it has nothing to stick within (lessons/wordpress/sticky-inside-a-template-part-needs-the-wrapper)
   — or on the bar itself where a template inlines the pattern (the landing page); under the admin
   bar when there is one. Past 100 px of scroll assets/js/header.js puts hb-scrolled on <html> and
   the bar slims to 56 px (the Navigation & motion board): tighter padding, the wordmark at the
   medium size, the Tickets button kept at a 44 px tap target — nothing hidden. The script also
   publishes the bar's live height as --hb-header-height on <html>, for anything that pins
   beneath it (the register card below; the Next-event ribbon, its own ticket). */
:root {
	--hb-header-height: 0px;
	--hb-header-slim: 56px;
	--hb-header-slim-pad: 5.5px; /* 56 − the hairline − the 44 px row, halved */
	/* The site's vertical rhythm (HB-119, re-tuned by HB-26 on the real type: Mohammad chose B, tighter, from renders on the
	   preview 2026-10-09): the space between two sections, text to text, and the space from the bar to a page's first line
	   when no hero opens it. 80/56 px at 1440, 48/32 on a phone. Inside a section the block gap (spacing 40, 24 px) sits
	   between any two blocks, a heading and its words included; a pair (a name and the line under it) takes spacing 20. */
	--hb-gap-section: clamp(3rem, 5.6vw, 5rem);
	--hb-gap-page-top: clamp(2rem, 3.9vw, 3.5rem);
	/* Where a jump lands: a section's first line this far under the slim bar (HB-119, 24 px, Mohammad 2026-09-30). */
	--hb-land: 24px;
}
/* An anchor lands clear of the stuck bar (HB-100, Mohammad on the preview 2026-09-28: a jump to #private-lessons put the section's
   title under the bar). After a jump the page is past 100 px, so the bar is the slim one. The target's top edge lands --hb-land
   under it; a section, whose words start below its own top padding, pulls itself up by that padding (below), so its first line
   lands in the same place as a heading that carries the anchor itself (HB-119). */
html {
	scroll-padding-top: calc(var(--wp-admin--admin-bar--height, 0px) + var(--hb-header-slim) + var(--hb-land));
}
/* An event's #tickets (every list's Buy / Register button) where the register card sits beside the title: the page's top, so the
   crumb, the title and the card show together (Mohammad, 2026-09-28). A margin larger than the card's offset scrolls to 0; on a
   phone the card is under the words and the jump goes to it. */
@media (min-width: 782px) {
	#tickets {
		scroll-margin-top: 100vh;
	}
}
header.wp-block-template-part,
.wp-site-blocks > .hb-header {
	position: sticky;
	top: var(--wp-admin--admin-bar--height, 0px);
	z-index: 20;
}
.hb-header {
	/* The text token on the bar itself: the menu links and the hamburger inherit their colour (core's rule), so
	   they must take it from here, where the float (HB-47) flips the tokens — body's resolved colour would not. */
	color: var(--wp--preset--color--text);
	border-bottom: 1px solid var(--wp--preset--color--border);
	transition: padding 200ms ease, --hb-bar-alpha 200ms ease; /* the alpha: the float below (HB-47) */
}
.hb-header > .alignwide {
	min-height: 0px;
	transition: min-height 200ms ease;
}
.hb-header .wp-block-site-title {
	transition: font-size 200ms ease;
}
/* The words and the button ease their colours too, for the float's flip to the dark set (HB-47). */
.hb-header .wp-block-site-title a,
.hb-header .wp-block-navigation a,
.hb-header .wp-block-navigation__responsive-container-open {
	transition: color 200ms ease;
}
.hb-header .hb-header__cta .wp-element-button {
	transition: padding 200ms ease, background-color 200ms ease, color 200ms ease, border-color 200ms ease;
}
/* The slim bar. !important because the resting padding is the pattern's own block attribute
   (inline), left to the Site Editor; the slim height is the board's rule, not a setting. */
:root.hb-scrolled .hb-header {
	padding-top: var(--hb-header-slim-pad) !important;
	padding-bottom: var(--hb-header-slim-pad) !important;
}
:root.hb-scrolled .hb-header > .alignwide {
	/* The row fills the slim bar, so its items sit centred whatever they are (the landing bar has only the wordmark). */
	min-height: calc(var(--hb-header-slim) - 1px - 2 * var(--hb-header-slim-pad));
}
:root.hb-scrolled .hb-header .wp-block-site-title {
	font-size: var(--wp--preset--font-size--medium);
}
:root.hb-scrolled .hb-header .hb-header__cta .wp-element-button {
	padding-top: 0.5625rem; /* 44 px tall: the 24 px line, 2 px of border, 9 px each side */
	padding-bottom: 0.5625rem;
	line-height: 1.5rem; /* the line itself, whatever the body size (17 px at 1.5 would make it 25.5, HB-12) */
}
@media (prefers-reduced-motion: reduce) {
	.hb-header,
	.hb-header > .alignwide,
	.hb-header .wp-block-site-title,
	.hb-header .wp-block-site-title a,
	.hb-header .wp-block-navigation a,
	.hb-header .wp-block-navigation__responsive-container-open,
	.hb-header .hb-header__cta .wp-element-button {
		transition: none;
	}
}
/* header.js suspends every transition for the one frame in which it sets the float's first state,
   so a page never loads with a solid bar that then fades (HB-47). */
:root.hb-header-instant .hb-header,
:root.hb-header-instant .hb-header * {
	transition: none !important;
}

/* Floating over Home's hero (HB-47, the Navigation & motion board's hero reveal; Mohammad, 2026-09-19). On a page
   whose content opens with the hero picture — Home, where the editors' picture is the first block — the bar hangs
   from a zero-height sticky box instead of taking a row of its own, the root block gap under it goes, and the
   picture starts at the page's top edge with the bar over it. Keyed to the picture being present and first, never
   to the template: a page without one keeps its bar above its content, and so does a browser without :has() (the
   picture then sits under the bar as before, never under an invisible one). While the picture is still under the
   bar, header.js keeps hb-over-hero on <html>: the bar's background and hairline fade to nothing — the alpha is
   the registered --hb-bar-alpha, eased on its own, so the colour beneath it still follows the mode shift frame by
   frame with no lag — and the wordmark, the links and Tickets take the dark set, read on the soft gradient the
   hero paints over its top band (the way its words read at the bottom). Once the picture's bottom edge passes the
   slim bar the class goes and the bar is HB-46's solid bar again; back at the top it clears again. */
@property --hb-bar-alpha {
	syntax: "<number>";
	inherits: false;
	initial-value: 1;
}
header.wp-block-template-part {
	/* The page's own tokens, captured above the bar: its paint keeps following the page (the mode shift) while
	   the words inside it take the dark set. */
	--hb-bar-bg: var(--wp--preset--color--bg);
	--hb-bar-border: var(--wp--preset--color--border);
}
/* The condition, once: the hero picture first in the page's content — or (HB-70) the band's Cover of the stage photo
   first in the page's first section, Performance's opening; its own rules follow the band's, below. */
.wp-site-blocks:has(> main > .wp-block-post-content > .hb-hero:first-child > figure.hb-hero__media, > main > .wp-block-post-content > .hb-section:first-child > .wp-block-cover.hb-band__cover:first-child) > header.wp-block-template-part {
	height: 0; /* the bar hangs from a zero-height sticky box: out of the flow, still stuck */
}
.wp-site-blocks:has(> main > .wp-block-post-content > .hb-hero:first-child > figure.hb-hero__media, > main > .wp-block-post-content > .hb-section:first-child > .wp-block-cover.hb-band__cover:first-child) > main {
	margin-block-start: 0;
	padding-top: 0 !important; /* the "Dark (stage mode)" template's main carries its top padding inline (HB-70) */
	padding-bottom: 0 !important; /* and its 7 rem at the bottom: the last section's own padding is the gap to the footer (Mohammad, HB-70 review) */
}
.wp-site-blocks:has(> main > .wp-block-post-content > .hb-hero:first-child > figure.hb-hero__media, > main > .wp-block-post-content > .hb-section:first-child > .wp-block-cover.hb-band__cover:first-child) .hb-header {
	/* !important: the preset class the pattern carries (has-bg-background-color) is one too. */
	background-color: color-mix(in srgb, var(--hb-bar-bg) calc(var(--hb-bar-alpha) * 100%), transparent) !important;
	border-bottom-color: color-mix(in srgb, var(--hb-bar-border) calc(var(--hb-bar-alpha) * 100%), transparent);
}
:root.hb-over-hero .hb-header {
	--hb-bar-alpha: 0;
	/* The dark set — the same flip the hero panel makes for its own words; the phone overlay menu, which paints
	   the bg token, goes dark with it and stays opaque. */
	--wp--preset--color--bg: var(--wp--custom--dark--bg);
	--wp--preset--color--surface: var(--wp--custom--dark--surface);
	--wp--preset--color--text: var(--wp--custom--dark--text);
	--wp--preset--color--text-muted: var(--wp--custom--dark--text-muted);
	--wp--preset--color--accent: var(--wp--custom--dark--accent);
	--wp--preset--color--accent-contrast: var(--wp--custom--dark--accent-contrast);
	--wp--preset--color--border: var(--wp--custom--dark--border);
}

/* Neither the wordmark nor Tickets ever breaks mid-phrase: the row is nowrap, so a narrow screen shrinks the
   items instead, and a broken word is what shows (Mohammad's phone, 2026-09-23: "The Haunting / Bow", "Ticket / s"). */
.hb-header .wp-block-site-title,
.hb-header .hb-header__cta .wp-element-button {
	white-space: nowrap;
}
/* Phone: Tickets sits next to the menu toggle, as on the Home — phone board. At this width the wordmark takes
   the slim bar's size and the button and the gap tighten, so wordmark + toggle + button fit one line at 320. */
@media (max-width: 599px) {
	.hb-header .hb-header__cta {
		order: 1;
	}
	.hb-header .wp-block-navigation {
		order: 2;
	}
	.hb-header .wp-block-site-title {
		font-size: var(--wp--preset--font-size--medium);
	}
	.hb-header .hb-header__right {
		gap: var(--wp--preset--spacing--30);
	}
	.hb-header .hb-header__cta .wp-element-button {
		padding-left: var(--wp--preset--spacing--30);
		padding-right: var(--wp--preset--spacing--30);
	}
}

/* Navigation overlay (phone): a full-screen list in large type, start-aligned
   whatever the bar's justification is (Home — phone board). */
/* (Core pads the overlay from the root padding — keep those values unit-bearing, "0px" not "0",
   or its clamp() is invalid and the padding collapses.) */
.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content,
.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__container {
	align-items: flex-start !important;
	justify-content: flex-start !important;
}
.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__container {
	gap: var(--wp--preset--spacing--40);
}
.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-item__content {
	font-size: var(--wp--preset--font-size--x-large);
	font-weight: 700;
}

/* Footer: hairline on top; link columns become two-up rather than a single stack on phones. */
.hb-footer {
	border-top: 1px solid var(--wp--preset--color--border);
}
/* Social icons in the text colour (the board's neutral squares), so they follow the dark variant too. */
.hb-footer .wp-block-social-links.is-style-logos-only .wp-social-link.wp-social-link {
	color: var(--wp--preset--color--text);
}
.hb-footer .wp-block-navigation__container {
	gap: var(--wp--preset--spacing--20);
}
@media (max-width: 781px) {
	.hb-footer .hb-footer__columns {
		flex-wrap: wrap !important;
	}
	.hb-footer .hb-footer__columns > .wp-block-column {
		flex-basis: calc(50% - var(--wp--style--block-gap, 1.5rem) / 2) !important;
	}
	.hb-footer .hb-footer__columns > .wp-block-column:first-child {
		flex-basis: 100% !important;
	}
}

/* Demo placeholder for an image or a module still to come (docs/linear.md §Demo content convention).
   Editors delete the block and insert the real one. */
.hb-placeholder {
	position: relative;
	display: grid;
	place-items: center;
	min-height: 12rem;
	padding: var(--wp--preset--spacing--40);
	background: var(--wp--preset--color--surface);
	border: 1px solid var(--wp--preset--color--border);
	color: var(--wp--preset--color--text-muted);
	text-align: center;
}
.hb-placeholder--wide {
	min-height: 20rem;
}
.hb-placeholder::after {
	content: "DEMO";
	position: absolute;
	top: 0.5rem;
	left: 0.5rem;
	padding: 0.15em 0.5em;
	font-size: 0.7rem;
	font-weight: 700;
	letter-spacing: 0.12em;
	background: var(--wp--preset--color--text);
	color: var(--wp--preset--color--bg);
}

/* Two Dark sections in a row butt together: the block gap between them would show the page
   background as a stripe while the Home mode shift is mid-way (Mohammad, 2026-09-16). */
.is-style-section-dark + .is-style-section-dark {
	margin-block-start: 0 !important;
}

/* One rhythm (HB-119): a page's sections (the Groups carrying .hb-section, Home, Performance, Learning) sit flush and each
   carries half the section gap above and below, so text to text is always --hb-gap-section, and a coloured band keeps
   its padding inside its colour. The theme owns it: !important, because the pages carry the editor's padding inline, and
   an editor adding or reordering a section never types a number. Left alone: the hero (it has its own height and the bar
   floats over it), the Next ribbon (a thin strip), and a section that opens on a Cover (its photo starts at its edge). */
.wp-block-post-content > .hb-section:not(.hb-hero):not(.hb-ribbon) {
	padding-top: calc(var(--hb-gap-section) / 2) !important;
	padding-bottom: calc(var(--hb-gap-section) / 2) !important;
	scroll-margin-top: calc(var(--hb-gap-section) / -2); /* a jump lands its first line, not its padding, under the bar */
}
.wp-block-post-content > .hb-section:not(.hb-ribbon) + .hb-section:not(.hb-ribbon) {
	margin-block-start: 0 !important;
}
/* Nothing inside adds to the gap: a section is its own block formatting context, so no margin inside it escapes an edge that
   has no padding (on a phone Learning's opening words sit in the flow under the photo, and their last 8 px margin leaked out). */
.wp-block-post-content > .hb-section:not(.hb-hero):not(.hb-ribbon):not(.is-layout-flex):not(.is-layout-grid) {
	display: flow-root;
}
/* The same for the words inside a band's photo (on a phone they sit in the flow under it): the last one's margin stops there. */
.wp-block-post-content > .hb-section > .wp-block-cover:last-child .wp-block-cover__inner-container > :last-child {
	margin-block-end: 0 !important;
}
.wp-block-post-content > .hb-section:first-child,
.wp-block-post-content > .hb-section:has(> .wp-block-cover:first-child) {
	padding-top: 0 !important;
	scroll-margin-top: 0;
}
.wp-block-post-content > .hb-section:has(> .wp-block-cover:last-child) {
	padding-bottom: 0 !important; /* a section that is only its photo (Learning's opening) ends at the photo's edge */
}
/* A photo's edge counts as the section's first or last line: the section beside it takes the whole gap on that side, so a
   band that opens or ends on its photo sits the same distance from the words next to it (Learning's and Performance's
   openings, Home's Mana Duet band). */
.wp-block-post-content > .hb-section:not(.hb-hero):not(.hb-ribbon):has(+ .hb-section > .wp-block-cover:first-child) {
	padding-bottom: var(--hb-gap-section) !important;
}
.wp-block-post-content > .hb-section:has(> .wp-block-cover:last-child) + .hb-section:not(.hb-ribbon) {
	padding-top: var(--hb-gap-section) !important;
	scroll-margin-top: calc(var(--hb-gap-section) * -1);
}
/* The hero ends at its picture's edge (HB-26): the Next ribbon sits flush under it, as on the Home board, and any other section
   after it takes the whole gap, the picture's edge counting as the hero's last line. */
.wp-block-post-content > .hb-section.hb-hero {
	padding-bottom: 0 !important;
}
.wp-block-post-content > .hb-section.hb-hero + .hb-section:not(.hb-ribbon) {
	padding-top: var(--hb-gap-section) !important;
	scroll-margin-top: calc(var(--hb-gap-section) * -1);
}
/* A heading and its words take the block gap like any two blocks (HB-26), where a pattern typed the pair gap: the intros of
   Home's Memories and newsletter sections. */
.wp-block-group.hb-memories__intro > * + *,
.wp-block-group.hb-signup__copy > * + * {
	margin-block-start: var(--wp--preset--spacing--40);
}
/* A muted line or a label right under a heading qualifies it: one unit, the pair gap (HB-26; the Mana Duet note on
   Performance, the press kit's word counts under Short, Medium, Long). */
:is(h1, h2, h3, h4) + :is(p.has-text-muted-color, p.hb-eyebrow) {
	margin-block-start: var(--wp--preset--spacing--20);
}
/* A page with no hero: its first line starts --hb-gap-page-top under the bar, whatever the template typed (the pages and
   single templates carry spacing-60, the event page spacing-40, all inline). The hero pages' own rule (above, the
   :has() on the header) is more specific and keeps their main flush; the Landing template keeps its own. */
/* WooCommerce wraps the cart's, the checkout's and a product's <main> in a block of its own: the wrapper sits flush too. */
.wp-site-blocks main.wp-block-group:not(.hb-landing),
.wp-site-blocks > :has(> main.wp-block-group:not(.hb-landing)) {
	margin-block-start: 0;
}
.wp-site-blocks main.wp-block-group:not(.hb-landing) {
	padding-top: var(--hb-gap-page-top) !important;
}
/* An empty notices box (the cart, the checkout) takes no block gap: the title under it starts at the page-top gap. */
.wp-site-blocks main.wp-block-group > .wp-block-woocommerce-store-notices:first-child:not(:has([role="alert"], .woocommerce-message, .woocommerce-error, .woocommerce-info, .wc-block-components-notice-banner)) + * {
	margin-block-start: 0;
}
/* The kamancheh notes (Landing template) give each contents section the spacing 60 preset as top padding in the page itself
   (5 rem until HB-26's recipe): the section pulls itself up by it, so its heading lands --hb-land under the bar like any other
   (HB-119). Keyed to that inline value only, so a section styled otherwise is judged by the check, not hidden by this rule. */
.hb-landing .wp-block-post-content .wp-block-group[id][style*="padding-top:5rem"] {
	scroll-margin-top: -5rem;
}
.hb-landing .wp-block-post-content .wp-block-group[id][style*="padding-top:var(--wp--preset--spacing--60)"] {
	scroll-margin-top: calc(var(--wp--preset--spacing--60) * -1);
}

/* Section label above a heading ("Flagship · Performance"). */
.hb-eyebrow {
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--wp--preset--color--text-muted);
}

/* Forms (Fluent Forms with "Inherit Theme Style", HB-17): fields and the submit button
   in the theme's tokens, so they follow the palette and the dark section style. */
.fluentform.ff-inherit-theme-style .ff-el-group {
	margin-bottom: var(--wp--preset--spacing--40);
}
.fluentform.ff-inherit-theme-style .ff-el-input--label label {
	display: inline-block;
	margin-bottom: var(--wp--preset--spacing--20);
	font-weight: 600;
}
.fluentform.ff-inherit-theme-style .ff-el-input--label.ff-el-is-required.asterisk-right label::after {
	content: " *";
	color: var(--wp--preset--color--text-muted);
}
.fluentform.ff-inherit-theme-style .ff-el-form-control {
	display: block;
	width: 100%;
	padding: 0.7rem 0.9rem;
	font: inherit;
	line-height: 1.4;
	color: var(--wp--preset--color--text);
	background: var(--wp--preset--color--bg);
	border: 1px solid var(--wp--preset--color--border);
	border-radius: 0;
}
.fluentform.ff-inherit-theme-style select.ff-el-form-control {
	appearance: auto;
}
.fluentform.ff-inherit-theme-style .ff-el-form-control:focus {
	outline: 2px solid var(--wp--preset--color--accent);
	outline-offset: 1px;
	border-color: var(--wp--preset--color--accent);
}
.fluentform.ff-inherit-theme-style .ff-el-is-error .ff-el-form-control {
	border-color: var(--wp--preset--color--text);
}
.fluentform.ff-inherit-theme-style .error,
.fluentform.ff-inherit-theme-style .text-danger {
	display: block;
	margin-top: var(--wp--preset--spacing--20);
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--text);
}
/* The submit button as the theme's button (theme.json styles.elements.button). */
.fluentform.ff-inherit-theme-style .ff-btn-submit {
	padding: 0.7rem 1.25rem;
	font: inherit;
	font-size: var(--wp--preset--font-size--medium);
	font-weight: 600;
	line-height: inherit;
	color: var(--wp--preset--color--accent-contrast);
	background: var(--wp--preset--color--accent);
	border: 1px solid var(--wp--preset--color--accent);
	border-radius: 0;
	cursor: pointer;
}
.fluentform.ff-inherit-theme-style .ff-btn-submit:hover,
.fluentform.ff-inherit-theme-style .ff-btn-submit:focus-visible {
	color: var(--wp--preset--color--bg);
	background: var(--wp--preset--color--text);
	border-color: var(--wp--preset--color--text);
}
.fluentform.ff-inherit-theme-style .ff-btn-submit[disabled] {
	opacity: 0.6;
	cursor: wait;
}
.fluentform.ff-inherit-theme-style .ff-message-success,
.fluentform.ff-inherit-theme-style .ff-errors-in-stack {
	padding: var(--wp--preset--spacing--40);
	background: var(--wp--preset--color--surface);
	border: 1px solid var(--wp--preset--color--border);
}

/* Cards, the register card, facts and questions (HB-20 — the Workshop board and Private lessons).
   Every colour a token; borders hairline; corners square like the buttons. */
.hb-card {
	padding: var(--wp--preset--spacing--40);
	background: var(--wp--preset--color--bg);
	border: 1px solid var(--wp--preset--color--border);
}
/* Cards side by side in a Columns block share the row's height. */
.wp-block-column > .hb-card {
	box-sizing: border-box;
	height: 100%;
}
.hb-card__price {
	font-size: var(--wp--preset--font-size--x-large);
	font-weight: 700;
	line-height: 1.1;
}
.hb-chip {
	padding: 0.15em 0.5em;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 700;
	border: 1px solid var(--wp--preset--color--text);
}
/* A form field drawn as layout only — the commerce slice replaces it with the real control. */
.hb-field {
	padding: 0.7rem 0.9rem;
	color: var(--wp--preset--color--text-muted);
	border: 1px solid var(--wp--preset--color--border);
}
/* The register-and-pay card stays in view while the workshop page scrolls (Workshop board, note 1).
   Its column is as tall as the row, so sticky has room; on a phone the columns stack and it flows. */
@media (min-width: 782px) {
	.hb-register {
		position: sticky;
		top: calc(var(--hb-header-height, 0px) + var(--wp--preset--spacing--40)); /* beneath the stuck header (HB-46) */
	}
}
/* Phone: the columns stack, and the card goes first — the one thing a visitor came to do
   sits under the title, above the details and the questions (Mohammad, 2026-09-16). */
@media (max-width: 781px) {
	.hb-workshop > .wp-block-column:last-child {
		order: -1;
	}
}
/* Facts row under a workshop's lead: date, venue, ages, level — hairlines above and below. */
.hb-facts {
	padding-block: var(--wp--preset--spacing--30);
	border-top: 1px solid var(--wp--preset--color--border);
	border-bottom: 1px solid var(--wp--preset--color--border);
	column-gap: var(--wp--preset--spacing--50);
}
/* Person: round portrait beside the name (the board's teacher card, Person pattern). */
.hb-person {
	padding: var(--wp--preset--spacing--40);
	border: 1px solid var(--wp--preset--color--border);
}
.hb-placeholder.hb-placeholder--portrait {
	flex: none;
	width: 7.5rem;
	height: 7.5rem;
	min-height: 0;
	padding: 0.5rem;
	border-radius: 50%;
	overflow: hidden;
	font-size: var(--wp--preset--font-size--small);
}
/* Questions: the core Details block as the board's accordion rows. */
.wp-block-details {
	padding-block: var(--wp--preset--spacing--30);
	border-top: 1px solid var(--wp--preset--color--border);
}
.wp-block-details:last-of-type {
	border-bottom: 1px solid var(--wp--preset--color--border);
}
.wp-block-details > summary {
	font-weight: 600;
	cursor: pointer;
}
.wp-block-details[open] > summary {
	margin-bottom: var(--wp--preset--spacing--30);
}
/* Breadcrumb line above a sub-page's content. */
.hb-crumbs {
	color: var(--wp--preset--color--text-muted);
}

/* Dark page (HB-18; ADR-0006): the "Dark (stage mode)" page template. functions.php puts hb-dark on
   <body> when a page uses it, and every colour token flips right there, so the header, the content, the
   footer, the phone menu overlay and every card and placeholder render in the dark set with no change to
   their markup — the same flip the Dark section style does for one section. The editor canvas gets the
   class from assets/js/editor-dark-template.js (editor styles rewrite body → .editor-styles-wrapper). */
body.hb-dark {
	--wp--preset--color--bg: var(--wp--custom--dark--bg);
	--wp--preset--color--surface: var(--wp--custom--dark--surface);
	--wp--preset--color--text: var(--wp--custom--dark--text);
	--wp--preset--color--text-muted: var(--wp--custom--dark--text-muted);
	--wp--preset--color--accent: var(--wp--custom--dark--accent);
	--wp--preset--color--accent-contrast: var(--wp--custom--dark--accent-contrast);
	--wp--preset--color--border: var(--wp--custom--dark--border);
	--bg: var(--wp--preset--color--bg);
	--surface: var(--wp--preset--color--surface);
	--text: var(--wp--preset--color--text);
	--text-muted: var(--wp--preset--color--text-muted);
	--accent: var(--wp--preset--color--accent);
	--accent-contrast: var(--wp--preset--color--accent-contrast);
	--border: var(--wp--preset--color--border);
	color-scheme: dark;
}

/* A page's hero image placeholder (the Mana Duet board's wide stage photo). */
.hb-placeholder--hero {
	min-height: 28rem;
}

/* Video behind a poster (HB-18; ADR-0007; pattern hauntingbow/video-poster). The poster — the DEMO box,
   or the Image block an editor puts in its place — fills the block at 16:9 with the play button in its
   bottom-left corner; assets/js/video-poster.js swaps in the embed on click. */
.hb-video {
	position: relative;
}
.hb-video--ready {
	cursor: pointer;
}
.hb-video > .hb-video__poster,
.hb-video > .wp-block-image {
	margin: 0;
}
.hb-video > .hb-video__poster {
	aspect-ratio: 16 / 9;
	min-height: 0;
}
.hb-video > .wp-block-image img {
	display: block;
	width: 100%;
	aspect-ratio: 16 / 9;
	object-fit: cover;
}
.hb-video > .hb-video__play {
	position: absolute;
	left: var(--wp--preset--spacing--40);
	bottom: var(--wp--preset--spacing--40);
	margin: 0;
	z-index: 1;
}
/* Phone: the button sits under the poster instead of over it — a two-line label would cover the picture. */
@media (max-width: 599px) {
	.hb-video > .hb-video__play {
		position: static;
		margin-top: var(--wp--preset--spacing--30);
	}
}
.hb-video__play .wp-block-button__link::before {
	content: "";
	display: inline-block;
	width: 0;
	height: 0;
	margin-right: 0.6em;
	vertical-align: -0.05em;
	border-style: solid;
	border-width: 0.4em 0 0.4em 0.65em;
	border-color: transparent transparent transparent currentColor;
}
.hb-video__frame {
	aspect-ratio: 16 / 9;
	background: var(--wp--preset--color--surface);
}
.hb-video__frame iframe {
	display: block;
	width: 100%;
	height: 100%;
	border: 0;
}

/* Press kit (HB-19): downloads. Core's File block — the file's name and the Download button on one
   line, wrapping on a phone — and, in a Gallery with the class hb-press-photos, every photo linked
   to its full-size file as a download (functions.php adds the attribute at render); a small tag in
   the photo's corner says so before anyone hovers. Every colour a token, so the dark page flips them. */
.wp-block-file {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--wp--preset--spacing--20) var(--wp--preset--spacing--30);
	font-size: var(--wp--preset--font-size--small);
}
.wp-block-file > a:not(.wp-block-file__button) {
	color: var(--wp--preset--color--text-muted);
	overflow-wrap: anywhere;
}
.wp-block-file * + .wp-block-file__button {
	margin-left: 0; /* the flex gap spaces it */
}
/* In a row of cards the Download button sits on the same line in every card, whatever the text above it. */
.wp-block-column > .hb-card {
	display: flex;
	flex-direction: column;
}
.hb-card > .wp-block-file:last-child {
	margin-top: auto;
	margin-bottom: 0;
	padding-top: var(--wp--preset--spacing--40);
}
/* Phone: one press photo per row — core's two-up leaves a 2400 × 1600 photo the size of a stamp under its caption. */
@media (max-width: 599px) {
	.hb-press-photos figure.wp-block-image {
		width: 100% !important;
	}
}
/* (::after — core's gallery already owns figure::before for the caption's blur strip.) */
.hb-press-photos figure.wp-block-image::after {
	content: "Download";
	position: absolute;
	top: 0.5rem;
	right: 0.5rem;
	z-index: 1;
	padding: 0.2em 0.55em;
	font-size: 0.7rem;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--preset--color--text);
	background: var(--wp--preset--color--bg);
	border: 1px solid var(--wp--preset--color--border);
	pointer-events: none;
}
.hb-press-photos figure.wp-block-image:hover::after,
.hb-press-photos figure.wp-block-image:focus-within::after {
	color: var(--wp--preset--color--accent-contrast);
	background: var(--wp--preset--color--accent);
	border-color: var(--wp--preset--color--accent);
}

/* Home (HB-21) — the Home board and the Home — phone board, section by section. Every measure is the
   board's (1440 frame) in rem; every colour a token, so the mode shift and the palette flip them. */

/* Hero: the full-bleed picture with the words over its lower band — no box (Mohammad, 2026-09-18, on the
   real photo: "our photos need the full width visible"): a dark gradient rises from the bottom, the words
   take the dark set of tokens, and the button sits beside the lead so the stack stays low and faces in the
   upper two thirds of the picture stay clear. Below 782 px the words drop under the picture in the page's
   own tokens — no text over an image on a phone (Home — phone board). The panel runs edge to edge and pads
   in to the wide edge on both sides, so its heading sits where the header wordmark does (DESIGN.md §8). */
/* The picture's height follows the viewport's height as well as its width (Mohammad, 2026-09-18: taller on
   a laptop, and on an ultra-wide screen a width-only rule left a letterbox of heads): 64 vw on narrow
   windows, the viewport minus the header on wide ones, never under 20 rem — the crop stays near 2:1 from a
   laptop to a 2560 px monitor. The crop favours the upper part of the photo, where faces usually are. */
.hb-hero {
	--hb-hero-height: max(20rem, min(64vw, 100vh - 8rem));
}
/* With the bar floating over it (HB-47; the header block above, keyed to the same condition) the picture fills
   the first screen: the 8 rem the bar used to take goes, and the floor is 40 vh — on a laptop (1440 × 900) the
   picture is the viewport, on a 2560 × 1440 monitor too, on a phone 40 vh (338 px at 390 × 844) rather than 20 rem
   (Mohammad, 2026-09-19: the float applies on the phone as well; 2026-09-22, from four renders: 40 vh — at 60 vh the
   photo's middle half was all that fit and both faces fell outside it). The admin bar, when there is one, still comes off. */
.wp-site-blocks:has(> main > .wp-block-post-content > .hb-hero:first-child > figure.hb-hero__media) .hb-hero {
	--hb-hero-height: max(40vh, min(64vw, 100vh - var(--wp-admin--admin-bar--height, 0px)));
	position: relative;
}
/* On a phone the picture is the whole screen (Mohammad, 2026-09-23, on the live Home: the phone hero reads
   like the desktop one) — this supersedes HB-47's 40 vh floor at this width. 100dvh, not 100vh: iOS Safari
   counts 100vh with the URL bar hidden, so the words would sit below what is on screen. */
@media (max-width: 781px) {
	.wp-site-blocks:has(> main > .wp-block-post-content > .hb-hero:first-child > figure.hb-hero__media) .hb-hero {
		--hb-hero-height: calc(100dvh - var(--wp-admin--admin-bar--height, 0px));
	}
}
/* The soft top gradient the floating bar's words read on, the way the panel's words read at the bottom: black at
   two thirds along the top edge, held through the bar's own height (5 rem), gone by 14 rem — 10 rem on a phone,
   where the picture is shorter. Worst case, a white sky: the dark text token still reads at 4.9:1 anywhere in
   the bar (the home-hero-flush check computes it from these stops). */
.wp-site-blocks:has(> main > .wp-block-post-content > .hb-hero:first-child > figure.hb-hero__media) .hb-hero::before {
	content: "";
	position: absolute; /* out of the grid, over the picture, under the panel (which is z-index 1 and later) */
	inset: 0 0 auto;
	height: 14rem;
	z-index: 1;
	pointer-events: none;
	background: linear-gradient(to bottom, rgba(0, 0, 0, 0.66) 0, rgba(0, 0, 0, 0.6) 5rem, rgba(0, 0, 0, 0) 14rem);
}
@media (max-width: 781px) {
	.wp-site-blocks:has(> main > .wp-block-post-content > .hb-hero:first-child > figure.hb-hero__media) .hb-hero::before {
		height: 10rem;
		background: linear-gradient(to bottom, rgba(0, 0, 0, 0.66) 0, rgba(0, 0, 0, 0.6) 5rem, rgba(0, 0, 0, 0) 10rem);
	}
}
.hb-hero > .hb-hero__media {
	min-height: var(--hb-hero-height);
}
.hb-hero > .hb-hero__panel > .hb-hero__lead {
	max-width: 48rem;
}
/* The words float over the picture's lower band: one grid cell for both, the panel at its end, the dark token
   set on a gradient rising from the bottom edge. Desktop since HB-21; on a phone since 2026-09-23 (Mohammad),
   which supersedes the 2026-09-18 rule that stacked the panel under the picture there. */
.hb-hero {
	display: grid;
}
.hb-hero > .hb-hero__media,
.hb-hero > .hb-hero__panel {
	grid-area: 1 / 1;
}
.hb-hero > .hb-hero__panel {
	/* From the page's edge to the wide edge: the root padding, plus the centring slack on wider screens. */
	--hb-hero-inset: calc(var(--wp--style--root--padding-left) + max(0px, (100% - var(--wp--style--global--wide-size)) / 2));
	/* The dark set, so the words, the eyebrow and the button follow the palette and the button-tokens rule. */
	--wp--preset--color--bg: var(--wp--custom--dark--bg);
	--wp--preset--color--surface: var(--wp--custom--dark--surface);
	--wp--preset--color--text: var(--wp--custom--dark--text);
	--wp--preset--color--text-muted: var(--wp--custom--dark--text-muted);
	--wp--preset--color--accent: var(--wp--custom--dark--accent);
	--wp--preset--color--accent-contrast: var(--wp--custom--dark--accent-contrast);
	--wp--preset--color--border: var(--wp--custom--dark--border);
	position: relative;
	z-index: 1;
	align-self: end;
	box-sizing: border-box;
	max-width: none;
	margin-left: calc(-1 * var(--wp--style--root--padding-left)) !important;
	margin-right: calc(-1 * var(--wp--style--root--padding-right)) !important; /* core's auto margin would shrink-wrap the gradient */
	padding: var(--wp--preset--spacing--70) var(--hb-hero-inset) var(--wp--preset--spacing--50);
	color: var(--wp--preset--color--text);
	background: linear-gradient(to top, rgba(0, 0, 0, 0.82) 0%, rgba(0, 0, 0, 0.62) 45%, rgba(0, 0, 0, 0) 100%);
}
.hb-hero > .hb-hero__panel > * {
	margin-block: 0;
}
@media (min-width: 782px) {
	.hb-hero > .hb-placeholder.hb-hero__media {
		align-items: start;
		padding-top: var(--wp--preset--spacing--50);
	}
	/* Desktop: the heading on the wide edge, the button beside the lead, so the stack stays low. */
	.hb-hero > .hb-hero__panel {
		display: grid;
		grid-template-columns: minmax(0, 56.25rem) auto;
		grid-template-areas: "heading ." "lead button";
		column-gap: var(--wp--preset--spacing--60);
		row-gap: var(--wp--preset--spacing--40);
		align-items: end;
	}
	.hb-hero > .hb-hero__panel > h1 {
		grid-area: heading;
	}
	.hb-hero > .hb-hero__panel > .hb-hero__lead {
		grid-area: lead;
	}
	.hb-hero > .hb-hero__panel > .wp-block-buttons {
		grid-area: button;
		justify-self: end;
	}
}
/* Phone: the same panel, stacked — heading, lead, button — and the button on the start edge. */
@media (max-width: 781px) {
	.hb-hero > .hb-hero__panel {
		display: flex;
		flex-direction: column;
		gap: var(--wp--preset--spacing--40);
		padding-top: var(--wp--preset--spacing--60);
		padding-bottom: var(--wp--preset--spacing--40);
	}
	.hb-hero > .hb-hero__panel > .wp-block-buttons {
		justify-content: flex-start;
	}
}

/* A real photo in a slot is an Image block carrying the slot's class (the recipe writes it so; an editor
   adds the class under Advanced when swapping a box) — it fills the same frame as the placeholder. */
.hb-hero > figure.hb-hero__media,
figure.hb-strand__media {
	margin-block: 0;
}
.hb-hero > figure.hb-hero__media img {
	display: block;
	width: 100%;
	height: var(--hb-hero-height);
	object-fit: cover;
	object-position: 50% 40%;
}

/* Home's hero loop (HB-67, ADR-0019 / ADR-0020): the theme's one silent <video> in the hero's frame, over its own
   first frame as the still (functions.php puts that frame in the Image block's place — Mohammad at review,
   2026-09-22: Home opens on the loop's first frame, never on the stage photo). Both take the cover crop centred:
   the loop is 1920 × 854, wider than any frame the hero takes, so the crop is always off the sides and the centre
   stays in frame at every width; the same object-position on the still and the video makes them one picture, so
   the loop appears to stand still and then move. The still underneath is what shows before the loop starts, while
   it is paused and under reduced motion. The markup is functions.php's (hauntingbow_hero_loop), the states
   hero-loop.js's: hb-hero__media--js once the script runs (the loop then fades in on is-playing — no blank frame
   over the still), is-paused hides the loop on the still. The gradient the bar's words read on (::before, z-index
   1) and the panel (z-index 1, later) paint over the loop as they paint over the photo. */
.hb-hero > figure.hb-hero__media--loop {
	position: relative;
}
.hb-hero > figure.hb-hero__media--loop > img.hb-hero__still {
	object-position: 50% 50%;
}
.hb-hero > figure.hb-hero__media--loop > .hb-hero__loop {
	position: absolute;
	inset: 0;
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: 50% 50%;
	transition: opacity 600ms ease, visibility 0s;
}
.hb-hero > figure.hb-hero__media--js > .hb-hero__loop {
	opacity: 0;
}
.hb-hero > figure.hb-hero__media--js.is-playing > .hb-hero__loop {
	opacity: 1;
}
.hb-hero > figure.hb-hero__media--loop.is-paused > .hb-hero__loop {
	opacity: 0;
	visibility: hidden; /* the still is the hero now: nothing of the loop for a pointer or a reader to find */
	transition: opacity 300ms ease, visibility 0s 300ms;
}
/* Held from the first paint: functions.php's head script puts hb-hero-held on <html> when this tab's session says
   paused, before the video is parsed — a warm-cache autoplay can start before the deferred script runs, and without
   this a paused visitor could see a few frames move. hero-loop.js takes the class off on Play. */
:root.hb-hero-held .hb-hero > figure.hb-hero__media--loop > .hb-hero__loop {
	opacity: 0;
	visibility: hidden;
	transition: none;
}
/* The pause control (WCAG 2.2.2): one 44 px square in the dark set over the picture, under the resting bar and on
   the wide edge — the same edge the bar's Tickets and the panel's heading sit on — above the top gradient. The
   icon flips with the state (pause while the loop runs, play while it is paused); hidden until the script has a
   playing loop to pause, so a browser without scripts, or one that blocks autoplay, shows the photo and no dead
   control. Hover and focus flip to the dark set's accent, as the theme's buttons do. */
.hb-hero > figure.hb-hero__media--loop > .hb-hero__pause {
	position: absolute;
	z-index: 2;
	top: calc(5rem + var(--wp--preset--spacing--30)); /* the bar at rest is 5 rem tall */
	/* The wide edge, from the figure's own edge (the figure is full-bleed: 100% is the viewport, so the root padding
	   is not added on top of the centring slack — the panel's inset does that from inside the padded group). */
	right: max(var(--wp--style--root--padding-right), (100% - var(--wp--style--global--wide-size)) / 2);
	box-sizing: border-box;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 2.75rem;
	height: 2.75rem;
	padding: 0;
	margin: 0;
	border: 1px solid var(--wp--custom--dark--border);
	border-radius: 0;
	background: var(--wp--custom--dark--bg);
	color: var(--wp--custom--dark--text);
	cursor: pointer;
	transition: background-color 200ms ease, color 200ms ease, border-color 200ms ease;
}
.hb-hero > figure.hb-hero__media--loop > .hb-hero__pause[hidden] {
	display: none;
}
.hb-hero > figure.hb-hero__media--loop > .hb-hero__pause:hover,
.hb-hero > figure.hb-hero__media--loop > .hb-hero__pause:focus-visible {
	background: var(--wp--custom--dark--accent);
	color: var(--wp--custom--dark--accent-contrast);
	border-color: var(--wp--custom--dark--accent);
}
.hb-hero > figure.hb-hero__media--loop > .hb-hero__pause:focus-visible {
	outline: 2px solid var(--wp--custom--dark--accent);
	outline-offset: 2px;
}
.hb-hero__pause-icon {
	display: block;
}
.hb-hero__pause-icon--play,
.is-paused > .hb-hero__pause > .hb-hero__pause-icon--pause {
	display: none;
}
.is-paused > .hb-hero__pause > .hb-hero__pause-icon--play {
	display: block;
}
/* Reduced motion: the loop has no source (functions.php gates each source on the same query) — it is not laid out
   at all, and there is nothing to pause. The still — the loop's first frame — is the hero. */
@media (prefers-reduced-motion: reduce) {
	.hb-hero > figure.hb-hero__media--loop > .hb-hero__loop,
	.hb-hero > figure.hb-hero__media--loop > .hb-hero__pause {
		display: none;
	}
}
/* Home's Listen · 30 s (HB-43, DESIGN.md §8): the editors' outline button beside Upcoming events, the clip in the
   media library. functions.php puts a play and a stop icon in the link, listen.js swaps them (is-playing on the
   button) and writes --hb-listen-progress (0–1) as the clip plays. The ring is a 2 px outline 4 px outside the
   button, in the dark set's border tone, filling clockwise from the top in the accent — a progress ring, not a
   waveform: the Navigation board's own words ("a thin ring around the button fills as it plays"), and a drawn
   waveform would need the file decoded in the page for no more than this says. Nothing moves but the fill. */
.hb-listen > .hb-listen__link {
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center; /* listen.js holds the resting width; Stop sits centred in it */
	gap: 0.5rem;
	box-sizing: border-box;
}
.hb-listen__icon {
	display: block;
	flex: none;
}
.hb-listen__icon--stop,
.hb-listen.is-playing .hb-listen__icon--play {
	display: none;
}
.hb-listen.is-playing .hb-listen__icon--stop {
	display: block;
}
.hb-listen > .hb-listen__link::after {
	content: "";
	position: absolute;
	inset: -6px; /* the ring's 2 px, 4 px clear of the button's own border */
	padding: 2px;
	pointer-events: none;
	background: conic-gradient(var(--wp--preset--color--accent) calc(var(--hb-listen-progress, 0) * 1turn), var(--wp--preset--color--border) 0);
	-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
	-webkit-mask-composite: xor;
	mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
	opacity: 0;
}
.hb-listen.is-playing > .hb-listen__link::after {
	opacity: 1;
}
.hb-listen > .hb-listen__audio {
	display: none;
}

/* Home's Mana Duet band (HB-69, DESIGN.md §8): a core Cover of the stage photo, the editors' to swap and to set the focal point of,
   full width. Where the window is wide enough to show both faces at the viewport's height — at least 600 px and landscape past
   6:5 — the photo fills the screen and the words float at its bottom on a dark fade, as the hero's do; anywhere narrower or
   taller (a phone, a portrait tablet: at the viewport's height the crop would lose both players off the sides) the photo shows
   at its own shape, full width, and the words sit under it on the band's dark. The Played at line follows the Cover, inside
   the band. The Cover's own overlay stays at 0 so the photo is untouched; the fade is the panel's. */
.wp-block-cover.hb-band__cover {
	/* From the page's edge to the wide edge, as the hero's panel: the root padding, plus the centring slack on wider screens —
	   the slack of the wide column inside the padded box, not of the whole width (HB-70: that counted the padding twice and put
	   the words 64 px in from the wordmark at 1440). */
	--hb-band-inset: calc(var(--wp--style--root--padding-left) + max(0px, (100% - var(--wp--style--root--padding-left) - var(--wp--style--root--padding-right) - var(--wp--style--global--wide-size)) / 2));
	display: block;
	min-height: 0;
	padding: 0;
}
.wp-block-cover.hb-band__cover > img.wp-block-cover__image-background {
	position: static;
	display: block;
	width: 100%;
	height: auto;
}
.wp-block-cover.hb-band__cover > .wp-block-cover__background {
	display: none;
}
.wp-block-cover.hb-band__cover > .wp-block-cover__inner-container {
	box-sizing: border-box;
	width: 100% !important; /* core's .has-custom-content-position rule (four classes) sets auto, which shrinks the fade to the words */
	max-width: none;
	padding: var(--wp--preset--spacing--50) var(--hb-band-inset) 0;
	color: var(--wp--preset--color--text);
}
.wp-block-cover.hb-band__cover > .wp-block-cover__inner-container > * {
	margin-block: 0 var(--wp--preset--spacing--20);
}
.wp-block-cover.hb-band__cover > .wp-block-cover__inner-container > .wp-block-buttons {
	margin-block-start: var(--wp--preset--spacing--30);
}
/* The lead is the band's words under its heading: the block gap above it where the words stack (HB-26). */
.wp-block-cover.hb-band__cover > .wp-block-cover__inner-container > .hb-band__lead {
	margin-block-start: var(--wp--preset--spacing--40);
}
@media (min-width: 600px) and (min-aspect-ratio: 6/5) {
	.wp-block-cover.hb-band__cover {
		/* The fade's clear top before the first line of words: every line sits where the fade is at least 0.76 black, so the dark
		   set's muted text reads at 5.2:1 and its saffron at 5.0:1 even over the mural's brightest paint (HB-12; the band-photo
		   check computes it from these stops). */
		--hb-band-fade: 6rem;
		display: flex;
		align-items: flex-end;
		min-height: calc(100vh - var(--wp-admin--admin-bar--height, 0px));
	}
	.wp-block-cover.hb-band__cover > img.wp-block-cover__image-background {
		position: absolute;
		inset: 0;
		height: 100%;
		object-fit: cover;
	}
	.wp-block-cover.hb-band__cover > .wp-block-cover__inner-container {
		padding: var(--hb-band-fade) var(--hb-band-inset) var(--wp--preset--spacing--50);
		background: linear-gradient(to top, rgba(0, 0, 0, 0.85) 0, rgba(0, 0, 0, 0.76) calc(100% - var(--hb-band-fade)), rgba(0, 0, 0, 0) 100%);
	}
}
/* Wide enough for two columns: the name on the wide edge, the lead and the buttons beside it, so the stack stays low and the
   players' faces in the photo's middle band stay clear. */
@media (min-width: 900px) and (min-aspect-ratio: 6/5) {
	.wp-block-cover.hb-band__cover > .wp-block-cover__inner-container {
		display: grid;
		grid-template-columns: minmax(0, 1fr) minmax(0, 32rem);
		column-gap: var(--wp--preset--spacing--60);
		row-gap: var(--wp--preset--spacing--20);
		align-items: end;
	}
	.wp-block-cover.hb-band__cover > .wp-block-cover__inner-container > * {
		grid-column: 1;
		margin: 0;
	}
	.wp-block-cover.hb-band__cover > .wp-block-cover__inner-container > .hb-band__lead {
		margin: 0;
		grid-column: 2;
		grid-row: 1 / span 3;
	}
	.wp-block-cover.hb-band__cover > .wp-block-cover__inner-container > .wp-block-buttons {
		grid-column: 2;
		grid-row: 4;
		margin: 0;
	}
}
/* The band opening a page (HB-70, Performance): the same Cover first in the page's first section, so it starts at the page's top
   edge with the bar floating over it (the header rules above, keyed to the same markup; header.js watches the Cover). Its words —
   the page's H1 among them — stay where the band puts them; what it adds is the soft top gradient the bar's words read on, the
   hero's own stops (the dark text token at 4.9:1 anywhere in the bar over a white wall), and on a phone, where the photo shows
   at its own shape and is only ~260 px tall, a shorter one that is gone by 7 rem so the players' faces stay clear of it. */
.wp-site-blocks > main > .wp-block-post-content > .hb-section:first-child > .wp-block-cover.hb-band__cover:first-child::before {
	content: "";
	position: absolute; /* over the photo, under the words' panel (z-index 1, later in the Cover) */
	inset: 0 0 auto;
	height: 14rem;
	z-index: 1;
	pointer-events: none;
	background: linear-gradient(to bottom, rgba(0, 0, 0, 0.66) 0, rgba(0, 0, 0, 0.6) 5rem, rgba(0, 0, 0, 0) 14rem);
}
@media not ((min-width: 600px) and (min-aspect-ratio: 6/5)) {
	.wp-site-blocks > main > .wp-block-post-content > .hb-section:first-child > .wp-block-cover.hb-band__cover:first-child::before {
		height: 7rem;
		background: linear-gradient(to bottom, rgba(0, 0, 0, 0.66) 0, rgba(0, 0, 0, 0.6) 4rem, rgba(0, 0, 0, 0) 7rem);
	}
}
/* Learning's hero (HB-100): the band opening a page, with the page's name, a one-line lead and the student line bottom left, and
   the jump links to its sections as outline buttons bottom right (the Cover carries hb-band--jumps). The words keep the band's
   dark set: the page is light, so the hero's section is a Dark section with 0 bottom padding (no dark strip under the photo). */
.wp-block-cover.hb-band__cover > .wp-block-cover__inner-container > .hb-student-line {
	font-size: var(--wp--preset--font-size--small);
}
.hb-student-line a {
	color: inherit;
	text-underline-offset: 0.2em;
}
.wp-block-cover.hb-band__cover > .wp-block-cover__inner-container > .wp-block-buttons.hb-band__jumps {
	margin-block-start: var(--wp--preset--spacing--30);
}
@media (min-width: 900px) and (min-aspect-ratio: 6/5) {
	.wp-block-cover.hb-band__cover.hb-band--jumps > .wp-block-cover__inner-container > .hb-band__lead {
		grid-column: 1;
		grid-row: auto;
		margin-block-start: calc(var(--wp--preset--spacing--40) - var(--wp--preset--spacing--20)); /* under the heading: the row gap and this make the block gap (HB-26) */
	}
	.wp-block-cover.hb-band__cover.hb-band--jumps > .wp-block-cover__inner-container > .wp-block-buttons.hb-band__jumps {
		grid-column: 2;
		grid-row: 1 / span 3;
		align-self: end;
		justify-content: flex-end;
		margin: 0;
	}
}
figure.hb-strand__media img {
	display: block;
	width: 100%;
	height: 100%;
	aspect-ratio: 8 / 5; /* the board's 418 × 260 */
	object-fit: cover;
	object-position: center top;
}

/* Strands: the board's 260 px pictures on desktop. */
@media (min-width: 782px) {
	.hb-strands .hb-strand__media {
		min-height: 16.25rem;
	}
}
/* Strands on a phone: the three cards become rows — a small square picture, the name and the line,
   hairlines between (Home — phone board). The picture's caption goes; the DEMO tag stays. */
@media (max-width: 781px) {
	.hb-strands > .wp-block-column {
		display: grid;
		grid-template-columns: 4rem minmax(0, 1fr);
		column-gap: var(--wp--preset--spacing--30);
		row-gap: var(--wp--preset--spacing--20);
		align-items: center;
		padding-block: var(--wp--preset--spacing--30);
		border-bottom: 1px solid var(--wp--preset--color--border);
	}
	.hb-strands > .wp-block-column:first-child {
		border-top: 1px solid var(--wp--preset--color--border);
	}
	.hb-strands > .wp-block-column > * {
		margin-block: 0;
	}
	.hb-strands > .wp-block-column > .hb-strand__media {
		grid-row: 1 / 4;
		width: 4rem;
		height: 4rem;
		min-height: 0;
		padding: 0;
	}
	.hb-strands > .wp-block-column > .hb-strand__media > p {
		display: none;
	}
	.hb-strands > .wp-block-column > figure.hb-strand__media img {
		aspect-ratio: auto;
	}
	.hb-strands > .wp-block-column > .hb-strand__media::after {
		top: 0.25rem;
		left: 0.25rem;
		font-size: 0.55rem;
	}
}

/* The gallery teaser's six tiles (200 px); a memory card's optional picture (160 px). */
.hb-placeholder--tile {
	min-height: 12.5rem;
}
.hb-placeholder--thumb {
	min-height: 10rem;
}
/* Six tiles across on desktop (the block's own grid); three on a tablet, two on a phone. */
@media (max-width: 781px) {
	.hb-section .hb-gallery-teaser {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}
@media (max-width: 599px) {
	.hb-section .hb-gallery-teaser {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}
.hb-memories__intro {
	max-width: 40rem;
}

/* Newsletter band: the copy (680 px) beside the field and Subscribe; the field is layout only until
   HB-15 connects a provider. On a phone everything stacks and the field and button run full width. */
.hb-signup {
	align-items: flex-start !important; /* the real form is many lines tall: the heading levels with its first field (HB-26) */
}
.hb-signup__copy {
	flex: 1 1 20rem;
	max-width: 42.5rem;
}
/* A fixed basis, or the note line under the field would inflate the form's intrinsic width past the
   row (680 + field + button + note > 1312 px) and push the field under the copy. */
.hb-signup__form {
	flex: 0 1 32rem;
}
.hb-signup__form .hb-field {
	min-width: 21.25rem;
	margin: 0;
	background: var(--wp--preset--color--bg);
}
.hb-signup__form > .hb-signup__note {
	flex-basis: 100%;
	margin: 0;
}
@media (max-width: 599px) {
	.hb-signup__form,
	.hb-signup__form .hb-field,
	.hb-signup__form .wp-block-buttons,
	.hb-signup__form .wp-block-button,
	.hb-signup__form .wp-block-button__link {
		width: 100%;
		min-width: 0;
		box-sizing: border-box;
	}
	.hb-signup__form .wp-block-button__link {
		text-align: center;
	}
}

/* Product page (HB-33): the words on the wide edge, the picture beside them (DESIGN.md §8 alignment
   rule). A product without a photo shows no picture at all — Woo's "awaiting product image"
   placeholder is noise for a visitor; the editor still sees it in the admin as the cue to add one. */
.hb-product .woocommerce-product-gallery__image--placeholder {
	display: none !important; /* Woo's gallery styles load after ours and win on specificity */
}
.hb-product .wc-block-components-product-price,
.hb-product .wp-block-woocommerce-product-price {
	margin-bottom: var(--wp--preset--spacing--30);
}

/* Video behind a title card (HB-44; pattern hauntingbow/video-card): the poster slot of a clip that is not ours
   to picture — a card in the page's own colours with the player's name and the piece. The border is drawn here,
   not through the block's border support: the palette slug "border" makes core's .has-border-color class carry
   color: var(--wp--preset--color--border), which would paint the card's words in the border grey. */
.hb-video > .hb-video__card {
	border: 1px solid var(--wp--preset--color--border);
	color: var(--wp--preset--color--text);
}

/* Hero reveal and fade on scroll (HB-173, DESIGN.md §8 "Motion"): the headline's words of a page that opens on a hero
   fade up once, 80 ms apart; a photo hero (the stage Cover) drifts slowly for 20 s, Home's loop is left as it is; event
   rows and cards fade in once they are properly on the screen, 60 ms apart, once, moving 18 px along the ray from the
   middle of the screen; on the two-way pages (Home and the storytelling pages) every piece of content fades in and out both ways (motion.js says when; hb-out-below and
   hb-out-above are its two out states, by the side a piece is out on). Everything here hangs on hb-motion, which the head's flag sets only when motion.js can undo it
   (functions.php, hauntingbow_motion_flag): without it, under reduced motion and in print nothing is held back. Only
   opacity, translate and scale move, so nothing shifts the layout, and the drift is clipped to the Cover. No parallax,
   no scroll-jacking (the board's Not doing list). */
@media screen and (prefers-reduced-motion: no-preference) {
	.hb-motion:not(.hb-motion-ready) :is(.hb-event-row, .hb-card, .hb-album-card),
	.hb-motion .wp-site-blocks > main > .wp-block-post-content > .hb-hero:first-child h1:not(.hb-words),
	.hb-motion .wp-site-blocks > main > .wp-block-post-content > .hb-section:first-child > .wp-block-cover.hb-band__cover:first-child h1:not(.hb-words) {
		opacity: 0;
	}
	.hb-motion .hb-m {
		transition: opacity 600ms cubic-bezier(0.2, 0.7, 0.2, 1), translate 600ms cubic-bezier(0.2, 0.7, 0.2, 1);
		transition-delay: calc(var(--hb-i, 0) * 60ms + var(--hb-wait, 0ms)); /* --hb-wait: the opening's lead and buttons */
	}
	.hb-motion.hb-motion-init .hb-m,
	.hb-motion .hb-m.hb-still {
		transition: none;
	}
	/* Out, by the side a piece is out on: along its ray from the middle of the screen (motion.js writes the two lengths). */
	.hb-motion .hb-m:is(.hb-out-below, .hb-out-above) {
		opacity: 0;
		translate: var(--hb-dx, 0) var(--hb-dy, 12px);
	}
	/* A piece at the edge, out along a slanted ray, must not widen the page: clip, not hidden, so nothing becomes a
	   scroll container and the sticky bar is untouched. */
	.hb-motion .wp-site-blocks > main {
		overflow-x: clip;
	}
	.hb-motion .hb-words .hb-word {
		display: inline-block;
		animation: hb-rise-word 700ms cubic-bezier(0.2, 0.7, 0.2, 1) calc(var(--hb-i, 0) * 80ms) both;
	}
	.hb-motion .wp-site-blocks > main > .wp-block-post-content > .hb-section:first-child > .wp-block-cover.hb-band__cover:first-child {
		overflow: clip;
	}
	.hb-motion .wp-site-blocks > main > .wp-block-post-content > .hb-section:first-child > .wp-block-cover.hb-band__cover:first-child > img.wp-block-cover__image-background {
		animation: hb-drift 20s ease-out both;
	}
}
@keyframes hb-rise-word {
	from {
		opacity: 0;
		translate: 0 0.3em;
	}
}
@keyframes hb-drift {
	from {
		scale: 1;
	}
	to {
		scale: 1.06;
	}
}
