/*
Theme Name: Bactronix
Theme URI: https://bactronixsc.com/
Author: Secret Squirrel Marketing
Author URI: https://secretsquirrel.marketing/
Description: Full site editing block theme for Bactronix of the Lowcountry (HVAC mold treatment and air duct cleaning, Charleston SC). Six colour tokens, one self hosted OFL variable family (Archivo, width as the display axis) shipped as WOFF2 so no third party font CDN is contacted, square corners, no shadows, no gradients. All design tokens live in theme.json. The theme carries no business facts; phone numbers, addresses and service copy come from the companion plugin.
Requires at least: 6.5
Tested up to: 6.8
Requires PHP: 8.0
Version: 0.3.1
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: bactronix-theme
Tags: full-site-editing, block-patterns, block-styles, custom-colors, custom-menu, editor-style, one-column, wide-blocks
*/

/*
  SOURCE OF THE LOOK
  ------------------
  The 2026-09-03 redesign (docs/redesign, plan A and plan B). The first port
  of this theme copied the live Divi site's look; the redesign replaced that
  with six colour tokens, one family, square corners and no elevation, so the
  "cited" and "measured" tiers the earlier sections used no longer apply to
  sections 1 to 5. Later sections still carry their measurements where they
  were taken on the running page; THEME-NOTES.md records the judgement calls.

  CLASS NAMING
  ------------
  Every class authored here is bx-*. Not one Divi builder class name survives
  the port; the shipped CSS, PHP, JS and JSON contain zero of them. The bx-*
  prefix continues the convention already used by the site's existing custom
  snippet (.bx-bar-btn, #bx-lead), whose styles are ported in section 8.

  PALETTE (theme.json is the source of truth; six tokens and no seventh)
    white     #FFFFFF    the page
    ink       #2E3439    every word
    red       #BB2B1E    structure and action only: rules, underlines, the
                         primary button, the focus ring. Never a section ground.
    red press #8E1F15    the primary button under the pointer
    steel     #5F6870    meta: dates, bylines, captions, sources
    hairline  #D9DCDF    1px borders and table rules

  SHAPE
    Every radius is 0. No box shadow anywhere. No gradient anywhere. Capitals
    only on the board, the stamp and H1s; buttons and labels are set in
    sentence case with no letter spacing.

  No em dashes anywhere per house style. No price is typed into this file.
*/

/* ------------------------------------------------------------------ *
 * 1 . TOKEN INDIRECTION
 * theme.json emits every preset as --wp--preset--*; the short --bx-* names
 * below alias them, with the same literal as the fallback, so a rule reads
 * one short name and a token changes in one place. --bx-ff is the one
 * family. Width is Archivo's display axis: the board and H1 run condensed,
 * headings a little less so, body at normal width.
 * ------------------------------------------------------------------ */

:root {
	--bx-ff: var(--wp--preset--font-family--bx-archivo, 'Archivo', 'Archivo Fallback', Arial, sans-serif);
	--bx-white: var(--wp--preset--color--bx-white, #FFFFFF);
	--bx-ink: var(--wp--preset--color--bx-ink, #2E3439);
	--bx-red: var(--wp--preset--color--bx-red, #BB2B1E);
	--bx-red-press: var(--wp--preset--color--bx-red-press, #8E1F15);
	--bx-steel: var(--wp--preset--color--bx-steel, #5F6870);
	--bx-hairline: var(--wp--preset--color--bx-hairline, #D9DCDF);
	--bx-focus: var(--wp--custom--focus--ring, #BB2B1E);
	--bx-section-pad: clamp(3rem, 5vw, 4.5rem);
	--bx-rule-w: var(--wp--custom--rule--width, 51px);
	--bx-rule-h: var(--wp--custom--rule--height, 4px);
	--bx-nav-height: var(--wp--custom--nav--height, 64px);
	/* Width is the display axis. Body is normal width. */
	--bx-w-board: 75%;
	--bx-w-display: 78%;
	--bx-w-heading: 82%;
	--bx-w-body: 100%;
}

/* ------------------------------------------------------------------ *
 * 2 . BASE
 * ------------------------------------------------------------------ */

/* Size adjusted fallback so the swap from a system face to Archivo does not
   shift the board. 'Archivo Fallback' sits second in the bx-archivo
   fontFamily string in theme.json (the value WordPress actually emits as
   --wp--preset--font-family--bx-archivo), not merely in a var() fallback
   argument here, so the browser paints this face for real during the swap
   window rather than skipping straight to a generic system sans. Metrics
   tuned against Arial Narrow, so the fallback line box is close to
   Archivo's own. */
@font-face {
	font-family: 'Archivo Fallback';
	src: local('Arial Narrow'), local('ArialNarrow'), local('Arial');
	size-adjust: 92%;
	ascent-override: 92%;
	descent-override: 24%;
}

body {
	font-family: var(--bx-ff);
	font-stretch: var(--bx-w-body);
	font-weight: 400;
	color: var(--bx-ink);
	background: var(--bx-white);
	line-height: 1.55;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	text-rendering: optimizeLegibility;
}

/* A measure. Prose inside a .bx-body column stops at 68 characters so a
   wide screen does not stretch a paragraph into a line nobody can track.

   margin-inline is pinned to 0 because core's constrained layout emits
   `.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull))
   { margin-left: auto !important; margin-right: auto !important }`, and a
   68ch paragraph is narrower than the 1080px column, so without this every
   paragraph on the site sat centred, about 215px right of the H2 above it at
   1440 (whole-branch review 2026-09-04, finding 1). Two class-and-element
   selectors at (0,1,1) beat core's :where() rule at (0,1,0) whatever the
   load order. */
.bx-body p,
.bx-body li {
	max-width: 68ch;
	margin-inline: 0 !important;
}

h1, h2, h3, h4, h5, h6 {
	font-family: var(--bx-ff);
	font-stretch: var(--bx-w-heading);
	font-weight: 700;
	line-height: 1.1;
	letter-spacing: 0;
	text-transform: none;
	margin-block-end: 0.625rem;
}

/* .wp-block-post-title is listed with h1 because theme.json's core/post-title
   block style generates a class rule that outranks a bare element selector;
   the page title is the H1 that matters most, so it takes the same width,
   weight and line height here as well as in theme.json. */
h1,
.wp-block-post-title {
	font-stretch: var(--bx-w-display);
	font-weight: 800;
	line-height: 1.0;
}

.wp-block-post-content ul,
.wp-block-post-content ol,
.entry-content ul,
.entry-content ol {
	padding-inline-start: 1.25em;
}

.wp-block-post-content ul,
.entry-content ul {
	list-style-type: disc;
}

.wp-block-post-content ol,
.entry-content ol {
	list-style-type: decimal;
}

.wp-block-post-content li,
.entry-content li {
	margin-block-end: 0.35em;
}

/* Tables: a hairline frame, hairline rules, no header fill. */
.wp-block-table table {
	width: 100%;
	border: 1px solid var(--bx-hairline);
	border-collapse: collapse;
	text-align: left;
}

.wp-block-table th {
	color: var(--bx-ink);
	font-weight: 700;
	padding: 9px 24px;
	background: transparent;
	border: 0;
	border-top: 1px solid var(--bx-hairline);
}

.wp-block-table td {
	border: 0;
	border-top: 1px solid var(--bx-hairline);
	padding: 6px 24px;
}

.wp-block-table figcaption {
	color: var(--bx-steel);
}

/* TABLES ON A PHONE (final fix wave, 2026-09-04). The fresh-eyes review
   found the offer page's "What moved" table and the cost page's price table
   cut off mid-word at 390 ("Up $250" off the edge; "Clean a", "antimic",
   "Charle"), scrolling sideways with nothing saying so. This section used
   to give every core table a 480px floor under 600px, which forced that
   scroll. Now: cells wrap, the type drops to bx-sm under 480px with tighter
   cell padding so the columns fit, and every table the converter emits
   (core and the plugin's comparison table alike) sits in a .bx-table-wrap
   that scrolls only when the columns still cannot fit. When it does, the
   theme's table-scroll.js marks the wrap is-scrollable, fades the right
   edge to white until the end is reached, and adds a screen-reader note
   that the table scrolls sideways. The inner figure's own overflow is
   opened so the wrap is the one scroller and can measure itself. */
.bx-body table th,
.bx-body table td {
	white-space: normal;
}

.bx-body .bx-table-wrap {
	overflow-x: auto;
	max-width: 100%;
	margin-block: var(--wp--preset--spacing--bx-md, 1.5rem);
}

.bx-body .bx-table-wrap > .wp-block-table,
.bx-body .bx-table-wrap > .bx-comparison-table {
	overflow: visible;
	margin-block: 0;
}

/* The fade: a mask on the scroller, so no shadow and nothing red. Gone once
   the visitor has scrolled to the end (is-at-end) or when nothing overflows. */
.bx-body .bx-table-wrap.is-scrollable:not(.is-at-end) {
	-webkit-mask-image: linear-gradient(to right, #000 calc(100% - 3rem), transparent);
	mask-image: linear-gradient(to right, #000 calc(100% - 3rem), transparent);
}

/* One class heavier than section 11's comparison-table rules (which come
   later in this file at (0,3,0) and (0,3,1)), so the phone size wins. */
@media (max-width: 480px) {
	:root .bx-body .wp-block-table table,
	:root .bx-body .bx-comparison-table .bx-ctable {
		font-size: var(--wp--preset--font-size--bx-sm, 0.875rem);
	}

	:root .bx-body .wp-block-table th,
	:root .bx-body .wp-block-table td,
	:root .bx-body .bx-comparison-table .bx-ctable th,
	:root .bx-body .bx-comparison-table .bx-ctable td {
		padding: 0.5rem 0.625rem;
	}
}

/* Links are ink with a red underline. The red is the only thing that marks a
   link, so it is 2px and sits a little under the baseline where it reads as
   a rule rather than a strike. Buttons are excluded: they carry their own
   ground and no underline. */
a:where(:not(.wp-element-button, .wp-block-button__link)) {
	color: var(--bx-ink);
	text-decoration: underline;
	text-decoration-color: var(--bx-red);
	text-decoration-thickness: 2px;
	text-underline-offset: 0.15em;
}

/* Citation anchors. tools/md-to-wxr writes one of these for every
   `[source: https://...]` token a week's blog post carries, so a stated
   figure links the EPA, CDC, USFA or NOAA page it came from. It is furniture
   inside a sentence: the same link treatment as any other, one size step
   down. */
.bx-source {
	font-size: var(--wp--preset--font-size--bx-sm, 0.875rem);
}

/* THE FOCUS RING, once. --bx-focus is theme.json's settings.custom.focus.ring
   through section 1, so the ring has one source. Written with :where() on
   purpose: a theme's default ring should lose to anything a component
   declares deliberately. Section 9 carries the one place it has to win. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
	outline: 2px solid var(--bx-focus);
	outline-offset: 2px;
}

/* ------------------------------------------------------------------ *
 * 3 . SECTIONS
 * Every section ground is the page: white. There is no red band and no
 * light grey band. Sections are separated by their padding and, where a
 * heading opens one, by the short red rule above it.
 * ------------------------------------------------------------------ */

.bx-section {
	padding-block: var(--bx-section-pad);
}

/* The short red rule above a heading: 51px by 4px, left aligned, one line
   above the text. Usable on a heading block directly (the "Red rule above"
   block style) or on any wrapper. */
.bx-rule::before,
.is-style-bx-ruled::before {
	content: "";
	display: block;
	width: var(--bx-rule-w);
	height: var(--bx-rule-h);
	background: var(--bx-red);
	margin-inline: 0;
	margin-block-end: 1.75rem;
}

/* ------------------------------------------------------------------ *
 * 4 . CARDS
 * A card is a white box with a hairline around it and nothing else: no
 * radius, no elevation. Plan B replaces the card rail with a list, so this
 * is the whole of the treatment rather than the start of one.
 * ------------------------------------------------------------------ */

.bx-card,
.is-style-bx-card {
	background: var(--bx-white);
	border: 1px solid var(--bx-hairline);
	border-radius: 0;
	padding: var(--wp--preset--spacing--bx-md, 1.5rem);
}

/* ------------------------------------------------------------------ *
 * 5 . BUTTONS
 * Two treatments and no third. Primary is the red fill with white text, for
 * the one action a section asks for. Secondary is an ink outline on white,
 * for the rest. Both are square, both at least 48px tall on every pointer,
 * both set in the display width of the one family, sentence case, no letter
 * spacing. The block styles are registered in functions.php; .bx-btn is the
 * same treatment for markup the companion plugin renders outside a block.
 * ------------------------------------------------------------------ */

.wp-block-button__link,
.wp-element-button,
.bx-btn {
	font-family: var(--bx-ff);
	font-stretch: var(--bx-w-display);
	font-weight: 700;
	font-size: var(--wp--preset--font-size--bx-body);
	line-height: 1.2;
	letter-spacing: 0;
	text-transform: none;
	text-decoration: none;
	border-radius: 0;
	min-height: 48px;
	padding: 12px 20px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.wp-block-button.is-style-bx-primary .wp-block-button__link,
.bx-btn--primary {
	background: var(--bx-red);
	color: var(--bx-white);
	border: 2px solid var(--bx-red);
}

.wp-block-button.is-style-bx-primary .wp-block-button__link:hover,
.wp-block-button.is-style-bx-primary .wp-block-button__link:active,
.bx-btn--primary:hover,
.bx-btn--primary:active {
	background: var(--bx-red-press);
	border-color: var(--bx-red-press);
}

.wp-block-button.is-style-bx-secondary .wp-block-button__link,
.bx-btn--secondary {
	background: var(--bx-white);
	color: var(--bx-ink);
	border: 2px solid var(--bx-ink);
}

.wp-block-button.is-style-bx-secondary .wp-block-button__link:hover,
.bx-btn--secondary:hover {
	background: var(--bx-ink);
	color: var(--bx-white);
}

@media (prefers-reduced-motion: reduce) {
	.wp-block-button__link, .wp-element-button, .bx-btn { transition: none; }
}

/* ------------------------------------------------------------------ *
 * 6 . HEADER AND NAVIGATION ADAPTERS
 * Templates and parts land in a later task. These are the measured chrome
 * values so the header part inherits them rather than restating numbers.
 * ------------------------------------------------------------------ */

.bx-header {
	background: var(--bx-white);
	min-height: var(--bx-nav-height);
}

/* Nav links, measured on the rendered desktop header on 2026-09-02: 23.8px
   tall at padding 0, which is 0.2px under the 24px minimum target size WCAG
   2.2 SC 2.5.8 asks for. Missing it by a fifth of a pixel is still missing it,
   and it costs six pixels of padding to clear. inline-flex so the padding
   actually grows the target box rather than only the ink, and a min-height so
   a shorter font never walks it back under the line. Measured after: 36px. */
.bx-header .wp-block-navigation a:where(:not(.wp-element-button)) {
	text-decoration: none;
	display: inline-flex;
	align-items: center;
	min-height: 24px;
	padding-block: 0.375rem;
}

/* AND THE 24px OF DEAD SPACE THE HEADER WAS HIDING.
   The nav sits inside a <details> whose <summary> is display:none above the
   breakpoint, and WordPress gives the nav a 24px block-gap margin above it
   anyway, spacing it from a toggle nobody can see. The header's measured 79px
   min-height was absorbing that: the header content box is 79 minus 32px of
   padding, 47px, and the nav plus its dead margin came to 47.8px, so the bar
   sat exactly on its minimum and the slack was invisible.

   Growing the links to a legal target size pushed that stack to 60px and the
   bar to 93px, which is not a trade the port has to make. Dropping the margin
   where the summary is hidden returns the header to its measured 79px with the
   taller links inside it. Below the breakpoint the summary IS visible and the
   panel drops under the bar, where .bx-navwrap[open] already supplies its own
   padding-block-start, so this is scoped above it and touches neither. */
@media (min-width: 981px) {
	.bx-navwrap > .wp-block-navigation {
		margin-block-start: 0;
	}
}

/* The nav is a native details element, server rendered open, so every link is
   reachable with JavaScript off. assets/js/nav.js collapses it below 980px,
   which is the reference site's own tablet breakpoint. Above that the toggle
   is hidden and the menu simply sits in the bar.

   The element is the core details block (parts/header.html), whose summary
   carries no class of its own, so the toggle is addressed structurally as
   well as by class. */
.bx-navtoggle,
.bx-navwrap > summary {
	display: none;
	cursor: pointer;
	list-style: none;
	font-family: var(--bx-ff);
	font-size: 0.875rem;
	font-weight: 600;
	color: var(--bx-ink);
	padding: 0.5rem 0;
	min-height: 44px;
	align-items: center;
}

.bx-navtoggle::-webkit-details-marker,
.bx-navwrap > summary::-webkit-details-marker {
	display: none;
}

@media (max-width: 980px) {
	.bx-navtoggle,
	.bx-navwrap > summary {
		display: flex;
	}
}

/* The current page and the hovered link take the site's one link mark: ink
   text with a 2px red underline. Red text is for the phone number only. The
   base nav rule above sets text-decoration: none at (0,2,1); these are
   (0,3,1) and (0,3,0), so the underline wins without an !important. */
.bx-header .wp-block-navigation .current-menu-item a,
.bx-header .wp-block-navigation [aria-current="page"],
.bx-header .wp-block-navigation a:where(:not(.wp-element-button)):hover {
	color: var(--bx-ink);
	text-decoration: underline;
	text-decoration-color: var(--bx-red);
	text-decoration-thickness: 2px;
	text-underline-offset: 0.15em;
}

/* ------------------------------------------------------------------ *
 * 7 . ABOVE THE FOLD CALLS TO ACTION
 *
 * THE MEASUREMENT THIS SECTION EXISTS FOR, taken on the running dev host on
 * 2026-09-02 at 1280x900, 1440x900 and 1920x900:
 *
 *   .bx-hero                 0 links, 0 buttons
 *   .bx-header               0 tel: links, position relative
 *   first tel: link on /     y=1012 at 1280, y=1017 at 1440 and 1920
 *   fold                     900px
 *
 * So a desktop visitor could not call without scrolling, on a site whose one
 * job is to make a landline ring. Phones were already covered: BX_Callbar's
 * sticky bar is gated to 980px and below, which is why the defect was only
 * ever on desktop.
 *
 * The markup is BX_Call_Actions (companion plugin), because a phone number is
 * a business fact and this theme carries none. The theme owns only how it
 * looks, which is the same split the footer's NAP block already uses.
 *
 * TARGET SIZE, HEADER. Council review on Task 2 (2026-09-03) moved the header
 * action off a pill onto red condensed text; .bx-call-actions--header below
 * still guarantees 44px through padding and min-height on the link itself, on
 * every pointer type, not only a coarse one, so the change of shape cost
 * nothing here.
 *
 * THE HERO PAIR WAS UNSTYLED, ON PURPOSE, THROUGH TASK 6. This section used
 * to carry the pill rules for both placements together: a shared uppercase
 * 13px pill for .bx-call-actions__call and .bx-call-actions__price, plus an
 * override for the old dimmed cover photo with its own contrast arithmetic
 * (white on brand red #BB2B1E at 6.04:1, the secondary pill a SOLID white
 * ground rather than an outline on the photograph, because #CA564D, the
 * reference site's secondary coral, is only 4.24:1). Task 3 owned the header
 * half of that only; Task 7 removed the hero placement from the two
 * templates that bound it, and the cover photo those templates opened on
 * with it (section 12, now gone), so the board carries the actions instead
 * and the hero pair's pill rules were dropped here rather than carried
 * forward.
 * ------------------------------------------------------------------ */

.bx-call-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--wp--preset--spacing--bx-xs, 0.75rem);
}

/* The header row is space-between, which with three children (brand, nav,
   phone) would park the nav in the middle of the bar, which is not where it
   was. An auto inline-start margin on the nav absorbs the free space instead,
   so the nav and the phone stay together on the right and the brand stays on
   the left, exactly as measured before. */
.bx-header__inner > .bx-navwrap {
	margin-inline-start: auto;
}

/* THE PHONE, AS TEXT. Council review on Task 2 (2026-09-03) moved the header
 * call to action off the pill this section used to draw: red condensed 800
 * weight text, no background, no border, no uppercase and no letter spacing,
 * which is what a phone number reads as rather than what a button does. The
 * verb keeps its own weight so "Call" and the digits are not the same grey
 * mass; the whole link keeps its 44px tap target through padding, not a
 * filled box.
 *
 * Hidden at 980px and below, where BX_Callbar's sticky bar owns the same job:
 * one breakpoint, two components, no width where both or neither shows.
 */
.bx-call-actions--header .bx-call-actions__call {
	font-family: var(--bx-ff);
	font-stretch: var(--bx-w-display);
	font-weight: 800;
	font-size: 1.25rem;
	color: var(--bx-red);
	text-decoration: none;
	background: none;
	border: 0;
	padding: 0.5rem 0;
	min-height: 44px;
	display: inline-flex;
	align-items: center;
}

.bx-call-actions--header .bx-call-actions__call:hover {
	color: var(--bx-red-press);
	text-decoration: underline;
	text-decoration-thickness: 2px;
}

.bx-call-actions--header .bx-call-actions__verb {
	font-weight: 700;
	margin-right: 0.25em;
}

/* THE PHONE HEADER (final fix wave, 2026-09-04). The fresh-eyes review found
   the mobile header carrying no phone (the spec: mark, phone, Menu) and the
   open menu dropping the mark to mid-height beside a right-aligned column.
   Below 981px the bar now reads mark, phone, Menu: the phone is the same
   call-actions block the desktop bar carries, no longer hidden here (the
   sticky call bar at the foot of the screen still has its own). The verb is
   kept for a screen reader and taken off the screen: at 320px there is no
   room for "Call" between the mark and Menu, and the red digits are the
   phone. When the menu opens the details takes the whole row under the
   mark, its summary stays pinned where it sat at the top right, the links
   stack flush left, and the phone follows them as the last item. Two
   classes on the inner row, because core's generated flex class sets
   flex-wrap: nowrap at (0,1,0). */
@media (max-width: 980px) {
	.bx-header .bx-header__inner {
		position: relative;
		flex-wrap: wrap;
		align-items: center;
		gap: 0 var(--wp--preset--spacing--bx-xs, 0.75rem);
	}

	.bx-header__inner > .bx-brand {
		order: 0;
		min-height: 44px;
	}

	.bx-header__inner > .bx-call-actions--header {
		display: flex;
		order: 1;
		margin-inline-start: auto;
	}

	.bx-header__inner > .bx-navwrap {
		order: 2;
		margin-inline-start: 0;
	}

	/* The phone and the Menu keep their 44px targets through min-height alone
	   here: with their desktop block padding on top they made the bar 60px
	   tall beside a 44px mark. */
	.bx-call-actions--header .bx-call-actions__call {
		font-size: var(--wp--preset--font-size--bx-body, 1.0625rem);
		padding-block: 0;
	}

	.bx-navwrap > summary {
		padding-block: 0;
	}

	.bx-call-actions--header .bx-call-actions__verb {
		position: absolute;
		width: 1px;
		height: 1px;
		margin: -1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}

	.bx-navwrap[open] {
		flex-basis: 100%;
	}

	.bx-navwrap[open] > summary {
		position: absolute;
		top: 0;
		right: 0;
	}

	.bx-navwrap[open] ~ .bx-call-actions--header {
		order: 3;
		flex-basis: 100%;
		margin-inline-start: 0;
		margin-block-start: var(--wp--preset--spacing--bx-xs, 0.75rem);
	}

	.bx-brand__line {
		display: none;
	}
}

/* At 320px the bar has 256px inside the page padding: the 118px mark, the
   phone and Menu with two gaps came to 273 and Menu wrapped under the mark
   (measured on the dev host, 2026-09-04). A 34px mark (100px wide), the
   phone at bx-sm and half-gaps come to about 230. Targets stay 44px. */
@media (max-width: 359px) {
	.bx-header .bx-header__inner {
		gap: 0 0.5rem;
	}

	/* Three classes: the 40px mark rule further down the file is (0,2,1). */
	.bx-header .bx-brand .wp-block-site-logo img {
		height: 34px;
	}

	.bx-call-actions--header .bx-call-actions__call {
		font-size: var(--wp--preset--font-size--bx-sm, 0.875rem);
	}
}

/* ------------------------------------------------------------------ *
 * 8 . EXISTING CUSTOM COMPONENTS, PORTED VERBATIM
 * The live site already carries a hand written lead form and a sticky
 * mobile CTA bar under bx-* class names (home.css lines 75 to 83). Their
 * markup will be emitted by the companion plugin, so the theme owns only
 * how they look. Values below are cited, not redrawn, except that the
 * Libre Franklin reference resolves to the theme's one shipped family,
 * Archivo, the corners are square, the pressed red is bx-red-press and the
 * bar's shadow is a hairline (2026-09-03, no shadow anywhere).
 * ------------------------------------------------------------------ */

.bx-bar {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 99998;
	display: flex;
	gap: 10px;
	padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
	background: var(--bx-white);
	border-top: 1px solid var(--bx-hairline);
}

/* The same values as the plugin's assets/css/callbar.css, which is the sheet
   that has to work when this theme is not active. This copy loads after it
   and wins ties, so the two are kept identical on purpose. */
.bx-bar-btn {
	flex: 1;
	text-align: center;
	padding: 14px 10px;
	border-radius: 0;
	font-family: var(--bx-ff);
	font-stretch: var(--bx-w-display);
	font-size: 15px;
	font-weight: 700;
	letter-spacing: 0;
	text-transform: none;
	text-decoration: none;
	border: 2px solid var(--bx-red);
}

.bx-bar-primary {
	background: var(--bx-red);
	border-color: var(--bx-red);
	color: var(--bx-white);
}

.bx-bar-secondary {
	background: var(--bx-white);
	border-color: var(--bx-ink);
	color: var(--bx-ink);
}

.bx-lead {
	max-width: 560px;
	margin: 18px 0 0;
	padding: 24px;
	border: 1px solid var(--bx-hairline);
	border-radius: 0;
	background: var(--bx-white);
	font-family: var(--bx-ff);
	text-align: left;
}

.bx-lead h3 {
	margin: 0 0 6px;
	font-family: var(--bx-ff);
	font-size: 1.25rem;
	font-weight: 600;
	color: var(--bx-ink);
}

.bx-lead p {
	margin: 0 0 14px;
	font-size: 0.9375rem;
	color: var(--bx-ink);
	line-height: 1.5;
}

.bx-lead label {
	display: block;
	font-size: 0.875rem;
	font-weight: 600;
	color: var(--bx-ink);
	margin: 10px 0 4px;
}

.bx-lead input[type="text"],
.bx-lead input[type="tel"],
.bx-lead input[type="email"],
.bx-lead input[type="number"] {
	width: 100%;
	padding: 10px 12px;
	border: 1px solid var(--bx-hairline);
	border-radius: 0;
	font-size: 1rem;
	color: var(--bx-ink);
	background: var(--bx-white);
	box-sizing: border-box;
}

.bx-lead button {
	width: 100%;
	margin-top: 14px;
	padding: 14px;
	border: 2px solid var(--bx-red);
	border-radius: 0;
	background: var(--bx-red);
	color: var(--bx-white);
	font-family: var(--bx-ff);
	font-stretch: var(--bx-w-display);
	font-size: var(--wp--preset--font-size--bx-body, 1.0625rem);
	font-weight: 700;
	letter-spacing: 0;
	text-transform: none;
	cursor: pointer;
}

.bx-lead button:hover,
.bx-lead button:focus {
	background: var(--bx-red-press);
	border-color: var(--bx-red-press);
}

.bx-lead .bx-hp {
	position: absolute;
	left: -5000px;
	top: -5000px;
}

/* The sent-state box: white, hairline framed, the red rule on its left
   edge, the same card treatment as the answer-first block. It used to sit
   on #F9F9F9, a seventh colour outside the six tokens. */
.bx-lead-ok {
	margin-top: 10px;
	padding: 14px;
	border: 1px solid var(--bx-hairline);
	border-left: 4px solid var(--bx-red);
	background: var(--bx-white);
	color: var(--bx-ink);
	font-size: 0.9375rem;
}

/* ------------------------------------------------------------------ *
 * 9 . ACCESSIBILITY BASE
 * Kept from the house block theme baseline. None of this alters the ported
 * visual; it only makes the port usable by keyboard and by readers who ask
 * for less motion.
 * ------------------------------------------------------------------ */

/* The baseline focus ring lives in section 2 and reads --bx-focus, which is
   theme.json's settings.custom.focus.ring through section 1, so the ring has
   one source. The white ring for a red band went with the band. */

/* THE RING THAT HAS TO WIN.
   The baseline in section 2 is written with :where(), which contributes nothing to
   specificity on purpose: a theme's default focus ring should lose to anything
   a component declares deliberately. It also loses to core's blanket
   suppressions, which are not deliberate component decisions at all:

     .wp-block-navigation__responsive-close:focus { outline: none }

   ships in wp-includes/blocks/navigation/style.min.css at (0,2,0) and beats the
   baseline's (0,1,0), so a keyboard user closing the overlay menu gets no ring.
   This header renders the nav as a <details>/<summary> disclosure
   (overlayMenu:"never"), so core's responsive container is not on the page
   today and that defect is latent rather than live; it becomes live the day
   anybody switches the nav to an overlay. The summary IS this theme's real
   close control, so it is listed here too rather than left depending on a
   zero-specificity rule nothing is stopping the next stylesheet from beating.

   Class-plus-pseudo-class specificity, not !important: this needs to outrank
   core's suppression, not everything forever. */
.bx-navwrap > summary:focus-visible,
.bx-navtoggle:focus-visible,
.wp-block-navigation__responsive-close:focus-visible,
.wp-block-navigation__responsive-container-close:focus-visible,
.wp-block-navigation__responsive-container-open:focus-visible {
	outline: 2px solid var(--bx-focus);
	outline-offset: 2px;
}

.screen-reader-text {
	border: 0;
	clip-path: inset(50%);
	height: 1px;
	margin: -1px;
	overflow: hidden;
	padding: 0;
	position: absolute;
	width: 1px;
	word-wrap: normal !important;
}

/* ONE SKIP LINK, NOT TWO.
   This theme used to render its own `.bx-skip-link` in parts/header.html, and
   WordPress renders `#wp-skip-link` for every block template. Both read "Skip
   to content", both pointed at #bx-main, and both sat at the top of the tab
   order, so every keyboard user Tabbed the same link twice on every page
   (found 2026-09-02).

   The theme's copy was the one removed. Core's is server rendered, comes first
   in the DOM, and derives its own target rather than hardcoding an anchor a
   template rename would break. What the theme keeps is the LOOK: this rule
   dresses core's link in brand red instead of core's grey. `#wp-skip-link` is
   an id, so it outranks core's own `.skip-link.screen-reader-text:focus`
   without an !important.

   White on #BB2B1E is 6.04:1. The .bx-skip-link selector stays listed so a
   theme or plugin that renders one still gets the same treatment. */
#wp-skip-link:focus,
.bx-skip-link:focus {
	position: fixed;
	top: 0.5rem;
	left: 0.5rem;
	z-index: 100000;
	padding: 0.75rem 1rem;
	background: var(--bx-red);
	color: var(--bx-white);
	clip-path: none;
	height: auto;
	width: auto;
	margin: 0;
}

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

/* ------------------------------------------------------------------ *
 * 10 . TEMPLATE AND PART CHROME
 * The classes the templates, parts and patterns in this theme author.
 * Nothing here carries a business fact; the header and the footer columns
 * render their words from block content, from site settings, or from a
 * companion plugin block.
 * ------------------------------------------------------------------ */

/* FULL BLEED, AND WHY IT NEEDS NO ARITHMETIC HERE. Under
   useRootPaddingAwareAlignments WordPress puts the vertical root padding on
   .wp-site-blocks but the HORIZONTAL root padding on each .has-global-padding
   block, not on the container. The header and footer bands are constrained
   groups, so they carry .has-global-padding themselves: they already run the
   full width of the viewport with their content inset by exactly the root
   gutter. A negative margin here would push them past the viewport edge and
   give the page a horizontal scrollbar, so there is none. Verified against
   the generated stylesheet on a rendered page rather than assumed. */

/* SIDEWAYS OVERFLOW IS CLIPPED AT THE ROOT (redesign B, Task 2, fix round 1,
   2026-09-04). The price board's stamp flies in from just off the viewport's
   right edge, 3.2x its size, on the press that sets a price; clip, not
   hidden, so this is not a scroll container and the vertical axis stays
   visible. Measured: scrollWidth never exceeds innerWidth during the flight
   at 390 or 1440 (tools/design/board-drive.mjs). Nothing else on the site
   reaches past the viewport's sides. */
.wp-site-blocks {
	overflow-x: clip;
}

/* Header bar. Static, white, a hairline the only edge (council C3, Task 3:
   the header does not stick). Background and min-height are section 6's; a
   header a later task styles inherits them rather than restating numbers. */
.bx-header {
	padding-block: var(--wp--preset--spacing--bx-xs, 0.75rem);
	border-bottom: 1px solid var(--bx-hairline);
}

.bx-header__inner {
	gap: var(--wp--preset--spacing--bx-md, 1.5rem);
}

.bx-brand {
	gap: var(--wp--preset--spacing--bx-xs, 0.75rem);
}

.bx-brand .wp-block-site-logo img {
	height: 40px;
	width: auto;
}

/* The mark carries the name (site-title is gone from the header part), and
   this line is the sub-line under it. Hidden below 980px alongside the phone,
   where the mobile bar already states who the site is. */
.bx-brand__line {
	margin: 0;
	font-size: var(--wp--preset--font-size--bx-sm);
	color: var(--bx-steel);
	white-space: nowrap;
}

/* THE FOOTER, INK ON WHITE (Task 4). Council B5 held that red is structure,
   a button, the phone, an underline, not a ground a whole section gets
   painted in, so the footer band and the CTA band it used to stack with are
   both gone. The brand mark is the same logo.png the header already
   renders: council B8 retired the reversed asset Task 3 shipped for it,
   because the red ground it was reversed for no longer exists (see
   bx_render_footer_brand() in functions.php for the contrast arithmetic,
   before and after). */
.bx-footer {
	border-top: 1px solid var(--bx-hairline);
	padding-block: var(--bx-section-pad) 2rem;
	color: var(--bx-ink);
}

.bx-footer__h {
	font-size: var(--wp--preset--font-size--bx-lg);
	margin: 0 0 0.75rem;
}

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

/* Each footer link is a 44px target (the project floor; the rows measured
   about 24px before, whole-branch review 2026-09-04, minor 13). The target
   grows, not the gap: the list item margin goes to 0 so six links do not
   stretch the column. */
.bx-footer__links li {
	margin: 0;
}

.bx-footer__links a,
.bx-footer__legal-links a {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
}

/* The NAP block's phone and email links were the only 14px-tall targets on
   the site (qa-loop, 2026-09-04: 89x14 and 159x14 at every width, legal by
   the SC 2.5.8 spacing exception and nothing else). One of them is the phone
   number. Same rule as the link list above. */
.bx-footer .bx-nap__phone a,
.bx-footer .bx-nap__email a {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
}

/* The legal links were separated by one collapsed space ("Privacy
   Accessibility", same review, minor 12). */
.bx-footer__legal-links a + a {
	margin-inline-start: 1.5rem;
}

.bx-footer__legal {
	border-top: 1px solid var(--bx-hairline);
	margin-top: 2rem;
	padding-top: 1rem;
	font-size: var(--wp--preset--font-size--bx-sm);
	color: var(--bx-steel);
}

.bx-footer__brand-line {
	margin: 0.5rem 0 0;
}

@media (max-width: 781px) {
	.bx-footer__cols {
		flex-direction: column;
	}
}

.bx-footer__mark {
	margin-block-end: var(--wp--preset--spacing--bx-sm, 1rem);
}

.bx-footer__mark-link {
	display: inline-block;
	line-height: 0;
}

/* Dimensions are on the <img> as attributes too (118x40, the asset's own
   1212x411 scaled to a 40px height: 1212 * 40 / 411 = 117.96, rounded), so
   the box is reserved before the file loads and the footer cannot shift
   under it. */
.bx-footer__mark-img {
	width: 118px;
	height: auto;
	max-width: 100%;
}

.bx-header .wp-block-navigation {
	gap: var(--wp--preset--spacing--bx-md, 1.5rem);
}

/* THE NARROW-DESKTOP SQUEEZE IS GONE (Task 3). It existed to stop a 137px red
 * pill wrapping the header onto a second row between 981 and 1279px. With the
 * pill gone (the phone is text, in section 7) and six short nav labels
 * instead of seven, the row has less to fit and no override is needed.
 */

/* Below the nav breakpoint the disclosure panel drops under the bar rather
   than squeezing the logo: the details spans the row (section 7's phone
   header rules), and the nav inside it starts at the left edge. Core's
   generated layout class justifies this nav to the right for the desktop
   bar; the open panel overrides it, or the column of links sat against the
   right edge under a left-aligned mark (fresh-eyes, 2026-09-04). */
@media (max-width: 980px) {
	.bx-navwrap[open] .wp-block-navigation {
		flex-basis: 100%;
		width: 100%;
		justify-content: flex-start;
		padding-block-start: var(--wp--preset--spacing--bx-sm, 1rem);
	}

	.bx-navwrap[open] .wp-block-navigation ul {
		flex-direction: column;
		align-items: flex-start;
		gap: var(--wp--preset--spacing--bx-xs, 0.75rem);
	}

	/* In the disclosure menu every link is a thumb target: 24px min-height
	   plus 10px of padding each side is 44px (section 6 keeps the desktop
	   bar at 36px for the header height it was measured at). */
	.bx-header .wp-block-navigation a:where(:not(.wp-element-button)) {
		padding-block: 0.625rem;
	}
}

/* Page shell. Each template stacks .bx-section groups inside main, so the
   heading block and the body block would otherwise contribute a section pad
   each at the seam between them. The head drops its bottom pad and the body
   keeps its top pad, which leaves exactly one section gap. */
.bx-page__head {
	padding-block-end: 0;
}

/* The front page and the $50-a-vent hub (redesign Task 7): the opening
   section holding the H1, the price board and the trust line. Replaces the
   dimmed cover photo those two templates used to open on. */
.bx-open {
	padding-block: 1.5rem var(--bx-section-pad);
}

.bx-open__title {
	margin: 0 0 1rem;
	text-transform: uppercase;
	font-stretch: var(--bx-w-display);
	font-weight: 800;
	line-height: 1.0;
	max-width: 22ch;
}

/* The title's left edge is the board's left edge. .bx-open is a constrained
   group, so core centres every child narrower than the column with
   `margin-left: auto !important` (the same rule the .bx-body measure pins
   against, section 2). The board fills the column; the 22ch title does not,
   so at 1440 it sat indented against the board (controller, 2026-09-04).
   Two classes at (0,2,0) beat core's :where() at (0,1,0). */
.bx-open .bx-open__title {
	margin-inline: 0 !important;
}

/* At 320 to 390 the caps H1 ran five or six lines. theme.json's bx-display
   preset floors at 1.75rem, but its middle term (1.2rem + 4.6vw) is still
   above the floor at 390, and WordPress prints the preset class
   `.has-bx-display-font-size` with !important, so the floor is pinned here
   with the same weight below 400px. */
@media (max-width: 400px) {
	.bx-open .bx-open__title {
		font-size: 1.75rem !important;
	}
}

.bx-open__trust {
	margin: 0.75rem 0 0;
	color: var(--bx-steel);
	font-size: var(--wp--preset--font-size--bx-sm);
}

/* The $50-a-vent hub, four sections under the board (redesign Task 8).
   The brief wrote these four facts as bold-led paragraphs inside a
   <div class="bx-rows"> wrapper, but chunkBody() in
   tools/md-to-wxr/lib/convert-lib.mjs only passes <form> through as raw
   HTML (the `t.startsWith('<form')` branch); any other raw HTML, a bare
   <div> included, falls into the generic paragraph parser and comes out
   escaped as literal text. So the markdown carries four plain paragraphs,
   each led by **a bold fact**, and this rule finds them by position: the
   four <p> elements immediately following this page's first H2 (the
   converter auto-anchors every H2 as ch-N, and ours is the first one in
   the body, so it is always ch-1).

   Both selector lists are scoped to .page-template-page-hub (this page is
   the only one on that template) AND to the #ch-1 sibling chain, not a
   bare ".bx-body p > strong:first-child" rule. That looser pattern, a bold
   lead run followed by more prose in the same paragraph, is how this
   corpus writes FAQ answers and feature call-outs across most of the
   other 42 content files, and it is also this very page's own trust-bar
   paragraph ("Marine veteran owned ... posted price") and its four
   "How do you actually treat mold" steps further down. An unscoped rule
   would repaint all of those in red too, well past "no red beyond the
   .bx-rows left rule." */
.page-template-page-hub #ch-1 + p,
.page-template-page-hub #ch-1 + p + p,
.page-template-page-hub #ch-1 + p + p + p,
.page-template-page-hub #ch-1 + p + p + p + p {
	border-left: 4px solid var(--bx-red);
	padding-left: 1rem;
	margin: 0 0 1rem;
}

.page-template-page-hub #ch-1 + p > strong:first-child,
.page-template-page-hub #ch-1 + p + p > strong:first-child,
.page-template-page-hub #ch-1 + p + p + p > strong:first-child,
.page-template-page-hub #ch-1 + p + p + p + p > strong:first-child {
	display: block;
	font-stretch: var(--bx-w-heading);
	font-weight: 800;
	text-transform: uppercase;
	font-size: var(--wp--preset--font-size--bx-lg);
}

/* The comparison table's last row is ours ("Bactronix, 12 vents", the
   number that did not move), and it is the one row weighted. The converter
   turns a plain-text Markdown table into the plugin's comparison-table
   block (.bx-comparison-table), and this page carries two of them, so the
   rule is found the same way the four rows above are: by position after
   the seventh H2 (ch-7, "What moved, and what does not"), one intro
   paragraph, then the figure. It was ch-3 until the 2026-09-04 reorder
   put the cost, method, cleaning and safety sections ahead of it; the
   cost section (now ch-3) opens with a paragraph and the price table, so
   a stale ch-3 here would weight that table's last row, a competitor's
   figure, which is the exact bug this comment already records once.
   tools/content/tests/offer-page.test.mjs pins the whole H2 sequence.
   An earlier, unscoped `.bx-body tr:last-child td` also weighted the
   last row of the price table further down this page (a competitor's
   figure) and of every table on five other pages (whole-branch review
   2026-09-04, minor 5). The generic `.bx-body table/th/td` rules that
   shipped with it are gone too: every plain table the converter emits is
   a .wp-block-table, which section 2 already styles, and the comparison
   table is section 11's.
   Since the final fix wave (2026-09-04) the converter puts every table in
   a .bx-table-wrap group (section 2, the phone scroller), so the figure is
   the wrap's child and the wrap is the paragraph's sibling. */
.page-template-page-hub #ch-7 + p + .bx-table-wrap .bx-comparison-table tbody tr:last-child td {
	font-weight: 700;
}

/* A body figure (the van photo included) as a full-width block with a
   steel, small caption. Not scoped to one page: nothing here uses red, and
   every page's figures were unstyled before this. */
.bx-body figure img {
	width: 100%;
	height: auto;
	display: block;
}

.bx-body figcaption {
	color: var(--bx-steel);
	font-size: var(--wp--preset--font-size--bx-sm);
	margin-top: 0.5rem;
	text-align: left;
	max-width: 68ch;
}

.bx-page__head .wp-block-post-title {
	margin-block: 0 var(--wp--preset--spacing--bx-sm, 1rem);
}

/* The inner page H1 on the question, service and location templates (the
   three that carry the bx-page__title class, redesign B, Task 3) in the
   display size, width, weight and measure, flush left: the type the two
   board pages' .bx-open__title runs, WITHOUT its caps. Fix round 1
   (2026-09-04): the first cut put this on the shared head group above and
   uppercased it, so every template's H1 (page, contact, book, the guides
   hub) went to caps; the ruling keeps caps on home and the offer page only,
   their own rule. The margin-inline pin is the same one .bx-open needs:
   core centres a child narrower than the constrained column. */
.bx-page__head .bx-page__title {
	margin-inline: 0 !important;
	font-stretch: var(--bx-w-display);
	font-weight: 800;
	line-height: 1.0;
	max-width: 22ch;
}

@media (max-width: 400px) {
	.bx-page__head .bx-page__title {
		font-size: 1.75rem !important;
	}
}

/* The contact page's NAP section renders a companion plugin block. Until
   that block exists the section would be a heading over nothing, so it
   collapses when the block contributes no element of its own. The footer's
   own NAP column carries no such wrapper class (it sits under its own
   "Call or write" heading in parts/footer.html), so this is contact-page
   only now. */
.bx-contact-nap:not(:has(p, ul, ol, address, div, table)) {
	display: none;
}

/* The grids (patterns/service-grid.php, patterns/location-grid.php): flat
   cards on a white ground, with the columns collapsing to one on a phone. */
.bx-grid__cols {
	gap: var(--wp--preset--spacing--bx-lg, 2rem);
}

.bx-grid .wp-block-column {
	min-width: 0;
	display: flex;
	flex-direction: column;
}

.bx-grid .wp-block-buttons {
	margin-block-start: auto;
	padding-block-start: var(--wp--preset--spacing--bx-sm, 1rem);
}

/* The cross link rail (patterns/cross-link-rail.php), the last thing on an
   inner page before the footer: a hairline above, a plain heading, then a
   list with a hairline between entries. One link and one steel line each,
   no cards, no buttons, no red (redesign B, Task 3: the council ruled the
   page end is plain links, and the rail's old three cards with a red rule
   and a "Learn more" button each were the page's second and third cards).
   The list is a list for a screen reader too: the bullet is removed with
   padding, never with a display change. */
.bx-rail {
	border-top: 1px solid var(--bx-hairline);
}

.bx-rail__h {
	font-size: var(--wp--preset--font-size--bx-xl, 1.375rem);
	margin-block: 0 var(--wp--preset--spacing--bx-sm, 1rem);
}

.bx-rail__back {
	margin: 0 0 var(--wp--preset--spacing--bx-sm, 1rem);
}

/* Links that stand alone in a list item or a line (the cross-link lists on
   the offer page and the town and guides hubs, the way back to the price)
   measured 15px tall on phones and 18px at 1440 (qa-loop, 2026-09-04): legal
   under SC 2.5.8 only by the spacing exception. The first fix made the list
   links inline-block with padding, and that put the bullet beside the SECOND
   line whenever a link wrapped at 390 (fresh-eyes, 2026-09-04): an
   inline-block's baseline is its last line's, so the marker sat on line two.
   Final fix wave: the list link is inline again, with the marker outside the
   text, so a wrapped line hangs under the first word rather than beside the
   bullet, and the ROW carries the rhythm through its line height (2 on a
   17px body is a 34px line box). The block padding stays on the inline link
   on purpose: an inline box ignores line-height for its own rect (measured
   19px at 390 with line-height alone, legal only by the spacing exception),
   while block padding on an inline element grows the hit box without moving
   a line, so the link's rect is about 29px and clears the 24px law by size.
   The way back to the price is one line and keeps its padded block box. */
.bx-body .entry-content li a {
	display: inline;
	padding-block: 0.3rem;
}

.bx-body li {
	list-style-position: outside;
}

/* The doubled line height goes to the rows that ARE links (the item opens
   with one: the cross-link lists, the town lists, the guides hub, home's
   services), not to prose bullets, where 34px leading on three-line items
   read as a page of air (seen on the cost page's numbered list at 390 when
   this was on every li). A browser without :has() keeps the body leading
   and the padded link box. */
.bx-body li:has(> a:first-child) {
	line-height: 2;
}

.bx-rail__back a {
	display: inline-block;
	padding-block: 0.3rem;
}

/* Two classes at (0,2,0) beat core's constrained-layout centring at (0,1,0)
   for a list narrower than the column (section 2 pins body prose the same
   way): the list starts where the heading starts. */
.bx-rail .bx-rail__list {
	list-style: none;
	padding-inline-start: 0;
	margin: 0;
	margin-inline: 0 !important;
	max-width: 68ch;
}

.bx-rail__list li {
	margin: 0;
	padding-block: var(--wp--preset--spacing--bx-xs, 0.75rem);
	border-top: 1px solid var(--bx-hairline);
}

.bx-rail__list li:last-child {
	border-bottom: 1px solid var(--bx-hairline);
}

.bx-rail__list a {
	font-weight: 700;
}

.bx-rail__line {
	display: block;
	color: var(--bx-steel);
	font-size: var(--wp--preset--font-size--bx-sm, 0.875rem);
	margin-top: 0.15rem;
}

/* Local proof and the map (redesign B, Task 5; council round 2, Local 1
   and 3). One section under the body on the service and town templates,
   holding the plugin's Google proof line (bx-brandos/google-proof, a <p>)
   and, on the town template, the service-area map (bx-brandos/service-map,
   a <figure>). Both blocks read every figure from the identity and render
   nothing when it carries none, so the section collapses when it is empty
   rather than leaving two section pads of white above the rail. The body
   section above already carries the bottom pad, so this one drops its top
   pad and the seam is one gap, the same rule the page head follows.

   THE PROOF LINE IS THIS SECTION'S ONE RED RULE: body type, ink, at the
   body measure, with the 4px red rule on its left, the treatment the offer
   page's invoice rows and the short answer carry. It is not a card: no
   frame, no ground, no radius. Two classes at (0,2,0) beat core's
   constrained-layout centring at (0,1,0), the same pin section 2 puts on
   body prose.

   ON HOME THE LINE IS PLAIN. There the same block renders inside the body
   via the [bx_google_proof] shortcode, directly above the two quotes, and
   the quote block already carries the theme's red left rule: with a rule
   of its own the proof read as a third quote (measured on the dev host,
   2026-09-04), which is the one thing the attribution must never do. So
   inside .bx-body it keeps the measure and the ink and carries no rule;
   the section's red rules are the quotes'. */
.bx-local {
	padding-block-start: 0;
}

.bx-local:not(:has(.bx-proof, .bx-map)) {
	display: none;
}

.bx-local .bx-proof,
.bx-body .bx-proof {
	max-width: 68ch;
	margin-inline: 0 !important;
	color: var(--bx-ink);
	font-size: var(--wp--preset--font-size--bx-body, 1.0625rem);
	line-height: 1.55;
}

.bx-local .bx-proof {
	padding-left: var(--wp--preset--spacing--bx-sm, 1rem);
	border-left: var(--bx-rule-h) solid var(--bx-red);
}

.bx-proof a {
	color: inherit;
	text-decoration: underline;
	text-decoration-color: var(--bx-red);
	text-underline-offset: 0.15em;
}

/* The map: a 16:9 frame at the body measure with a hairline border, and a
   steel caption in the figure register every other figure on the site
   uses. aspect-ratio reserves the height before the lazy frame loads, so
   the page's CLS stays at 0.000; the iframe's own width and height
   attributes are the same ratio for a page whose stylesheet has not
   arrived. No shadow, no radius. */
.bx-local .bx-map {
	max-width: 68ch;
	margin-inline: 0 !important;
	margin-block: var(--wp--preset--spacing--bx-lg, 2rem) 0;
}

.bx-map__frame {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 16 / 9;
	border: 1px solid var(--bx-hairline);
	background: var(--bx-white);
}

.bx-map__caption {
	color: var(--bx-steel);
	font-size: var(--wp--preset--font-size--bx-sm, 0.875rem);
	margin-top: 0.5rem;
	text-align: left;
}

.bx-locations {
	list-style: none;
	padding-inline-start: 0;
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
	gap: var(--wp--preset--spacing--bx-xs, 0.75rem) var(--wp--preset--spacing--bx-lg, 2rem);
}

/* Blog index (home.html, and the same card lent to index.html's generic
   archive fallback: category, tag, author and search results). One column
   at every width, full width cards, the same flat bx-card treatment and red
   rule heading every other card in this theme uses. The featured image
   block carries its own aspectRatio attribute (set in the template, WP 6.5+),
   which reserves the box before the file loads, so a slow image never shifts
   the title or excerpt below it: CLS stays zero whether or not a post has a
   thumbnail. */
.bx-blog-list__items {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--bx-lg, 2rem);
}

.bx-blog-list__item {
	padding: var(--wp--preset--spacing--bx-md, 1.5rem);
}

.bx-blog-list__thumb {
	margin-block-end: var(--wp--preset--spacing--bx-sm, 1rem);
}

.bx-blog-list__thumb img {
	width: 100%;
	height: auto;
	object-fit: cover;
}

.bx-blog-list__item .wp-block-post-title {
	margin-block: 0 var(--wp--preset--spacing--bx-xxs, 0.5rem);
}

.bx-blog-list__item .wp-block-post-date {
	display: block;
	color: var(--bx-steel);
	margin-block-end: var(--wp--preset--spacing--bx-xs, 0.75rem);
}

/* Single post byline and featured image. The author name comes from
   wp:post-author-name, which prints the display name of whichever WordPress
   user owns the post; the companion plugin's BX_Author forces that to the
   canonical content account on every insert and update, and its display name
   is the identity name (see includes/class-bx-author.php). Nothing here
   reads or repeats the name. */
.bx-post-meta {
	gap: var(--wp--preset--spacing--bx-sm, 1rem);
	color: var(--bx-steel);
	margin-block-start: var(--wp--preset--spacing--bx-xs, 0.75rem);
}

.bx-post-meta .wp-block-post-author-name::before {
	content: "By ";
}

.bx-post-featured {
	margin-block: var(--wp--preset--spacing--bx-md, 1.5rem) 0;
}

.bx-post-featured img {
	width: 100%;
	height: auto;
	object-fit: cover;
}

/* Read next. Appended to post content by functions.php's bx_append_read_next
   on the_content for single posts, so this is plain markup rather than a
   block: the append runs after core's do_blocks, which already turned every
   block comment in the post into HTML by the time this filter sees it. */
.bx-read-next {
	margin-block-start: var(--wp--preset--spacing--bx-lg, 2rem);
	padding-block-start: var(--wp--preset--spacing--bx-md, 1.5rem);
	border-top: 1px solid var(--bx-hairline, #E2E2E2);
}

.bx-read-next p {
	margin: 0;
	font-weight: 600;
}

/* ------------------------------------------------------------------ *
 * 11 . COMPANION PLUGIN BLOCKS
 * The plugin renders answer-first, faq, faq-item, comparison-table and the
 * price board (vent-calculator), all under bx-* class names, plus the
 * contact form under [ss_contact_form] and the NAP seam in the footer and
 * on the contact page.
 *
 * THE PRICE BOARD BRINGS ITS OWN STYLESHEET, written against the --bx-*
 * tokens directly (blocks/vent-calculator/style.scss, redesign Task 6,
 * 2026-09-04). Nothing in this file targets the board's wrapper class or any
 * .bx-board part, and nothing may: the board's visibility contract is the
 * hidden attribute, and a theme rule setting display on one of its parts
 * would show two states at once. tests/test-board.php pins the absence.
 *
 * THE TOKEN BRIDGE THAT USED TO SIT HERE IS GONE (same task). It defined the
 * other brand's preset names (a cream colour, a dark brown, a sand, and a
 * body/small/h2/h3 size scale) on the plugin's selectors so the older
 * block stylesheets, written against that brand, resolved to this palette.
 * The board no longer reads any of them, and the rules below cover what the
 * remaining blocks (answer-first, faq, comparison-table) read from it, in
 * the same way they already covered the values those blocks hardcode: a
 * 16px radius, an rgba brown hairline. They are written with a :root prefix
 * purely to raise specificity above the plugin's single class selectors so
 * load order cannot decide the outcome. Their spacing names fall back to
 * literals that equal this theme's own spacing presets, so no spacing rule
 * is needed here.
 * ------------------------------------------------------------------ */

/* Answer-first. The plugin ships a 16px radius cream card with a brown left
   rule and 2rem of padding on every side. Here it is not a card at all
   (redesign B, Task 3): no frame, no ground, no radius, no inset but the
   one the rule needs. The 4px red left rule stays, and it is this section's
   one red rule. Council round 2 (UI/AD 8, the top-of-page sweep) found the
   earlier hairline frame drawing a 1080px box with 340px of empty card at
   1440 on every inner page; the rule alone, at body measure, is the offer
   page's own four-row treatment. */
:root .bx-answer-first {
	border: 0;
	border-left: var(--bx-rule-h) solid var(--bx-red);
	border-radius: 0;
	background: transparent;
	padding: 0 0 0 var(--wp--preset--spacing--bx-sm, 1rem);
	max-width: 68ch;
	margin-inline: 0 !important;
}

/* The eyebrow ("The short answer") is steel, sentence case, 700, at the
   14px small size. The plugin's own stylesheet (blocks/answer-first/
   style.scss) sets text-transform: uppercase and letter-spacing: 0.08em on
   `.bx-answer-first .bx-eyebrow` at (0,2,0); this rule's :root prefix takes
   it to (0,2,1) so the theme wins whatever the load order. Red and tracked
   caps are both out here by rule: red is the phone, the rule, the button
   and the stamp; caps are the board, the stamp and the H1s (whole-branch
   review 2026-09-04, finding 3). */
:root .bx-answer-first .bx-eyebrow {
	font-family: var(--bx-ff);
	font-size: var(--wp--preset--font-size--bx-sm, 0.875rem);
	font-weight: 700;
	letter-spacing: 0;
	text-transform: none;
	color: var(--bx-steel);
}

:root .bx-answer-first .bx-last-updated {
	font-size: var(--wp--preset--font-size--bx-sm, 0.875rem);
	color: var(--bx-steel);
}

:root .bx-answer-first__text {
	color: var(--bx-ink);
	font-size: var(--wp--preset--font-size--bx-lg, 1.25rem);
	line-height: 1.55;
}

/* The separator the converter writes for every "---" in a content file.
   Core draws it as a 100px line centred in the column; here it is a
   left-aligned hairline across the body measure, the same rule the tables
   and the FAQ items use (redesign B, Task 3: council round 2 listed the
   centred separators in the old template's chrome). Written against the
   converter's own output rather than a block style, so no content file has
   to change. The :not() pair repeats core's own selector for the 100px
   width so this rule outranks it, (0,4,0) to (0,3,0), whatever the load
   order. */
.bx-body .wp-block-separator:not(.is-style-wide):not(.is-style-dots) {
	width: 100%;
	max-width: 68ch;
	margin-inline: 0 !important;
	margin-block: var(--wp--preset--spacing--bx-lg, 2rem);
	height: 0;
	border: 0;
	border-top: 1px solid var(--bx-hairline);
	opacity: 1;
}

/* FAQ. Native details and summary, so the only work here is type and rules:
   a hairline above the first item and below each one, the marker in ink,
   the question a step under the H3 size rather than the near-H2 size the
   plugin ships (redesign B, Task 3: council round 2 listed "FAQ questions at
   near-H2 size" in the old chrome). No card, no ground, no radius. */
:root .bx-faq__heading {
	font-family: var(--bx-ff);
	font-size: var(--wp--preset--font-size--bx-xxl, 2rem);
	color: var(--bx-ink);
}

:root .bx-faq__items {
	border-top: 1px solid var(--bx-hairline);
	max-width: 68ch;
	margin-inline: 0 !important;
}

:root .bx-faq-item {
	border-bottom: 1px solid var(--bx-hairline);
	background: transparent;
	border-radius: 0;
}

/* The question is a disclosure control, not a line of prose, and it measured
   20.7px tall at every width (qa-loop, 2026-09-04): a 44px target here, the
   marker and the text centred on it. */
:root .bx-faq-item .bx-faq-question {
	display: flex;
	align-items: center;
	box-sizing: border-box;
	min-height: 44px;
	padding-block: 0.6rem;
}

/* The plus and minus marker is ink, not red: red is the phone, the rule,
   the button and the stamp (finding 3, same review). */
:root .bx-faq-item .bx-faq-question::before {
	color: var(--bx-ink);
}

:root .bx-faq-item .bx-faq-question__text {
	font-family: var(--bx-ff);
	font-size: var(--wp--preset--font-size--bx-lg, 1.25rem);
	font-weight: 700;
	color: var(--bx-ink);
}

:root .bx-faq-item .bx-faq-answer {
	color: var(--bx-ink);
	font-size: var(--wp--preset--font-size--bx-body, 1.0625rem);
	line-height: 1.55;
}

/* Comparison table. The site's own table treatment is a hairline frame with
   no header fill (section 2), so the plugin's dark header band and zebra
   striping are flattened to it. */
:root .bx-comparison-table .bx-ctable {
	border: 1px solid var(--bx-hairline);
	font-size: var(--wp--preset--font-size--bx-body, 1.0625rem);
}

:root .bx-comparison-table .bx-ctable thead th {
	background: transparent;
	color: var(--bx-ink);
	font-size: var(--wp--preset--font-size--bx-sm, 0.875rem);
	font-weight: 700;
}

:root .bx-comparison-table .bx-ctable tbody th[scope="row"] {
	background: transparent;
	color: var(--bx-ink);
}

:root .bx-comparison-table .bx-ctable tbody tr:nth-child(even) td {
	background: transparent;
}

:root .bx-comparison-table .bx-ctable th,
:root .bx-comparison-table .bx-ctable td {
	border: 0;
	border-top: 1px solid var(--bx-hairline);
}

:root .bx-comparison-table .bx-ctable__caption {
	font-family: var(--bx-ff);
	color: var(--bx-ink);
}

:root .bx-comparison-table .bx-ctable__source {
	font-size: var(--wp--preset--font-size--bx-sm, 0.875rem);
	color: var(--bx-steel);
}

/* The contact form the plugin renders under [ss_contact_form]. Its markup
   contract is fixed in the plugin (section#ss-contact.ss-contact, its h2, and
   form.ss-contact-form), so the theme borrows the ported lead panel look
   rather than restating a second form design. */
.bx-contact-form .ss-contact__heading {
	font-family: var(--bx-ff);
	color: var(--bx-ink);
}

.bx-contact-form .ss-contact-form {
	max-width: 560px;
	padding: 24px;
	border: 1px solid var(--bx-hairline);
	border-radius: 0;
	background: var(--bx-white);
}

.bx-contact-form .ss-contact-form label {
	display: block;
	font-size: 0.875rem;
	font-weight: 600;
	color: var(--bx-ink);
	margin: 10px 0 4px;
}

.bx-contact-form .ss-contact-form input[type="text"],
.bx-contact-form .ss-contact-form input[type="tel"],
.bx-contact-form .ss-contact-form input[type="email"],
.bx-contact-form .ss-contact-form textarea,
.bx-contact-form .ss-contact-form select {
	width: 100%;
	padding: 10px 12px;
	border: 1px solid var(--bx-hairline);
	border-radius: 0;
	font-family: var(--bx-ff);
	font-size: 1rem;
	color: var(--bx-ink);
	background: var(--bx-white);
	box-sizing: border-box;
}

.bx-contact-form .ss-contact-form button,
.bx-contact-form .ss-contact-form input[type="submit"] {
	width: 100%;
	margin-top: 14px;
	padding: 14px;
	border: 2px solid var(--bx-red);
	border-radius: 0;
	background: var(--bx-red);
	color: var(--bx-white);
	font-family: var(--bx-ff);
	font-stretch: var(--bx-w-display);
	font-size: var(--wp--preset--font-size--bx-body, 1.0625rem);
	font-weight: 700;
	letter-spacing: 0;
	text-transform: none;
	cursor: pointer;
}

.bx-contact-form .ss-contact-form button:hover,
.bx-contact-form .ss-contact-form button:focus,
.bx-contact-form .ss-contact-form input[type="submit"]:hover,
.bx-contact-form .ss-contact-form input[type="submit"]:focus {
	background: var(--bx-red-press);
	border-color: var(--bx-red-press);
}

.bx-contact-form .ss-contact--done {
	max-width: 560px;
	margin-top: 10px;
	padding: 14px;
	border: 1px solid var(--bx-hairline);
	border-left: 4px solid var(--bx-red);
	background: var(--bx-white);
	color: var(--bx-ink);
	font-size: 0.9375rem;
}

/* The plugin's footer social line wears the same quiet register as the legal
   line, which is where it sits. */
.bx-footer .foot-socials {
	margin-block: 0;
	font-size: 0.875rem;
}

/* ------------------------------------------------------------------ *
 * 12 . PRINT
 * ------------------------------------------------------------------ */

@media print {
	/* #bx-bar, NOT .bx-bar. The rule shipped for months matching only the
	   class, and BX_Callbar::html() renders `id="bx-bar"` with no class of that
	   name on it, so the sticky bar printed on every sheet. The class selector
	   stays because the ported live-site markup in section 8 still uses it;
	   both forms are listed rather than one being guessed at. Found 2026-09-02.

	   The plugin's own callbar.css carries the same rule, because the bar has
	   to behave on a page where this theme is not active. This one covers the
	   ported .bx-bar markup, which the plugin stylesheet knows nothing about. */
	#bx-bar,
	.bx-bar,
	.bx-header .wp-block-navigation,
	.bx-rail {
		display: none !important;
	}

	/* Sticky is a screen behaviour. A sticky header in a print stylesheet is
	   an ordinary block anyway in every engine that matters, but saying so
	   costs one line and removes the question. */
	.wp-site-blocks > header {
		position: static;
	}
}
